Skip to content

Commit 7281ec4

Browse files
authored
Merge pull request topcoder-platform#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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 67 additions & 11 deletions
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 0 additions & 4 deletions
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

Lines changed: 18 additions & 10 deletions
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

Lines changed: 141 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy