diff --git a/__tests__/shared/components/challenge-detail/MySubmissions/SubmissionsList/index.jsx b/__tests__/shared/components/challenge-detail/MySubmissions/SubmissionsList/index.jsx index 02f78f79b..c92710c07 100644 --- a/__tests__/shared/components/challenge-detail/MySubmissions/SubmissionsList/index.jsx +++ b/__tests__/shared/components/challenge-detail/MySubmissions/SubmissionsList/index.jsx @@ -23,7 +23,7 @@ describe('getDisplayedScores', () => { }); }); - test('shows final scores once the review phase is complete', () => { + test('shows final scores after the review phase is complete', () => { expect(getDisplayedScores( { finalScore: 100, diff --git a/__tests__/shared/services/reviewSummations.js b/__tests__/shared/services/reviewSummations.js new file mode 100644 index 000000000..27f66b326 --- /dev/null +++ b/__tests__/shared/services/reviewSummations.js @@ -0,0 +1,107 @@ +/* eslint-env jest */ +import { config } from 'topcoder-react-utils'; +import getReviewSummations from '../../../src/shared/services/reviewSummations'; + +const baseUrl = `${config.API.V6}${config.URL.REVIEW_SUMMATIONS_API_URL}`; + +describe('reviewSummations service', () => { + const originalFetch = global.fetch; + const originalHeaders = global.Headers; + + beforeAll(() => { + if (!global.Headers) { + global.Headers = class HeadersMock { + constructor() { + this.values = {}; + } + + set(key, value) { + this.values[key] = value; + } + + get(key) { + return this.values[key]; + } + }; + } + }); + + afterAll(() => { + global.fetch = originalFetch; + global.Headers = originalHeaders; + }); + + beforeEach(() => { + global.fetch = jest.fn(); + }); + + it('loads every page when review summations metadata reports more pages', async () => { + global.fetch + .mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ + data: [{ id: 'summation-page-1' }], + meta: { + page: 1, + perPage: 100, + totalPages: 2, + totalItems: 101, + }, + }), + }) + .mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ + data: [{ id: 'summation-page-2' }], + meta: { + page: 2, + perPage: 100, + totalPages: 2, + totalItems: 101, + }, + }), + }); + + const result = await getReviewSummations('token-v3', 'challenge-id'); + + expect(global.fetch).toHaveBeenCalledTimes(2); + expect(global.fetch).toHaveBeenNthCalledWith( + 1, + `${baseUrl}?challengeId=challenge-id&perPage=500&page=1`, + expect.objectContaining({ method: 'GET' }), + ); + expect(global.fetch).toHaveBeenNthCalledWith( + 2, + `${baseUrl}?challengeId=challenge-id&perPage=500&page=2`, + expect.objectContaining({ method: 'GET' }), + ); + expect(result.data).toEqual([ + { id: 'summation-page-1' }, + { id: 'summation-page-2' }, + ]); + expect(result.meta).toEqual(expect.objectContaining({ + page: 2, + perPage: 500, + totalItems: 2, + totalPages: 2, + })); + }); + + it('uses the short-page heuristic only when metadata does not include total pages', async () => { + global.fetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ + data: [{ id: 'summation-only-page' }], + meta: { + page: 1, + perPage: 100, + }, + }), + }); + + const result = await getReviewSummations(null, 'challenge-id'); + + expect(global.fetch).toHaveBeenCalledTimes(1); + expect(result.data).toEqual([{ id: 'summation-only-page' }]); + }); +}); diff --git a/src/shared/components/challenge-detail/MySubmissions/SubmissionsList/index.jsx b/src/shared/components/challenge-detail/MySubmissions/SubmissionsList/index.jsx index 5bae961d3..7a3fe1d64 100644 --- a/src/shared/components/challenge-detail/MySubmissions/SubmissionsList/index.jsx +++ b/src/shared/components/challenge-detail/MySubmissions/SubmissionsList/index.jsx @@ -9,7 +9,6 @@ import moment from 'moment'; import { PrimaryButton, Modal } from 'topcoder-react-ui-kit'; import PT from 'prop-types'; import { services } from 'topcoder-react-lib'; -import { isReviewPhaseComplete } from 'utils/challenge-detail/mm-final-results'; import sortList from 'utils/challenge-detail/sort'; import { getSubmissionStatus } from 'utils/challenge-detail/submission-status'; @@ -77,15 +76,13 @@ const getSubmissionCreatedTime = (submission) => { /** * Returns the scores that should be displayed for a Marathon Match submission row. - * Initial score is the authoritative provisional score for MM submissions, and - * final scores become visible once review is complete or the payload already - * includes a final result during review. + * Initial score is the authoritative provisional score for MM submissions, while + * final scores should surface as soon as Review API provides them. * * @param {Object} submission submission attempt shown in My Submissions. - * @param {Object} challenge challenge that owns the submission. * @returns {{ finalScore: number|null, provisionalScore: number|null }} display-ready scores. */ -export function getDisplayedScores(submission = {}, challenge = {}) { +export function getDisplayedScores(submission = {}) { const toNumericScore = (value) => { if (_.isNil(value) || value === '' || value === '-') { return null; @@ -98,10 +95,9 @@ export function getDisplayedScores(submission = {}, challenge = {}) { const initialScore = toNumericScore(_.get(submission, 'initialScore')); const provisionalScore = toNumericScore(_.get(submission, 'provisionalScore')); const finalScore = toNumericScore(_.get(submission, 'finalScore')); - const showFinalScore = isReviewPhaseComplete(challenge) || !_.isNil(finalScore); return { - finalScore: showFinalScore ? finalScore : null, + finalScore, provisionalScore: !_.isNil(initialScore) ? initialScore : provisionalScore, }; } @@ -467,7 +463,7 @@ class SubmissionsListView extends React.Component { { sortedSubmissions.map((mySubmission) => { - let { finalScore, provisionalScore } = getDisplayedScores(mySubmission, challenge); + let { finalScore, provisionalScore } = getDisplayedScores(mySubmission); if (_.isNumber(finalScore)) { if (finalScore > 0) { finalScore = finalScore.toFixed(2); diff --git a/src/shared/components/challenge-detail/Submissions/SubmissionInformationModal/index.jsx b/src/shared/components/challenge-detail/Submissions/SubmissionInformationModal/index.jsx index ea235fe43..044e02c52 100644 --- a/src/shared/components/challenge-detail/Submissions/SubmissionInformationModal/index.jsx +++ b/src/shared/components/challenge-detail/Submissions/SubmissionInformationModal/index.jsx @@ -61,11 +61,30 @@ class SubmissionInformationModal extends React.Component { render() { const { toggleTestcase, onClose, isLoadingSubmissionInformation, - submissionInformation, showFinalResults, + submissionInformation, } = this.props; const submissionBasicInfo = isLoadingSubmissionInformation ? null : this.getSubmissionBasicInfo(); const testcases = isLoadingSubmissionInformation ? [] : this.getTestcases(); + const toNumericScore = (value) => { + if (_.isNil(value) || value === '' || value === '-') { + return null; + } + + const numeric = Number(value); + return Number.isFinite(numeric) ? numeric : null; + }; + const displayedScores = submissionBasicInfo + ? { + finalScore: toNumericScore(_.get(submissionBasicInfo, 'finalScore')), + provisionalScore: (() => { + const initialScore = toNumericScore(_.get(submissionBasicInfo, 'initialScore')); + return !_.isNil(initialScore) + ? initialScore + : toNumericScore(_.get(submissionBasicInfo, 'provisionalScore')); + })(), + } + : { finalScore: null, provisionalScore: null }; return ( onClose(false)}> @@ -90,12 +109,14 @@ class SubmissionInformationModal extends React.Component {
- {(!submissionBasicInfo.finalScore && submissionBasicInfo.finalScore !== 0) || !showFinalResults ? '-' : submissionBasicInfo.finalScore} + {displayedScores.finalScore === null ? '-' : displayedScores.finalScore}
- {(!submissionBasicInfo.provisionalScore && submissionBasicInfo.provisionalScore !== 0) ? '-' : submissionBasicInfo.provisionalScore} + {displayedScores.provisionalScore === null + ? '-' + : displayedScores.provisionalScore}
{moment(submissionBasicInfo.submissionTime) @@ -180,7 +201,6 @@ SubmissionInformationModal.propTypes = { openTestcase: PT.shape({}).isRequired, clearTestcaseOpen: PT.func.isRequired, submission: PT.shape().isRequired, - showFinalResults: PT.bool.isRequired, }; export default SubmissionInformationModal; diff --git a/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/index.jsx b/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/index.jsx index 527c4da57..acad037d7 100644 --- a/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/index.jsx +++ b/src/shared/components/challenge-detail/Submissions/SubmissionRow/SubmissionHistoryRow/index.jsx @@ -27,7 +27,6 @@ export default function SubmissionHistoryRow({ provisionalScore, submissionTime, createdAt, - showFinalResults, status, challengeStatus, auth, @@ -38,6 +37,10 @@ export default function SubmissionHistoryRow({ // todo: hide download button until update submissions API const hideDownloadForMMRDM = true; const parseScore = (value) => { + if (value === null || value === undefined || value === '' || value === '-') { + return null; + } + const numeric = Number(value); return Number.isFinite(numeric) ? numeric : null; }; @@ -63,9 +66,6 @@ export default function SubmissionHistoryRow({ } }; const getFinalScore = () => { - if (!showFinalResults) { - return 'N/A'; - } if (finalScoreValue === null) { return 'N/A'; } @@ -139,7 +139,6 @@ export default function SubmissionHistoryRow({ SubmissionHistoryRow.defaultProps = { finalScore: null, provisionalScore: null, - showFinalResults: false, isLoggedIn: false, createdAt: null, submissionTime: null, @@ -169,7 +168,6 @@ SubmissionHistoryRow.propTypes = { PT.oneOf([null]), ]), challengeStatus: PT.string.isRequired, - showFinalResults: PT.bool, auth: PT.shape().isRequired, numWinners: PT.number.isRequired, submissionId: PT.string.isRequired, diff --git a/src/shared/components/challenge-detail/Submissions/SubmissionRow/index.jsx b/src/shared/components/challenge-detail/Submissions/SubmissionRow/index.jsx index 921c73d2b..328310e6e 100644 --- a/src/shared/components/challenge-detail/Submissions/SubmissionRow/index.jsx +++ b/src/shared/components/challenge-detail/Submissions/SubmissionRow/index.jsx @@ -35,6 +35,10 @@ export default function SubmissionRow({ } = latestSubmission; const parseScore = (value) => { + if (_.isNil(value) || value === '' || value === '-') { + return null; + } + const numeric = Number(value); return Number.isFinite(numeric) ? numeric : null; }; @@ -66,9 +70,6 @@ export default function SubmissionRow({ }; const getFinalReviewResult = () => { - if (!showFinalResults) { - return 'N/A'; - } if (_.isNil(finalScore)) { return 'N/A'; } diff --git a/src/shared/services/reviewSummations.js b/src/shared/services/reviewSummations.js index 24308df33..0ab16fa82 100644 --- a/src/shared/services/reviewSummations.js +++ b/src/shared/services/reviewSummations.js @@ -30,8 +30,7 @@ async function fetchReviewSummationsPage({ const totalPages = _.get(payload, 'meta.totalPages') || _.get(payload, 'meta.total_pages'); const reachedEnd = !data.length - || (totalPages && page >= totalPages) - || data.length < DEFAULT_PER_PAGE; + || (totalPages ? page >= totalPages : data.length < DEFAULT_PER_PAGE); if (reachedEnd) { return {