Skip to content

Commit 7281ec4

Browse files
authored
Merge pull request #108 from topcoder-platform/develop
hot fixes for 0.8.x
2 parents da86320 + 83f9f0c commit 7281ec4

File tree

7 files changed

+231
-27
lines changed

7 files changed

+231
-27
lines changed

__tests__/__snapshots__/index.js.snap

+2
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,10 @@ Object {
318318
},
319319
},
320320
"submission": Object {
321+
"default": undefined,
321322
"getFinalScore": [Function],
322323
"getProvisionalScore": [Function],
324+
"processMMSubmissions": [Function],
323325
},
324326
"tc": Object {
325327
"COMPETITION_TRACKS": Object {

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"lint:js": "./node_modules/.bin/eslint --ext .js,.jsx .",
3232
"test": "npm run lint && npm run jest"
3333
},
34-
"version": "0.8.2",
34+
"version": "0.8.3",
3535
"dependencies": {
3636
"auth0-js": "^6.8.4",
3737
"config": "^3.2.0",

src/actions/challenge.js

+67-11
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,48 @@
33
* @desc Actions related to Topcoder challenges APIs.
44
*/
55

6+
/* global CONFIG */
67
import _ from 'lodash';
78
import { config } from 'topcoder-react-utils';
89
import { createActions } from 'redux-actions';
910
import { getService as getChallengesService } from '../services/challenges';
1011
import { getService as getSubmissionService } from '../services/submissions';
12+
import { getService as getMemberService } from '../services/members';
1113
import { getApi } from '../services/api';
14+
import * as submissionUtil from '../utils/submission';
15+
16+
const { PAGE_SIZE } = CONFIG;
17+
18+
/**
19+
* Private. Loads from the backend all data matching some conditions.
20+
* @param {Function} getter Given params object of shape { limit, offset }
21+
* loads from the backend at most "limit" data, skipping the first
22+
* "offset" ones. Returns loaded data as an array.
23+
* @param {Number} page Optional. Next page of data to load.
24+
* @param {Number} perPage Optional. The size of the page content to load.
25+
* @param {Array} prev Optional. data loaded so far.
26+
*/
27+
function getAll(getter, page = 1, perPage = PAGE_SIZE, prev) {
28+
/* Amount of submissions to fetch in one API call. 50 is the current maximum
29+
* amount of submissions the backend returns, event when the larger limit is
30+
* explicitely required. */
31+
return getter({
32+
page,
33+
perPage,
34+
}).then((res) => {
35+
if (res.length === 0) {
36+
return prev || res;
37+
}
38+
// parse submissions
39+
let current = [];
40+
if (prev) {
41+
current = prev.concat(res);
42+
} else {
43+
current = res;
44+
}
45+
return getAll(getter, 1 + page, perPage, current);
46+
});
47+
}
1248

1349
/**
1450
* @static
@@ -106,10 +142,26 @@ function getMMSubmissionsInit(challengeId) {
106142
* @param {String} tokenV3 Topcoder auth token v3.
107143
* @return {Action}
108144
*/
109-
async function getMMSubmissionsDone(challengeId, registrants, tokenV3) {
145+
function getMMSubmissionsDone(challengeId, registrants, tokenV3) {
146+
const filter = { challengeId };
147+
const memberService = getMemberService(tokenV3);
110148
const submissionsService = getSubmissionService(tokenV3);
111-
const submissions = await submissionsService.getSubmissions(challengeId);
112-
return { challengeId, submissions, tokenV3 };
149+
150+
// TODO: Move those numbers to configs
151+
return getAll(params => submissionsService.getSubmissions(filter, params), 1, 500)
152+
.then((submissions) => {
153+
const userIds = _.uniq(_.map(submissions, sub => sub.memberId));
154+
return memberService.getMembersInformation(userIds)
155+
.then((resources) => {
156+
const finalSubmissions = submissionUtil
157+
.processMMSubmissions(submissions, resources, registrants);
158+
return {
159+
challengeId,
160+
submissions: finalSubmissions,
161+
tokenV3,
162+
};
163+
});
164+
});
113165
}
114166

115167
/**
@@ -319,8 +371,8 @@ function getActiveChallengesCountDone(handle, tokenV3) {
319371
* @param {String} submissionId The submission id
320372
* @return {Action}
321373
*/
322-
function getSubmissionInformationInit(submissionId) {
323-
return _.toString(submissionId);
374+
function getSubmissionInformationInit(challengeId, submissionId) {
375+
return { challengeId: _.toString(challengeId), submissionId: _.toString(submissionId) };
324376
}
325377

326378
/**
@@ -330,12 +382,16 @@ function getSubmissionInformationInit(submissionId) {
330382
* @param {String} tokenV3 Topcoder auth token v3.
331383
* @return {Action}
332384
*/
333-
function getSubmissionInformationDone(submissionId, tokenV3) {
334-
return getSubmissionService(tokenV3)
335-
.getSubmissionInformation(submissionId)
336-
.then(response => ({
337-
submissionId, submission: response,
338-
}));
385+
function getSubmissionInformationDone(challengeId, submissionId, tokenV3) {
386+
const filter = { challengeId };
387+
const submissionsService = getSubmissionService(tokenV3);
388+
389+
return getAll(params => submissionsService.getSubmissions(filter, params), 1, 500)
390+
.then((submissions) => {
391+
const submission = _.find(submissions, { id: submissionId });
392+
_.remove(submission.review, review => review.typeId === CONFIG.AV_SCAN_SCORER_REVIEW_TYPE_ID);
393+
return { submissionId, submission };
394+
});
339395
}
340396

341397
export default createActions({

src/reducers/challenge.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ function onGetActiveChallengesCountDone(state, { payload, error }) {
327327
function onGetSubmissionInformationInit(state, action) {
328328
return {
329329
...state,
330-
loadingSubmissionInformationForSubmissionId: action.payload,
330+
loadingSubmissionInformationForChallengeId: action.payload.challengeId,
331+
loadingSubmissionInformationForSubmissionId: action.payload.submissionId,
331332
submissionInformation: null,
332333
};
333334
}

src/services/groups.js

-4
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,6 @@ export function checkUserGroups(groupIds, userGroups, knownGroups) {
140140
function handleApiResponse(response) {
141141
if (!response.ok) throw new Error(response.statusText);
142142
return response.json();
143-
// return response.json().then(({ result }) => {
144-
// return result;
145-
// if (result.status !== 200) throw new Error(result.content);
146-
// });
147143
}
148144

149145
/**

src/services/submissions.js

+18-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @desc This module provides a service for convenient manipulation with
44
* Topcoder submissions via TC API. Currently only used for MM challenges
55
*/
6-
6+
import qs from 'qs';
77
import { getApi } from './api';
88

99
/**
@@ -16,20 +16,27 @@ class SubmissionsService {
1616
*/
1717
constructor(tokenV3) {
1818
this.private = {
19-
broker: getApi('MM_BROKER', tokenV3),
19+
apiV5: getApi('V5', tokenV3),
2020
tokenV3,
2121
};
2222
}
2323

2424
/**
2525
* Get submissions of challenge
26-
* @param {Object} challengeId
26+
* @param {Object} filters
27+
* @param {Object} params
2728
* @return {Promise} Resolves to the api response.
2829
*/
29-
async getSubmissions(challengeId) {
30-
const url = `/v5/submissions?challengeId=${challengeId}`;
31-
return this.private.broker.get(url)
32-
.then(res => (res.ok ? res.json() : new Error(res.statusText)));
30+
async getSubmissions(filters, params) {
31+
const query = {
32+
...filters,
33+
...params,
34+
};
35+
36+
const url = `/submissions?${qs.stringify(query, { encode: false })}`;
37+
return this.private.apiV5.get(url)
38+
.then(res => (res.ok ? res.json() : new Error(res.statusText)))
39+
.then(res => res);
3340
}
3441

3542
/**
@@ -38,9 +45,10 @@ class SubmissionsService {
3845
* @returns {Promise} Resolves to the api response.
3946
*/
4047
async getSubmissionInformation(submissionId) {
41-
const url = `/v5/submissions/${submissionId}`;
42-
return this.private.broker.get(url)
43-
.then(res => (res.ok ? res.json() : new Error(res.statusText)));
48+
const url = `/submissions/${submissionId}`;
49+
return this.private.apiV5.get(url)
50+
.then(res => (res.ok ? res.json() : new Error(res.statusText)))
51+
.then(res => res);
4452
}
4553
}
4654

src/utils/submission.js

+141
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,84 @@
11
/**
22
* Various submissions functions.
33
*/
4+
/* global CONFIG */
5+
/* eslint-disable no-param-reassign */
6+
import _ from 'lodash';
7+
8+
const { AV_SCAN_SCORER_REVIEW_TYPE_ID } = CONFIG;
9+
10+
function removeDecimal(num) {
11+
const re = new RegExp('^-?\\d+');
12+
return num.toString().match(re)[0];
13+
}
14+
15+
function toAcurateFixed(num, decimal) {
16+
const re = new RegExp(`^-?\\d+(?:.\\d{0,${(decimal)}})?`);
17+
return num.toString().match(re)[0];
18+
}
19+
20+
function toFixed(num, decimal) {
21+
if (_.isNaN(parseFloat(num))) return num;
22+
num = parseFloat(num);
23+
24+
const result = _.toFinite(toAcurateFixed(num, decimal));
25+
const integerResult = _.toFinite(removeDecimal(num));
26+
27+
if (_.isInteger(result)) {
28+
return integerResult;
29+
}
30+
return result;
31+
}
32+
33+
function getMMChallengeHandleStyle(handle, registrants) {
34+
const style = _.get(_.find(registrants, m => m.handle === handle), 'colorStyle', null);
35+
if (style) return JSON.parse(style.replace(/(\w+):\s*([^;]*)/g, '{"$1": "$2"}'));
36+
return {};
37+
}
38+
39+
/**
40+
* Process each submission rank of MM challenge
41+
* @param submissions the array of submissions
42+
*/
43+
function processRanks(submissions) {
44+
let maxFinalScore = 0;
45+
submissions.sort((a, b) => {
46+
let pA = _.get(a, 'submissions[0]', { provisionalScore: 0 }).provisionalScore;
47+
let pB = _.get(b, 'submissions[0]', { provisionalScore: 0 }).provisionalScore;
48+
if (pA === '-') pA = 0;
49+
if (pB === '-') pB = 0;
50+
if (pA === pB) {
51+
const timeA = new Date(_.get(a, 'submissions[0].submissionTime'));
52+
const timeB = new Date(_.get(b, 'submissions[0].submissionTime'));
53+
return timeA - timeB;
54+
}
55+
return pB - pA;
56+
});
57+
_.each(submissions, (submission, i) => {
58+
submissions[i].provisionalRank = i + 1;
59+
});
60+
61+
submissions.sort((a, b) => {
62+
let pA = _.get(a, 'submissions[0]', { finalScore: 0 }).finalScore;
63+
let pB = _.get(b, 'submissions[0]', { finalScore: 0 }).finalScore;
64+
if (pA === '-') pA = 0;
65+
if (pB === '-') pB = 0;
66+
if (pA > 0) maxFinalScore = pA;
67+
if (pB > 0) maxFinalScore = pB;
68+
if (pA === pB) {
69+
const timeA = new Date(_.get(a, 'submissions[0].submissionTime'));
70+
const timeB = new Date(_.get(b, 'submissions[0].submissionTime'));
71+
return timeA - timeB;
72+
}
73+
return pB - pA;
74+
});
75+
if (maxFinalScore > 0) {
76+
_.each(submissions, (submission, i) => {
77+
submissions[i].finalRank = i + 1;
78+
});
79+
}
80+
return { submissions, maxFinalScore };
81+
}
482

583
/**
684
* Get provisional score of submission
@@ -33,3 +111,66 @@ export function getFinalScore(submission) {
33111
}
34112
return finalScore;
35113
}
114+
115+
/**
116+
* Process submissions of MM challenge
117+
* @param submissions the array of submissions
118+
* @param resources the challenge resources
119+
* @param registrants the challenge registrants
120+
*/
121+
export function processMMSubmissions(submissions, resources, registrants) {
122+
const data = {};
123+
const result = [];
124+
125+
_.each(submissions, (submission) => {
126+
const { memberId } = submission;
127+
let memberHandle;
128+
const resource = _.find(resources, r => _.get(r, 'userId').toString() === memberId.toString());
129+
if (_.isEmpty(resource)) {
130+
memberHandle = memberId;
131+
} else {
132+
memberHandle = _.has(resource, 'handle') ? _.get(resource, 'handle') : memberId.toString();
133+
}
134+
if (!data[memberHandle]) {
135+
data[memberHandle] = [];
136+
}
137+
const validReviews = _.filter(submission.review,
138+
r => !_.isEmpty(r) && (r.typeId !== AV_SCAN_SCORER_REVIEW_TYPE_ID));
139+
validReviews.sort((a, b) => {
140+
const dateA = new Date(a.created);
141+
const dateB = new Date(b.created);
142+
return dateB - dateA;
143+
});
144+
145+
const provisionalScore = toFixed(_.get(validReviews, '[0].score', '-'), 5);
146+
const finalScore = toFixed(_.get(submission, 'reviewSummation[0].aggregateScore', '-'), 5);
147+
148+
data[memberHandle].push({
149+
submissionId: submission.id,
150+
submissionTime: submission.created,
151+
provisionalScore,
152+
finalScore,
153+
});
154+
});
155+
156+
_.each(data, (value, key) => {
157+
result.push({
158+
submissions: [...value.sort((a, b) => new Date(b.submissionTime)
159+
.getTime() - new Date(a.submissionTime).getTime())],
160+
member: key,
161+
colorStyle: getMMChallengeHandleStyle(key, registrants),
162+
});
163+
});
164+
165+
const { submissions: finalSubmissions, maxFinalScore } = processRanks(result);
166+
finalSubmissions.sort((a, b) => {
167+
if (maxFinalScore === 0) {
168+
return a.provisionalRank - b.provisionalRank;
169+
}
170+
return a.finalRank - b.finalRank;
171+
});
172+
173+
return finalSubmissions;
174+
}
175+
176+
export default undefined;

0 commit comments

Comments
 (0)