Skip to content

Commit 36fc2f5

Browse files
Merge pull request #443 from topcoder-platform/cherrypick/mm-dashboard
statistics endpoint
2 parents 90e1885 + 309a040 commit 36fc2f5

File tree

5 files changed

+114
-3
lines changed

5 files changed

+114
-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: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,51 @@ 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+
if (res.data.length > 0) return res.data[0]
1004+
return {}
1005+
}
1006+
9621007
module.exports = {
9631008
wrapExpress,
9641009
autoWrapExpress,
@@ -1000,5 +1045,7 @@ module.exports = {
10001045
ensureUserCanModifyChallenge,
10011046
userHasFullAccess,
10021047
sumOfPrizes,
1003-
getGroupById
1048+
getGroupById,
1049+
getChallengeSubmissions,
1050+
getMemberById
10041051
}

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
@@ -1270,6 +1270,49 @@ async function validateWinners (winners, challengeId) {
12701270
}
12711271
}
12721272

1273+
/**
1274+
* Get challenge statistics
1275+
* @param {Object} currentUser the user who perform operation
1276+
* @param {String} id the challenge id
1277+
* @returns {Object} the challenge with given id
1278+
*/
1279+
async function getChallengeStatistics (currentUser, id) {
1280+
const challenge = await getChallenge(currentUser, id)
1281+
// for now, only Data Science challenges are supported
1282+
if (challenge.type !== 'Challenge' && challenge.track !== 'Data Science') {
1283+
throw new errors.BadRequestError(`Challenge of type ${challenge.type} and track ${challenge.track} does not support statistics`)
1284+
}
1285+
// get submissions
1286+
const submissions = await helper.getChallengeSubmissions(challenge.id)
1287+
// for each submission, load member profile
1288+
const map = {}
1289+
for (const submission of submissions) {
1290+
if (!map[submission.memberId]) {
1291+
// Load member profile and cache
1292+
const member = await helper.getMemberById(submission.memberId)
1293+
map[submission.memberId] = {
1294+
photoUrl: member.photoURL,
1295+
rating: _.get(member, 'maxRating.rating', 0),
1296+
ratingColor: _.get(member, 'maxRating.ratingColor', '#9D9FA0'),
1297+
homeCountryCode: member.homeCountryCode,
1298+
handle: member.handle,
1299+
submissions: []
1300+
}
1301+
}
1302+
// add submission
1303+
map[submission.memberId].submissions.push({
1304+
created: submission.created,
1305+
score: _.get(_.find(submission.review || [], r => r.metadata), 'score', 0)
1306+
})
1307+
}
1308+
return _.map(_.keys(map), (userId) => map[userId])
1309+
}
1310+
1311+
getChallengeStatistics.schema = {
1312+
currentUser: Joi.any(),
1313+
id: Joi.id()
1314+
}
1315+
12731316
/**
12741317
* Update challenge.
12751318
* @param {Object} currentUser the user who perform operation
@@ -2058,7 +2101,8 @@ module.exports = {
20582101
getChallenge,
20592102
fullyUpdateChallenge,
20602103
partiallyUpdateChallenge,
2061-
deleteChallenge
2104+
deleteChallenge,
2105+
getChallengeStatistics
20622106
}
20632107

20642108
logger.buildService(module.exports)

0 commit comments

Comments
 (0)