Skip to content

Commit 5a1dd4e

Browse files
Merge pull request #438 from topcoder-platform/aggregate-mm-data
add endpoint for challenge statistics
2 parents 09c6e34 + e610527 commit 5a1dd4e

File tree

5 files changed

+113
-3
lines changed

5 files changed

+113
-3
lines changed

config/default.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ module.exports = {
4848
// in bytes
4949
FILE_UPLOAD_SIZE_LIMIT: process.env.FILE_UPLOAD_SIZE_LIMIT
5050
? Number(process.env.FILE_UPLOAD_SIZE_LIMIT) : 50 * 1024 * 1024, // 50M
51+
// TODO: change this to localhost
52+
SUBMISSIONS_API_URL: process.env.SUBMISSIONS_API_URL || 'https://api.topcoder-dev.com/v5/submissions',
53+
MEMBERS_API_URL: process.env.MEMBERS_API_URL || 'https://api.topcoder-dev.com/v5/members',
5154
RESOURCES_API_URL: process.env.RESOURCES_API_URL || 'http://localhost:4000/v5/resources',
5255
// TODO: change this to localhost
5356
RESOURCE_ROLES_API_URL: process.env.RESOURCE_ROLES_API_URL || 'http://api.topcoder-dev.com/v5/resource-roles',

src/common/helper.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,50 @@ async function getGroupById (groupId) {
959959
}
960960
}
961961

962+
/**
963+
* Get challenge submissions
964+
* @param {String} challengeId the challenge id
965+
* @returns {Array} the submission
966+
*/
967+
async function getChallengeSubmissions (challengeId) {
968+
const token = await getM2MToken()
969+
let allSubmissions = []
970+
// get search is paginated, we need to get all pages' data
971+
let page = 1
972+
while (true) {
973+
const result = await axios.get(`${config.SUBMISSIONS_API_URL}?challengeId=${challengeId}`, {
974+
headers: { Authorization: `Bearer ${token}` },
975+
params: {
976+
page,
977+
perPage: 100
978+
}
979+
})
980+
const ids = result.data || []
981+
if (ids.length === 0) {
982+
break
983+
}
984+
allSubmissions = allSubmissions.concat(ids)
985+
page += 1
986+
if (result.headers['x-total-pages'] && page > Number(result.headers['x-total-pages'])) {
987+
break
988+
}
989+
}
990+
return allSubmissions
991+
}
992+
993+
/**
994+
* Get member by ID
995+
* @param {String} userId the user ID
996+
* @returns {Object}
997+
*/
998+
async function getMemberById (userId) {
999+
const token = await getM2MToken()
1000+
const res = await axios.get(`${config.MEMBERS_API_URL}?userId=${userId}`, {
1001+
headers: { Authorization: `Bearer ${token}` }
1002+
})
1003+
return res.data || {}
1004+
}
1005+
9621006
module.exports = {
9631007
wrapExpress,
9641008
autoWrapExpress,
@@ -1000,5 +1044,7 @@ module.exports = {
10001044
ensureUserCanModifyChallenge,
10011045
userHasFullAccess,
10021046
sumOfPrizes,
1003-
getGroupById
1047+
getGroupById,
1048+
getChallengeSubmissions,
1049+
getMemberById
10041050
}

src/controllers/ChallengeController.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ async function getChallenge (req, res) {
3838
res.send(result)
3939
}
4040

41+
/**
42+
* Get challenge statistics
43+
* @param {Object} req the request
44+
* @param {Object} res the response
45+
*/
46+
async function getChallengeStatistics (req, res) {
47+
const result = await service.getChallengeStatistics(req.authUser, req.params.challengeId)
48+
res.send(result)
49+
}
50+
4151
/**
4252
* Fully update challenge
4353
* @param {Object} req the request
@@ -77,5 +87,6 @@ module.exports = {
7787
getChallenge,
7888
fullyUpdateChallenge,
7989
partiallyUpdateChallenge,
80-
deleteChallenge
90+
deleteChallenge,
91+
getChallengeStatistics
8192
}

src/routes.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ module.exports = {
6161
scopes: [DELETE, ALL]
6262
}
6363
},
64+
'/challenges/:challengeId/statistics': {
65+
get: {
66+
controller: 'ChallengeController',
67+
method: 'getChallengeStatistics',
68+
}
69+
},
6470
'/challenge-types': {
6571
get: {
6672
controller: 'ChallengeTypeController',

src/services/ChallengeService.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,49 @@ getChallenge.schema = {
12091209
id: Joi.id()
12101210
}
12111211

1212+
/**
1213+
* Get challenge statistics
1214+
* @param {Object} currentUser the user who perform operation
1215+
* @param {String} id the challenge id
1216+
* @returns {Object} the challenge with given id
1217+
*/
1218+
async function getChallengeStatistics (currentUser, id) {
1219+
const challenge = await getChallenge(currentUser, id)
1220+
// for now, only Data Science challenges are supported
1221+
if (challenge.type !== 'Challenge' && challenge.track !== 'Data Science') {
1222+
throw new errors.BadRequestError(`Challenge of type ${challenge.type} and track ${challenge.track} does not support statistics`)
1223+
}
1224+
// get submissions
1225+
const submissions = await helper.getChallengeSubmissions(challenge.id)
1226+
// for each submission, load member profile
1227+
const map = {}
1228+
for (const submission of submissions) {
1229+
if (!map[submission.memberId]) {
1230+
// Load member profile and cache
1231+
const member = await helper.getMemberById(submission.memberId)
1232+
map[submission.memberId] = {
1233+
photoUrl: member.photoURL,
1234+
rating: _.get(member, 'maxRating.rating', 0),
1235+
ratingColor: _.get(member, 'maxRating.rating', '#9D9FA0'),
1236+
homeCountryCode: member.homeCountryCode,
1237+
handle: member.handle,
1238+
submissions: []
1239+
}
1240+
}
1241+
// add submission
1242+
map[submission.memberId].submissions.push({
1243+
created: submission.created,
1244+
score: _.get(_.find(submission.review || [], r => r.metadata), 'score', 0)
1245+
})
1246+
}
1247+
return _.map(_.keys(map), (userId) => map[userId])
1248+
}
1249+
1250+
getChallengeStatistics.schema = {
1251+
currentUser: Joi.any(),
1252+
id: Joi.id()
1253+
}
1254+
12121255
/**
12131256
* Check whether given two phases array are different.
12141257
* @param {Array} phases the first phases array
@@ -2078,7 +2121,8 @@ module.exports = {
20782121
getChallenge,
20792122
fullyUpdateChallenge,
20802123
partiallyUpdateChallenge,
2081-
deleteChallenge
2124+
deleteChallenge,
2125+
getChallengeStatistics
20822126
}
20832127

20842128
logger.buildService(module.exports)

0 commit comments

Comments
 (0)