diff --git a/README.md b/README.md index f2158298..a3a77830 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ The [Topcoder](https://www.topcoder.com) lib for internal ReactJS projects. + +### Configuration for AV-Scan scorer review type ID + +Change the property in `AV_SCAN_SCORER_REVIEW_TYPE_ID` in config. + ### Development ```shell # Install dependencies diff --git a/__tests__/__snapshots__/index.js.snap b/__tests__/__snapshots__/index.js.snap index e7ee51bc..fd8bd27a 100644 --- a/__tests__/__snapshots__/index.js.snap +++ b/__tests__/__snapshots__/index.js.snap @@ -17,6 +17,8 @@ Object { "getActiveChallengesCountInit": [Function], "getDetailsDone": [Function], "getDetailsInit": [Function], + "getMmSubmissionsDone": [Function], + "getMmSubmissionsInit": [Function], "getSubmissionsDone": [Function], "getSubmissionsInit": [Function], "loadResultsDone": [Function], @@ -426,14 +428,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], }, @@ -467,6 +472,7 @@ Object { "getApiV2": [Function], "getApiV3": [Function], "getApiV4": [Function], + "getApiV5": [Function], "getTcM2mToken": [Function], }, "billing": Object { @@ -510,6 +516,10 @@ Object { "default": undefined, "getReviewOpportunitiesService": [Function], }, + "submissions": Object { + "default": undefined, + "getService": [Function], + }, "terms": Object { "default": undefined, "getService": [Function], @@ -527,6 +537,12 @@ Object { "getService": [Function], }, }, + "submission": Object { + "default": undefined, + "getFinalScore": [Function], + "getProvisionalScore": [Function], + "processMMSubmissions": [Function], + }, "tc": Object { "COMPETITION_TRACKS": Object { "DATA_SCIENCE": "data_science", diff --git a/__tests__/reducers/__snapshots__/challenge.js.snap b/__tests__/reducers/__snapshots__/challenge.js.snap index 9db7c531..7ddb822b 100644 --- a/__tests__/reducers/__snapshots__/challenge.js.snap +++ b/__tests__/reducers/__snapshots__/challenge.js.snap @@ -6,7 +6,9 @@ Object { "details": null, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -27,7 +29,9 @@ Object { "fetchChallengeFailure": false, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -48,7 +52,9 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -66,7 +72,9 @@ Object { "fetchChallengeFailure": false, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "12345", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -87,8 +95,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [], @@ -114,8 +124,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [ @@ -143,8 +155,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -170,8 +184,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "12345", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -196,8 +212,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -223,8 +241,10 @@ Object { "fetchChallengeFailure": false, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [ @@ -252,8 +272,10 @@ Object { "fetchChallengeFailure": false, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [ @@ -281,8 +303,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [ @@ -310,8 +334,10 @@ Object { "fetchChallengeFailure": false, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "12345", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [ @@ -339,8 +365,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [], @@ -366,8 +394,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [ @@ -395,8 +425,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -422,8 +454,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "12345", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -448,8 +482,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -471,7 +507,9 @@ Object { "details": null, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -492,7 +530,9 @@ Object { "fetchChallengeFailure": false, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -513,7 +553,9 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -531,7 +573,9 @@ Object { "fetchChallengeFailure": false, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "12345", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -552,8 +596,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [], @@ -579,8 +625,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [ @@ -608,8 +656,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -635,8 +685,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "12345", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -661,8 +713,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -684,7 +738,9 @@ Object { "details": null, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -705,7 +761,9 @@ Object { "fetchChallengeFailure": false, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -726,7 +784,9 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -744,7 +804,9 @@ Object { "fetchChallengeFailure": false, "loadingCheckpoints": false, "loadingDetailsForChallengeId": "12345", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object {}, "mySubmissionsManagement": Object {}, "registering": false, @@ -765,8 +827,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [], @@ -792,8 +856,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "12345", "v2": Array [ @@ -821,8 +887,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -848,8 +916,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "12345", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, @@ -874,8 +944,10 @@ Object { "fetchChallengeFailure": "Unknown error", "loadingCheckpoints": false, "loadingDetailsForChallengeId": "", + "loadingMMSubmissionsForChallengeId": "", "loadingResultsForChallengeId": "", "loadingSubmissionsForChallengeId": "", + "mmSubmissions": Array [], "mySubmissions": Object { "challengeId": "", "v2": null, diff --git a/config/default.json b/config/default.json new file mode 100644 index 00000000..23136c0f --- /dev/null +++ b/config/default.json @@ -0,0 +1,5 @@ +{ + "AV_SCAN_SCORER_REVIEW_TYPE_ID": "", + "PAGE_SIZE": 50, + "REVIEW_OPPORTUNITY_PAGE_SIZE": 1000 +} diff --git a/config/development.json b/config/development.json new file mode 100644 index 00000000..d7c751d4 --- /dev/null +++ b/config/development.json @@ -0,0 +1,3 @@ +{ + "AV_SCAN_SCORER_REVIEW_TYPE_ID": "" +} diff --git a/config/jest/default.js b/config/jest/default.js index 9479649f..aa792822 100644 --- a/config/jest/default.js +++ b/config/jest/default.js @@ -1,3 +1,9 @@ const config = require('topcoder-react-utils/config/jest/default'); +const nodeConfig = require('config'); -module.exports = config; +module.exports = { + ...config, + globals: { + CONFIG: nodeConfig, + }, +}; diff --git a/config/production.json b/config/production.json new file mode 100644 index 00000000..d7c751d4 --- /dev/null +++ b/config/production.json @@ -0,0 +1,3 @@ +{ + "AV_SCAN_SCORER_REVIEW_TYPE_ID": "" +} diff --git a/config/webpack/default.js b/config/webpack/default.js index 932840d6..a1a3c63b 100644 --- a/config/webpack/default.js +++ b/config/webpack/default.js @@ -1,7 +1,15 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +const webpack = require('webpack'); + module.exports = { + plugins: [ + // eslint-disable-next-line global-require + new webpack.DefinePlugin({ CONFIG: JSON.stringify(require('config')) }), + ], // Don't include the dependencies to keep built bundle small, // they will be provided by the app using this lib externals: [ + 'config', 'qs', 'lodash', 'le_node', @@ -17,4 +25,7 @@ module.exports = { 'to-capital-case', 'topcoder-react-utils', ], + node: { + fs: 'empty', + }, }; diff --git a/docs/actions.challenge.md b/docs/actions.challenge.md index 23d16fb5..015a2abd 100644 --- a/docs/actions.challenge.md +++ b/docs/actions.challenge.md @@ -24,6 +24,8 @@ Actions related to Topcoder challenges APIs. * [.updateChallengeDone(uuid, challenge, tokenV3)](#module_actions.challenge.updateChallengeDone) ⇒ <code>Action</code> * [.getActiveChallengesCountInit()](#module_actions.challenge.getActiveChallengesCountInit) ⇒ <code>Action</code> * [.getActiveChallengesCountDone(handle, tokenV3)](#module_actions.challenge.getActiveChallengesCountDone) ⇒ <code>Action</code> + * [.getMMSubmissionsInit(challengeId)](#module_actions.challenge.getMMSubmissionsInit) ⇒ <code>Action</code> + * [.getMMSubmissionsDone(challengeId, submitterIds, registrants, tokenV3)](#module_actions.challenge.getMMSubmissionsDone) ⇒ <code>Action</code> <a name="module_actions.challenge.dropCheckpoints"></a> @@ -31,14 +33,14 @@ Actions related to Topcoder challenges APIs. Creates an action that drops from Redux store all checkpoints loaded before. -**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) <a name="module_actions.challenge.dropResults"></a> ### actions.challenge.dropResults() ⇒ <code>Action</code> Creates an action that drops from Redux store all challenge results loaded before. -**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) <a name="module_actions.challenge.getDetailsInit"></a> ### actions.challenge.getDetailsInit(challengeId) ⇒ <code>Action</code> @@ -93,7 +95,7 @@ challenge. Creates an action that signals beginning of registration for a challenge. -**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) <a name="module_actions.challenge.registerDone"></a> ### actions.challenge.registerDone(auth, challengeId) ⇒ <code>Action</code> @@ -114,7 +116,7 @@ Creates an action that registers user for a challenge. Creates an action that signals beginning of user unregistration from a challenge. -**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) <a name="module_actions.challenge.unregisterDone"></a> ### actions.challenge.unregisterDone(auth, challengeId) ⇒ <code>Action</code> @@ -161,7 +163,7 @@ Creates an action that loads challenge results. Creates an action that signals beginning of challenge checkpoints data loading. -**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) <a name="module_actions.challenge.fetchCheckpointsDone"></a> ### actions.challenge.fetchCheckpointsDone(tokenV2, challengeId) @@ -180,7 +182,7 @@ Creates an action that loads challenge checkpoints data. Creates an action that Toggles checkpoint details panel in the Topcoder Submission Management Page. -**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) **Todo** - [ ] This is UI action relevant to a specific page in specific app. Must be @@ -197,7 +199,7 @@ Creates an action that Toggles checkpoint details panel in the Topcoder ### actions.challenge.updateChallengeInit(uuid) ⇒ <code>Action</code> Creates an action that signals beginning of challenge details update. -**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) **Todo** - [ ] No idea, why we have this action. This functionality should be covered @@ -214,7 +216,7 @@ Creates an action that signals beginning of challenge details update. ### actions.challenge.updateChallengeDone(uuid, challenge, tokenV3) ⇒ <code>Action</code> Creates an action that updates challenge details. -**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) **Todo** - [ ] No idea, why we have this action. This functionality should be covered @@ -233,7 +235,7 @@ Creates an action that updates challenge details. ### actions.challenge.getActiveChallengesCountInit() ⇒ <code>Action</code> Creates an action that signals beginning of getting count of user's active challenges. -**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) <a name="module_actions.challenge.getActiveChallengesCountDone"></a> ### actions.challenge.getActiveChallengesCountDone(handle, tokenV3) ⇒ <code>Action</code> @@ -246,3 +248,28 @@ Creates an action that gets count of user's active challenges from the backend. | handle | <code>String</code> | Topcoder user handle. | | tokenV3 | <code>String</code> | Optional. Topcoder auth token v3. Without token only public challenges will be counted. With the token provided, the action will also count private challenges related to this user. | + + +### actions.challenge.getMMSubmissionsInit(challengeId) ⇒ <code>Action</code> +Creates an action that signals beginning of Marathon Match submissions loading. + +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +<a name="module_actions.challenge.getMMSubmissionsInit"></a> + +| Param | Type | Description | +| --- | --- | --- | +| challengeId | <code>String</code> | The challenge id. | + +### actions.challenge.getMMSubmissionsDone(challengeId, submitterIds, registrants, tokenV3) ⇒ <code>Action</code> +Creates an action that loads Marathon Match submissions to the specified +challenge. + +**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge) +<a name="module_actions.challenge.getMMSubmissionsDone"></a> + +| Param | Type | Description | +| --- | --- | --- | +| challengeId | <code>String</code> | The challenge id. | +| submitterIds | <code>Array</code> | The ids of submitters | +| registrants | <code>Array</code> | The registrants of challenge | +| tokenV3 | <code>String</code> | Topcoder auth token v3. | diff --git a/docs/index.md b/docs/index.md index 185c46d5..9139f14a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -143,6 +143,12 @@ action to really cancel group loading.</p> <dd><p>Reducer for state.terms.</p> </dd> <dt> +<a href="services.submissions.md">services.submissions</a></dt> +<dd><p>his module provides a service for convenient manipulation with + Topcoder submissions via TC API.</p> +</dd> +<dt> +<dt> <a href="services.api.md">services.api</a></dt> <dd><p>This module provides a service for conventient access to Topcoder APIs.</p> </dd> @@ -301,3 +307,8 @@ the proxy will forward them to the service only if LOG_ENTRIES_TOKEN is set).</p <dd><p>Collection of url function.</p> </dd> <dt> + <dt> +<a href="submission.md">submission</a></dt> +<dd><p>Collection of submission function.</p> +</dd> +<dt> diff --git a/docs/reducers.challenge.md b/docs/reducers.challenge.md index d2f2bd0e..eb3514f6 100644 --- a/docs/reducers.challenge.md +++ b/docs/reducers.challenge.md @@ -15,6 +15,8 @@ State segment managed by this reducer has the following strcuture: * [.default](#module_reducers.challenge.default) * [.factory(options)](#module_reducers.challenge.factory) ⇒ <code>Promise</code> * _inner_ + * [~onGetMMSubmissionsInit(state, action)](#module_reducers.challenge..onGetMMSubmissionsInit) ⇒ <code>Object</code> + * [~onGetMMSubmissionsDone(state, action)](#module_reducers.challenge..onGetDetailsDone) ⇒ <code>Object</code> * [~onGetDetailsInit(state, action)](#module_reducers.challenge..onGetDetailsInit) ⇒ <code>Object</code> * [~onGetDetailsDone(state, action)](#module_reducers.challenge..onGetDetailsDone) ⇒ <code>Object</code> * [~onGetSubmissionsInit(state, action)](#module_reducers.challenge..onGetSubmissionsInit) ⇒ <code>Object</code> @@ -34,7 +36,7 @@ State segment managed by this reducer has the following strcuture: ### reducers.challenge.default Reducer with default intial state. -**Kind**: static property of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Kind**: static property of [<code>reducers.challenge</code>](#module_reducers.challenge) <a name="module_reducers.challenge.factory"></a> ### reducers.challenge.factory(options) ⇒ <code>Promise</code> @@ -42,7 +44,7 @@ Factory which creates a new reducer with its initial state tailored to the given options object, if specified (for server-side rendering). If options object is not specified, it creates just the default reducer. Accepted options are: -**Kind**: static method of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Kind**: static method of [<code>reducers.challenge</code>](#module_reducers.challenge) **Resolves**: <code>Function(state, action): state</code> New reducer. | Param | Type | Default | Description | @@ -53,18 +55,48 @@ object is not specified, it creates just the default reducer. Accepted options a | [options.challenge.challengeDetails.id] | <code>String</code> | <code>''</code> | Optional. ID of the challenge to load details for. | | [options.challenge.challengeDetails.mySubmission] | <code>Boolean</code> | <code>false</code> | Optional. The flag indicates whether load my submission. | +<a name="module_reducers.challenge..onGetMMSubmissionsInit"></a> + +### reducers.challenge~onGetMMSubmissionsInit(state, action) ⇒ <code>Object</code> +Handles CHALLENGE/GET_MM_SUBMISSION_INIT action. + +**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Returns**: <code>Object</code> - New state + +| Param | Type | +| --- | --- | +| state | <code>Object</code> | +| action | <code>Object</code> | + +<a name="module_reducers.challenge..onGetDetailsDone"></a> + +### reducers.challenge~onGetMMSubmissionsDone(state, action) ⇒ <code>Object</code> +Handles CHALLENGE/GET_MM_SUBMISSION_DONE action. +Note, that it silently discards received details if the ID of received +challenge mismatches the one stored in loadingMMSubmissionsForChallengeId field +of the state. + +**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Returns**: <code>Object</code> - New state. + +| Param | Type | +| --- | --- | +| state | <code>Object</code> | +| action | <code>Object</code> | + + <a name="module_reducers.challenge..onGetDetailsInit"></a> ### reducers.challenge~onGetDetailsInit(state, action) ⇒ <code>Object</code> Handles CHALLENGE/GET_DETAILS_INIT action. -**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) **Returns**: <code>Object</code> - New state | Param | Type | | --- | --- | -| state | <code>Object</code> | -| action | <code>Object</code> | +| state | <code>Object</code> | +| action | <code>Object</code> | <a name="module_reducers.challenge..onGetDetailsDone"></a> @@ -74,26 +106,26 @@ Note, that it silently discards received details if the ID of received challenge mismatches the one stored in loadingDetailsForChallengeId field of the state. -**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) **Returns**: <code>Object</code> - New state. | Param | Type | | --- | --- | -| state | <code>Object</code> | -| action | <code>Object</code> | +| state | <code>Object</code> | +| action | <code>Object</code> | <a name="module_reducers.challenge..onGetSubmissionsInit"></a> ### reducers.challenge~onGetSubmissionsInit(state, action) ⇒ <code>Object</code> Handles CHALLENGE/GET_SUBMISSION_INIT action. -**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) **Returns**: <code>Object</code> - New state. | Param | Type | | --- | --- | -| state | <code>Object</code> | -| action | <code>Object</code> | +| state | <code>Object</code> | +| action | <code>Object</code>| <a name="module_reducers.challenge..onGetSubmissionsDone"></a> @@ -128,8 +160,8 @@ Handles CHALLENGE/LOAD_RESULTS_INIT action. | Param | Type | | --- | --- | -| state | <code>Object</code> | -| action | <code>Object</code> | +| state | <code>Object</code> | +| action | <code>Object</code> | <a name="module_reducers.challenge..onLoadResultsDone"></a> @@ -141,7 +173,7 @@ Handles CHALLENGE/LOAD_RESULTS_DONE action. | Param | Type | | --- | --- | | state | <code>Object</code> | -| action | <code>Object</code> | +| action | <code>Object</code> | <a name="module_reducers.challenge..onRegisterDone"></a> @@ -164,15 +196,15 @@ Handles CHALLENGE/UNREGISTER_DONE action. | Param | Type | | --- | --- | -| state | <code>Object</code> | -| action | <code>Object</code> | +| state | <code>Object</code> | +| action | <code>Object</code> | <a name="module_reducers.challenge..onUpdateChallengeInit"></a> ### reducers.challenge~onUpdateChallengeInit(state, actions) ⇒ <code>Object</code> Handles CHALLENGE/UPDATE_CHALLENGE_INIT. -**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) **Returns**: <code>Object</code> - New state. | Param | Type | Description | @@ -185,7 +217,7 @@ Handles CHALLENGE/UPDATE_CHALLENGE_INIT. ### reducers.challenge~onUpdateChallengeDone(state, actions) ⇒ <code>Object</code> Handles CHALLENGE/UPDATE_CHALLENGE_DONE. -**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) **Returns**: <code>Object</code> - New state. | Param | Type | Description | @@ -198,7 +230,7 @@ Handles CHALLENGE/UPDATE_CHALLENGE_DONE. ### reducers.challenge~onGetActiveChallengesCountDone(state, action) ⇒ <code>Object</code> Handles CHALLENGE/GET_ACTIVE_CHALLENGES_COUNT_DONE action. -**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) **Returns**: <code>Object</code> - New state | Param | Type | Description | @@ -211,7 +243,7 @@ Handles CHALLENGE/GET_ACTIVE_CHALLENGES_COUNT_DONE action. ### reducers.challenge~create(initialState) ⇒ <code>function</code> Creates a new Challenge reducer with the specified initial state. -**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) +**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge) **Returns**: <code>function</code> - Challenge reducer. | Param | Type | Description | diff --git a/docs/services.submissions.md b/docs/services.submissions.md new file mode 100644 index 00000000..201297e5 --- /dev/null +++ b/docs/services.submissions.md @@ -0,0 +1,57 @@ +<a name="module_services.submissions"></a> + +## services.submissions +This module provides a service for searching for Topcoder +submissions via API V5. + +* [services.submissions](#module_services.submissions) + * _static_ + * [.getService(tokenV3)](#module_services.submissions.getService) ⇒ <code>SubmissionService</code> + * _inner_ + * [~SubmissionsService](#module_services.submissions..SubmissionsService) + * [new SubmissionsService(tokenV3)](#new_module_services.submissions..SubmissionsService_new) + * [.getSubmissions(filters, params)](#module_services.submissions..SubmissionsService+getSubmissions) ⇒ <code>Promise</code> + +<a name="module_services.submissions.getService"></a> + +### services.submissions.getService(tokenV3) ⇒ <code>SubmissionsService</code> +Returns a new or existing submissions service. + +**Kind**: static method of [<code>services.submissions</code>](#module_services.submissions) +**Returns**: <code>SubmissionsService</code> - Submissions service object + +| Param | Type | Description | +| --- | --- | --- | +| tokenV3 | <code>String</code> | Auth token for Topcoder API v5. | + +<a name="module_services.submissions..SubmissionsService"></a> + +### services.submissions~SubmissionsService +Service class. + +**Kind**: inner class of [<code>services.submissions</code>](#module_services.submissions) + +* [~SubmissionsService](#module_services.submissions..SubmissionsService) + * [new SubmissionsService(tokenV3)](#new_module_services.submissions..SubmissionsService_new) + * [.getSubmissions(filters, params)](#module_services.submissions..SubmissionsService+getSubmissions) ⇒ <code>Promise</code> + +<a name="new_module_services.submissions..SubmissionsService_new"></a> + +#### new SubmissionsService(tokenV3) + +| Param | Type | Description | +| --- | --- | --- | +| tokenV3 | <code>String</code> | Auth token for Topcoder API v5. | + +<a name="module_services.submissions..SubmissionsService+getSubmissions"></a> + +#### submissionsService.getSubmissions(filters, params) ⇒ <code>Promise</code> +Get submissions of challenge + +**Kind**: instance method of [<code>SubmissionsService</code>](#module_services.submissions..SubmissionsService) +**Returns**: <code>Promise</code> - Resolves to the submissions array. + +| Param | Type | Description | +| --- | --- | --- | +| filters | <code>Object</code> | The filters object | +| params | <code>Object</code> | The params object | diff --git a/docs/submission.md b/docs/submission.md new file mode 100644 index 00000000..ff48298b --- /dev/null +++ b/docs/submission.md @@ -0,0 +1,38 @@ +<a name="module_submission"></a> + +## submission +Collection of submission functions. + +* [submission](#module_submission) + * [.getProvisionalScore(submission)](#module_submission.getProvisionalScore) + * [.getFinalScore(submission)](#module_submission.getFinalScore) + * [.processMMSubmissions(submissions, resources, registrants)](#module_submission.processMMSubmissions) + +<a name="module_submission.getProvisionalScore"></a> +### submission.getProvisionalScore(submission) +Get provisional score of submission +**Kind**: static method of [<code>submission</code>](#module_submission) + +| Param | Type | +| --- | --- | +| submission | <code>Object</code> | + +<a name="module_submission.getFinalScore"></a> +### submission.getFinalScore(submission) +Get final score of submission +**Kind**: static method of [<code>submission</code>](#module_submission) + +| Param | Type | +| --- | --- | +| submission | <code>Object</code> | + +<a name="module_submission.processMMSubmissions"></a> +### submission.processMMSubmissions(submissions, resources, registrants) +Process submissions of MM challenge +**Kind**: static method of [<code>submission</code>](#module_submission) + +| Param | Type | +| --- | --- | +| submission | <code>Object</code> | +| resources | <code>Array</code> | +| registrants | <code>Array</code> | diff --git a/package.json b/package.json index a8163f0b..30247526 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "version": "0.7.15", "dependencies": { "auth0-js": "^6.8.4", + "config": "^3.1.0", "isomorphic-fetch": "^2.2.1", "le_node": "^1.7.0", "lodash": "^4.17.10", diff --git a/src/actions/challenge-listing.js b/src/actions/challenge-listing.js index e60f10f0..1ab0d8f5 100644 --- a/src/actions/challenge-listing.js +++ b/src/actions/challenge-listing.js @@ -1,7 +1,7 @@ /** * Challenge listing actions. */ - +/* global CONFIG */ import _ from 'lodash'; import { createActions } from 'redux-actions'; import { decodeToken } from 'tc-accounts'; @@ -10,12 +10,12 @@ import { processSRM, COMPETITION_TRACKS } from '../utils/tc'; import { services } from '../services'; import { errors } from '../utils'; import * as filterUtil from '../utils/challenge/filter'; -import * as config from '../config'; + const { fireErrorMessage } = errors; const { getService } = services.challenge; const { getReviewOpportunitiesService } = services.reviewOpportunities; -const { PAGE_SIZE, REVIEW_OPPORTUNITY_PAGE_SIZE } = config; +const { PAGE_SIZE, REVIEW_OPPORTUNITY_PAGE_SIZE } = CONFIG; /** * Process filter diff --git a/src/actions/challenge.js b/src/actions/challenge.js index f5dfb09b..9c00f33f 100644 --- a/src/actions/challenge.js +++ b/src/actions/challenge.js @@ -2,12 +2,48 @@ * @module "actions.challenge" * @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 {Array} prev Optional. data loaded so far. + */ +function getAll(getter, page = 1, 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: PAGE_SIZE, + }).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, current); + }); +} + /** * @static @@ -83,6 +119,49 @@ function getSubmissionsDone(challengeId, tokenV2) { }); } +/** + * @static + * @desc Creates an action that signals beginning of Marathon Match submissions loading. + * @param {String} challengeId Challenge ID. + * @return {Action} + */ +function getMMSubmissionsInit(challengeId) { + /* As a safeguard, we enforce challengeId to be string (in case somebody + * passes in a number, by mistake). */ + return _.toString(challengeId); +} + + +/** + * @static + * @desc Creates an action that loads Marathon Match submissions to the specified + * challenge. + * @param {String} challengeId Challenge ID. + * @param {Array} submitterIds The array of submitter ids. + * @param {Array} registrants The array of register. + * @param {String} tokenV3 Topcoder auth token v3. + * @return {Action} + */ +function getMMSubmissionsDone(challengeId, submitterIds, registrants, tokenV3) { + const filter = { challengeId }; + const memberService = getMemberService(tokenV3); + const submissionsService = getSubmissionService(tokenV3); + const calls = [ + memberService.getMembersInformation(submitterIds), + getAll(params => submissionsService.getSubmissions(filter, params)), + ]; + return Promise.all(calls).then(([resources, submissions]) => { + const finalSubmissions = submissionUtil + .processMMSubmissions(submissions, resources, registrants); + return { + challengeId, + submissions: finalSubmissions, + tokenV3, + }; + }); +} + + /** * @static * @desc Creates an action that signals beginning of registration for a @@ -305,5 +384,7 @@ export default createActions({ UPDATE_CHALLENGE_DONE: updateChallengeDone, GET_ACTIVE_CHALLENGES_COUNT_INIT: getActiveChallengesCountInit, GET_ACTIVE_CHALLENGES_COUNT_DONE: getActiveChallengesCountDone, + GET_MM_SUBMISSIONS_INIT: getMMSubmissionsInit, + GET_MM_SUBMISSIONS_DONE: getMMSubmissionsDone, }, }); diff --git a/src/config/index.js b/src/config/index.js deleted file mode 100644 index 2a5ab790..00000000 --- a/src/config/index.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - PAGE_SIZE: 50, - REVIEW_OPPORTUNITY_PAGE_SIZE: 1000, -}; diff --git a/src/index.js b/src/index.js index 15b0e7d7..7dcdfde6 100644 --- a/src/index.js +++ b/src/index.js @@ -12,5 +12,5 @@ export { actions } from './actions'; export { services } from './services'; export { - challenge, logger, errors, tc, time, mock, url, + challenge, logger, errors, tc, time, mock, url, submission, } from './utils'; diff --git a/src/reducers/challenge.js b/src/reducers/challenge.js index 889623e1..2d5b47c9 100644 --- a/src/reducers/challenge.js +++ b/src/reducers/challenge.js @@ -113,6 +113,44 @@ function onGetSubmissionsDone(state, action) { }; } +/** + * Handles CHALLENGE/GET_MM_SUBMISSION_INIT action. + * @param {Object} state + * @param {Object} action + * @return {Object} New state. + */ +function onGetMMSubmissionsInit(state, action) { + return { + ...state, + loadingMMSubmissionsForChallengeId: action.payload, + mmSubmissions: [], + }; +} + +/** + * Handles CHALLENGE/GET_MM_SUBMISSION_DONE action. + * @param {Object} state Previous state. + * @param {Object} action Action. + */ +function onGetMMSubmissionsDone(state, action) { + if (action.error) { + logger.error('Failed to get Marathon Match submissions for the challenge', action.payload); + return { + ...state, + loadingMMSubmissionsForChallengeId: '', + mmSubmissions: [], + }; + } + + const { challengeId, submissions } = action.payload; + if (challengeId.toString() !== state.loadingMMSubmissionsForChallengeId) return state; + return { + ...state, + loadingMMSubmissionsForChallengeId: '', + mmSubmissions: submissions, + }; +} + /** * Handles challengeActions.fetchCheckpointsDone action. * @param {Object} state Previous state. @@ -294,6 +332,8 @@ function create(initialState) { [a.getDetailsDone]: onGetDetailsDone, [a.getSubmissionsInit]: onGetSubmissionsInit, [a.getSubmissionsDone]: onGetSubmissionsDone, + [a.getMmSubmissionsInit]: onGetMMSubmissionsInit, + [a.getMmSubmissionsDone]: onGetMMSubmissionsDone, [smpActions.smp.deleteSubmissionDone]: (state, { payload }) => ({ ...state, mySubmissions: { @@ -324,6 +364,7 @@ function create(initialState) { loadingCheckpoints: false, loadingDetailsForChallengeId: '', loadingResultsForChallengeId: '', + loadingMMSubmissionsForChallengeId: '', mySubmissions: {}, checkpoints: null, registering: false, @@ -331,6 +372,7 @@ function create(initialState) { resultsLoadedForChallengeId: '', unregistering: false, updatingChallengeUuid: '', + mmSubmissions: [], })); } diff --git a/src/services/api.js b/src/services/api.js index 7c1e3355..97a045a1 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -264,6 +264,7 @@ export function getApi(version, token) { export const getApiV2 = token => getApi('V2', token); export const getApiV3 = token => getApi('V3', token); export const getApiV4 = token => getApi('V4', token); +export const getApiV5 = token => getApi('V5', token); /** * Gets a valid TC M2M token, either requesting one from TC Auth0 API, or diff --git a/src/services/index.js b/src/services/index.js index b9707791..4d776832 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -14,6 +14,7 @@ import * as userSetting from './user-settings'; import * as user from './user'; import * as lookup from './lookup'; import * as userTraits from './user-traits'; +import * as submissions from './submissions'; export const services = { api, @@ -29,6 +30,7 @@ export const services = { reviewOpportunities, lookup, userTraits, + submissions, }; export default undefined; diff --git a/src/services/members.js b/src/services/members.js index 62afbf36..9afa0311 100644 --- a/src/services/members.js +++ b/src/services/members.js @@ -289,6 +289,18 @@ class MembersService { const res = await this.private.api.get(`/members/${handle}/verify?token=${emailVerifyToken}`); return getApiResponsePayload(res); } + + /** + * Get members information + * @param {Array} userIds the member ids + */ + async getMembersInformation(userIds) { + const query = `query=${encodeURI(_.map(userIds, id => `userId:${id}`).join(' OR '))}`; + const limit = `limit=${userIds.length}`; + const url = `/members/_search?fields=userId%2Chandle%2CphotoURL%2CfirstName%2ClastName&${query}&${limit}`; + const res = await this.private.api.get(url); + return getApiResponsePayload(res); + } } let lastInstance = null; diff --git a/src/services/submissions.js b/src/services/submissions.js new file mode 100644 index 00000000..6de0e6c2 --- /dev/null +++ b/src/services/submissions.js @@ -0,0 +1,57 @@ +/** + * @module "services.submission" + * @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'; + +/** + * Submission service. + */ +class SubmissionsService { + /** + * Creates a new SubmissionService instance. + * @param {String} tokenV3 Optional. Auth token for Topcoder API v3. + */ + constructor(tokenV3) { + this.private = { + apiV5: getApi('V5', tokenV3), + tokenV3, + }; + } + + /** + * Get submissions of challenge + * @param {Object} filters + * @param {Object} params + * @return {Promise} Resolves to the api response. + */ + 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); + } +} + +let lastInstance = null; +/** + * Returns a new or existing submissions service. + * @param {String} tokenV3 Optional. Auth token for Topcoder API v3. + * @return {SubmissionsService} Submissions service object + */ +export function getService(tokenV3) { + if (!lastInstance || lastInstance.private.tokenV3 !== tokenV3) { + lastInstance = new SubmissionsService(tokenV3); + } + return lastInstance; +} + +/* Using default export would be confusing in this case. */ +export default undefined; diff --git a/src/utils/index.js b/src/utils/index.js index e7e4ff52..e3fc752f 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -10,6 +10,7 @@ import * as filter from './challenge/filter'; import * as buckets from './challenge/buckets'; import * as sort from './challenge/sort'; import * as url from './url'; +import * as submission from './submission'; const challenge = { filter, @@ -25,4 +26,5 @@ export { mock, errors, url, + submission, }; diff --git a/src/utils/submission.js b/src/utils/submission.js new file mode 100644 index 00000000..d3bec7e4 --- /dev/null +++ b/src/utils/submission.js @@ -0,0 +1,177 @@ +/** + * Various submissions functions. + */ +/* global CONFIG */ +/* eslint-disable no-param-reassign */ +import _ from 'lodash'; + +const { AV_SCAN_SCORER_REVIEW_TYPE_ID } = CONFIG; + +function round(num, decimal) { + if (_.isNaN(num)) { + return 0; + } + const p1 = 10 ** (decimal + 1); + const p2 = 10 ** decimal; + return Math.round(num * p1 / 10) / p2; +} + +function removeDecimal(num, decimal) { + return ((num % decimal) + decimal) % decimal; +} + +function toFixed(num, decimal) { + const result = _.toFinite(round(num, decimal).toFixed(decimal)); + const integerResult = _.toFinite(removeDecimal(result, decimal)); + if (_.isInteger(integerResult)) { + 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; + return pB - pA; + }); + _.each(submissions, (submission, i) => { + submissions[i].provisionalRank = i + 1; + }); + + submissions.sort((a, b) => { + let pA = _.get(a, 'submissions[0]', { provisionalScore: 0 }).finalScore; + let pB = _.get(b, 'submissions[0]', { provisionalScore: 0 }).finalScore; + if (pA === '-') pA = 0; + if (pB === '-') pB = 0; + if (pA > 0) maxFinalScore = pA; + if (pB > 0) maxFinalScore = pB; + return pB - pA; + }); + if (maxFinalScore > 0) { + _.each(submissions, (submission, i) => { + submissions[i].finalRank = i + 1; + }); + } + return { submissions, maxFinalScore }; +} + +/** + * Get provisional score of submission + * @param submission + */ +export function getProvisionalScore(submission) { + const { submissions: subs } = submission; + if (!subs || subs.length === 0) { + return 0; + } + const { provisionalScore } = subs[0]; + if (!provisionalScore || provisionalScore < 0) { + return 0; + } + return provisionalScore; +} + +/** + * Get final score of submission + * @param submission + */ +export function getFinalScore(submission) { + const { submissions: subs } = submission; + if (!subs || subs.length === 0) { + return 0; + } + const { finalScore } = subs[0]; + if (!finalScore || finalScore < 0) { + return 0; + } + 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; + }); + let provisionalScore; + if (validReviews.length > 0) { + provisionalScore = _.get(validReviews, '[0].score', 0); + if (_.isString(provisionalScore)) provisionalScore = _.toFinite(provisionalScore); + provisionalScore = toFixed(provisionalScore, 5); + } else { + provisionalScore = -1; + } + + let finalScore = _.get(submission, 'reviewSummation[0].aggregateScore', 0); + if (_.isString(finalScore)) finalScore = _.toFinite(finalScore); + if (finalScore > 0) { + finalScore = toFixed(finalScore, 5); + } else { + finalScore = 0; + } + 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;