From cd3ce5e8b1602e163d6bacf8f6fe7d81ad182629 Mon Sep 17 00:00:00 2001 From: Dushyant Bhalgami Date: Fri, 25 Oct 2019 20:24:35 +0530 Subject: [PATCH 01/10] updates for get additional review details for submission without M2M token --- __tests__/__snapshots__/index.js.snap | 5 + src/actions/challenge.js | 78 ++++++++++++-- src/reducers/challenge.js | 3 +- src/services/groups.js | 4 - src/services/submissions.js | 28 +++-- src/utils/submission.js | 141 ++++++++++++++++++++++++++ 6 files changed, 233 insertions(+), 26 deletions(-) diff --git a/__tests__/__snapshots__/index.js.snap b/__tests__/__snapshots__/index.js.snap index 87dd0545..79a7d795 100644 --- a/__tests__/__snapshots__/index.js.snap +++ b/__tests__/__snapshots__/index.js.snap @@ -212,14 +212,17 @@ Object { "countReset": [Function], "debug": [Function], "dir": [Function], + "dirxml": [Function], "error": [Function], "group": [Function], "groupCollapsed": [Function], "groupEnd": [Function], "info": [Function], "log": [Function], + "table": [Function], "time": [Function], "timeEnd": [Function], + "timeLog": [Function], "trace": [Function], "warn": [Function], }, @@ -318,8 +321,10 @@ Object { }, }, "submission": Object { + "default": undefined, "getFinalScore": [Function], "getProvisionalScore": [Function], + "processMMSubmissions": [Function], }, "tc": Object { "COMPETITION_TRACKS": Object { diff --git a/src/actions/challenge.js b/src/actions/challenge.js index 2a0cd1e8..4627c0a3 100644 --- a/src/actions/challenge.js +++ b/src/actions/challenge.js @@ -3,12 +3,48 @@ * @desc Actions related to Topcoder challenges APIs. */ +/* global CONFIG */ import _ from 'lodash'; import { config } from 'topcoder-react-utils'; import { createActions } from 'redux-actions'; import { getService as getChallengesService } from '../services/challenges'; import { getService as getSubmissionService } from '../services/submissions'; +import { getService as getMemberService } from '../services/members'; import { getApi } from '../services/api'; +import * as submissionUtil from '../utils/submission'; + +const { PAGE_SIZE } = CONFIG; + +/** + * Private. Loads from the backend all data matching some conditions. + * @param {Function} getter Given params object of shape { limit, offset } + * loads from the backend at most "limit" data, skipping the first + * "offset" ones. Returns loaded data as an array. + * @param {Number} page Optional. Next page of data to load. + * @param {Number} perPage Optional. The size of the page content to load. + * @param {Array} prev Optional. data loaded so far. + */ +function getAll(getter, page = 1, perPage = PAGE_SIZE, prev) { + /* Amount of submissions to fetch in one API call. 50 is the current maximum + * amount of submissions the backend returns, event when the larger limit is + * explicitely required. */ + return getter({ + page, + perPage, + }).then((res) => { + if (res.length === 0) { + return prev || res; + } + // parse submissions + let current = []; + if (prev) { + current = prev.concat(res); + } else { + current = res; + } + return getAll(getter, 1 + page, perPage, current); + }); +} /** * @static @@ -106,10 +142,26 @@ function getMMSubmissionsInit(challengeId) { * @param {String} tokenV3 Topcoder auth token v3. * @return {Action} */ -async function getMMSubmissionsDone(challengeId, registrants, tokenV3) { +function getMMSubmissionsDone(challengeId, registrants, tokenV3) { + const filter = { challengeId }; + const memberService = getMemberService(tokenV3); const submissionsService = getSubmissionService(tokenV3); - const submissions = await submissionsService.getSubmissions(challengeId); - return { challengeId, submissions, tokenV3 }; + + // TODO: Move those numbers to configs + return getAll(params => submissionsService.getSubmissions(filter, params), 1, 500) + .then((submissions) => { + const userIds = _.uniq(_.map(submissions, sub => sub.memberId)); + return memberService.getMembersInformation(userIds) + .then((resources) => { + const finalSubmissions = submissionUtil + .processMMSubmissions(submissions, resources, registrants); + return { + challengeId, + submissions: finalSubmissions, + tokenV3, + }; + }); + }); } /** @@ -319,8 +371,8 @@ function getActiveChallengesCountDone(handle, tokenV3) { * @param {String} submissionId The submission id * @return {Action} */ -function getSubmissionInformationInit(submissionId) { - return _.toString(submissionId); +function getSubmissionInformationInit(challengeId, submissionId) { + return { challengeId: _.toString(challengeId), submissionId: _.toString(submissionId) }; } /** @@ -330,12 +382,16 @@ function getSubmissionInformationInit(submissionId) { * @param {String} tokenV3 Topcoder auth token v3. * @return {Action} */ -function getSubmissionInformationDone(submissionId, tokenV3) { - return getSubmissionService(tokenV3) - .getSubmissionInformation(submissionId) - .then(response => ({ - submissionId, submission: response, - })); +function getSubmissionInformationDone(challengeId, submissionId, tokenV3) { + const filter = { challengeId }; + const submissionsService = getSubmissionService(tokenV3); + + return getAll(params => submissionsService.getSubmissions(filter, params), 1, 500) + .then((submissions) => { + const submission = _.find(submissions, { id: submissionId }); + _.remove(submission.review, review => review.typeId === CONFIG.AV_SCAN_SCORER_REVIEW_TYPE_ID); + return { submissionId, submission }; + }); } export default createActions({ diff --git a/src/reducers/challenge.js b/src/reducers/challenge.js index 11ff0ef1..b2edf441 100644 --- a/src/reducers/challenge.js +++ b/src/reducers/challenge.js @@ -327,7 +327,8 @@ function onGetActiveChallengesCountDone(state, { payload, error }) { function onGetSubmissionInformationInit(state, action) { return { ...state, - loadingSubmissionInformationForSubmissionId: action.payload, + loadingSubmissionInformationForChallengeId: action.payload.challengeId, + loadingSubmissionInformationForSubmissionId: action.payload.submissionId, submissionInformation: null, }; } diff --git a/src/services/groups.js b/src/services/groups.js index 9ebba0d5..69603c0f 100644 --- a/src/services/groups.js +++ b/src/services/groups.js @@ -140,10 +140,6 @@ export function checkUserGroups(groupIds, userGroups, knownGroups) { function handleApiResponse(response) { if (!response.ok) throw new Error(response.statusText); return response.json(); - // return response.json().then(({ result }) => { - // return result; - // if (result.status !== 200) throw new Error(result.content); - // }); } /** diff --git a/src/services/submissions.js b/src/services/submissions.js index e420c39e..c8b20052 100644 --- a/src/services/submissions.js +++ b/src/services/submissions.js @@ -3,7 +3,7 @@ * @desc This module provides a service for convenient manipulation with * Topcoder submissions via TC API. Currently only used for MM challenges */ - +import qs from 'qs'; import { getApi } from './api'; /** @@ -16,20 +16,27 @@ class SubmissionsService { */ constructor(tokenV3) { this.private = { - broker: getApi('MM_BROKER', tokenV3), + apiV5: getApi('V5', tokenV3), tokenV3, }; } /** * Get submissions of challenge - * @param {Object} challengeId + * @param {Object} filters + * @param {Object} params * @return {Promise} Resolves to the api response. */ - async getSubmissions(challengeId) { - const url = `/v5/submissions?challengeId=${challengeId}`; - return this.private.broker.get(url) - .then(res => (res.ok ? res.json() : new Error(res.statusText))); + async getSubmissions(filters, params) { + const query = { + ...filters, + ...params, + }; + + const url = `/submissions?${qs.stringify(query, { encode: false })}`; + return this.private.apiV5.get(url) + .then(res => (res.ok ? res.json() : new Error(res.statusText))) + .then(res => res); } /** @@ -38,9 +45,10 @@ class SubmissionsService { * @returns {Promise} Resolves to the api response. */ async getSubmissionInformation(submissionId) { - const url = `/v5/submissions/${submissionId}`; - return this.private.broker.get(url) - .then(res => (res.ok ? res.json() : new Error(res.statusText))); + const url = `/submissions/${submissionId}`; + return this.private.apiV5.get(url) + .then(res => (res.ok ? res.json() : new Error(res.statusText))) + .then(res => res); } } diff --git a/src/utils/submission.js b/src/utils/submission.js index 0b581004..970fea14 100644 --- a/src/utils/submission.js +++ b/src/utils/submission.js @@ -1,6 +1,84 @@ /** * Various submissions functions. */ +/* global CONFIG */ +/* eslint-disable no-param-reassign */ +import _ from 'lodash'; + +const { AV_SCAN_SCORER_REVIEW_TYPE_ID } = CONFIG; + +function removeDecimal(num) { + const re = new RegExp('^-?\\d+'); + return num.toString().match(re)[0]; +} + +function toAcurateFixed(num, decimal) { + const re = new RegExp(`^-?\\d+(?:.\\d{0,${(decimal)}})?`); + return num.toString().match(re)[0]; +} + +function toFixed(num, decimal) { + if (_.isNaN(parseFloat(num))) return num; + num = parseFloat(num); + + const result = _.toFinite(toAcurateFixed(num, decimal)); + const integerResult = _.toFinite(removeDecimal(num)); + + if (_.isInteger(result)) { + return integerResult; + } + return result; +} + +function getMMChallengeHandleStyle(handle, registrants) { + const style = _.get(_.find(registrants, m => m.handle === handle), 'colorStyle', null); + if (style) return JSON.parse(style.replace(/(\w+):\s*([^;]*)/g, '{"$1": "$2"}')); + return {}; +} + +/** + * Process each submission rank of MM challenge + * @param submissions the array of submissions + */ +function processRanks(submissions) { + let maxFinalScore = 0; + submissions.sort((a, b) => { + let pA = _.get(a, 'submissions[0]', { provisionalScore: 0 }).provisionalScore; + let pB = _.get(b, 'submissions[0]', { provisionalScore: 0 }).provisionalScore; + if (pA === '-') pA = 0; + if (pB === '-') pB = 0; + if (pA === pB) { + const timeA = new Date(_.get(a, 'submissions[0].submissionTime')); + const timeB = new Date(_.get(b, 'submissions[0].submissionTime')); + return timeA - timeB; + } + return pB - pA; + }); + _.each(submissions, (submission, i) => { + submissions[i].provisionalRank = i + 1; + }); + + submissions.sort((a, b) => { + let pA = _.get(a, 'submissions[0]', { finalScore: 0 }).finalScore; + let pB = _.get(b, 'submissions[0]', { finalScore: 0 }).finalScore; + if (pA === '-') pA = 0; + if (pB === '-') pB = 0; + if (pA > 0) maxFinalScore = pA; + if (pB > 0) maxFinalScore = pB; + if (pA === pB) { + const timeA = new Date(_.get(a, 'submissions[0].submissionTime')); + const timeB = new Date(_.get(b, 'submissions[0].submissionTime')); + return timeA - timeB; + } + return pB - pA; + }); + if (maxFinalScore > 0) { + _.each(submissions, (submission, i) => { + submissions[i].finalRank = i + 1; + }); + } + return { submissions, maxFinalScore }; +} /** * Get provisional score of submission @@ -33,3 +111,66 @@ export function getFinalScore(submission) { } return finalScore; } + +/** + * Process submissions of MM challenge + * @param submissions the array of submissions + * @param resources the challenge resources + * @param registrants the challenge registrants + */ +export function processMMSubmissions(submissions, resources, registrants) { + const data = {}; + const result = []; + + _.each(submissions, (submission) => { + const { memberId } = submission; + let memberHandle; + const resource = _.find(resources, r => _.get(r, 'userId').toString() === memberId.toString()); + if (_.isEmpty(resource)) { + memberHandle = memberId; + } else { + memberHandle = _.has(resource, 'handle') ? _.get(resource, 'handle') : memberId.toString(); + } + if (!data[memberHandle]) { + data[memberHandle] = []; + } + const validReviews = _.filter(submission.review, + r => !_.isEmpty(r) && (r.typeId !== AV_SCAN_SCORER_REVIEW_TYPE_ID)); + validReviews.sort((a, b) => { + const dateA = new Date(a.created); + const dateB = new Date(b.created); + return dateB - dateA; + }); + + const provisionalScore = toFixed(_.get(validReviews, '[0].score', '-'), 5); + const finalScore = toFixed(_.get(submission, 'reviewSummation[0].aggregateScore', '-'), 5); + + data[memberHandle].push({ + submissionId: submission.id, + submissionTime: submission.created, + provisionalScore, + finalScore, + }); + }); + + _.each(data, (value, key) => { + result.push({ + submissions: [...value.sort((a, b) => new Date(b.submissionTime) + .getTime() - new Date(a.submissionTime).getTime())], + member: key, + colorStyle: getMMChallengeHandleStyle(key, registrants), + }); + }); + + const { submissions: finalSubmissions, maxFinalScore } = processRanks(result); + finalSubmissions.sort((a, b) => { + if (maxFinalScore === 0) { + return a.provisionalRank - b.provisionalRank; + } + return a.finalRank - b.finalRank; + }); + + return finalSubmissions; +} + +export default undefined; From 3393eb23516bb3e953cd141b6a61fdafd163ca4e Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Wed, 30 Oct 2019 17:18:16 +0530 Subject: [PATCH 02/10] test release tag, updated snaps, test npm version --- __tests__/__snapshots__/index.js.snap | 3 --- package.json | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/__tests__/__snapshots__/index.js.snap b/__tests__/__snapshots__/index.js.snap index 79a7d795..f2f97d81 100644 --- a/__tests__/__snapshots__/index.js.snap +++ b/__tests__/__snapshots__/index.js.snap @@ -212,17 +212,14 @@ Object { "countReset": [Function], "debug": [Function], "dir": [Function], - "dirxml": [Function], "error": [Function], "group": [Function], "groupCollapsed": [Function], "groupEnd": [Function], "info": [Function], "log": [Function], - "table": [Function], "time": [Function], "timeEnd": [Function], - "timeLog": [Function], "trace": [Function], "warn": [Function], }, diff --git a/package.json b/package.json index 3cb777eb..6f65b28e 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "lint:js": "./node_modules/.bin/eslint --ext .js,.jsx .", "test": "npm run lint && npm run jest" }, - "version": "0.8.2", + "version": "1000.1.0", "dependencies": { "auth0-js": "^6.8.4", "config": "^3.2.0", From 5d01f64f8e866a9b758c42f8e0960f38a09ca584 Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Wed, 30 Oct 2019 17:25:29 +0530 Subject: [PATCH 03/10] Test release tag --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2bf4f64f..a24a6382 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,7 +28,7 @@ jobs: - attach_workspace: at: . - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - - run: npm publish + - run: npm publish --tag=test-release workflows: version: 2 From 654daa646608811fbd4e7db537a536a55dd9504e Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Thu, 31 Oct 2019 13:29:23 +0530 Subject: [PATCH 04/10] change org to @topcoder-platform --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6f65b28e..beaea337 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ ], "license": "UNLICENSED", "main": "index.js", - "name": "topcoder-react-lib", + "name": "@topcoder-platform/topcoder-react-lib", "repository": { "type": "git", "url": "git+https://github.com/topcoder-platform/topcoder-react-lib.git" From 5206f012cf91694af6dabd76ce1bb45528c55d4e Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Thu, 31 Oct 2019 16:58:50 +0530 Subject: [PATCH 05/10] npm ver bump up "version": "1000.2.0" --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index beaea337..3b12cda1 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "lint:js": "./node_modules/.bin/eslint --ext .js,.jsx .", "test": "npm run lint && npm run jest" }, - "version": "1000.1.0", + "version": "1000.2.0", "dependencies": { "auth0-js": "^6.8.4", "config": "^3.2.0", From 7bae9e32fd716097ff25820318e3e35c5299626e Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Fri, 1 Nov 2019 08:27:25 +0530 Subject: [PATCH 06/10] change org to @topcoder-platform with public access --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a24a6382..f90ae49f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,7 +28,7 @@ jobs: - attach_workspace: at: . - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - - run: npm publish --tag=test-release + - run: npm publish --access public --tag=test-release workflows: version: 2 From 9b35e99b0bf154735c18c2645ffd2d6911867390 Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Fri, 1 Nov 2019 15:23:33 +0530 Subject: [PATCH 07/10] npm bump up --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b12cda1..288884ac 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "lint:js": "./node_modules/.bin/eslint --ext .js,.jsx .", "test": "npm run lint && npm run jest" }, - "version": "1000.2.0", + "version": "1000.2.1", "dependencies": { "auth0-js": "^6.8.4", "config": "^3.2.0", From aa350f53307eb8f3a0ec7876a96195e44ddf5dec Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Fri, 1 Nov 2019 15:54:07 +0530 Subject: [PATCH 08/10] bump up npm version --- .circleci/config.yml | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f90ae49f..a24a6382 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,7 +28,7 @@ jobs: - attach_workspace: at: . - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - - run: npm publish --access public --tag=test-release + - run: npm publish --tag=test-release workflows: version: 2 diff --git a/package.json b/package.json index 288884ac..4d37fa5f 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ ], "license": "UNLICENSED", "main": "index.js", - "name": "@topcoder-platform/topcoder-react-lib", + "name": "topcoder-react-lib", "repository": { "type": "git", "url": "git+https://github.com/topcoder-platform/topcoder-react-lib.git" @@ -31,7 +31,7 @@ "lint:js": "./node_modules/.bin/eslint --ext .js,.jsx .", "test": "npm run lint && npm run jest" }, - "version": "1000.2.1", + "version": "1000.2.2", "dependencies": { "auth0-js": "^6.8.4", "config": "^3.2.0", From d1fdf89f9791112b9570252211965b6a4c9f156c Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Wed, 6 Nov 2019 10:49:13 +0530 Subject: [PATCH 09/10] remove test tag --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a24a6382..2bf4f64f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,7 +28,7 @@ jobs: - attach_workspace: at: . - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - - run: npm publish --tag=test-release + - run: npm publish workflows: version: 2 From 4fad1ffb4ab782a1b7aae18e67c2e017c5ef158c Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Wed, 6 Nov 2019 10:51:50 +0530 Subject: [PATCH 10/10] hot fixes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4d37fa5f..16f4613c 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "lint:js": "./node_modules/.bin/eslint --ext .js,.jsx .", "test": "npm run lint && npm run jest" }, - "version": "1000.2.2", + "version": "0.8.3", "dependencies": { "auth0-js": "^6.8.4", "config": "^3.2.0",