Skip to content

Commit 5cd686e

Browse files
committed
fix(challenge-listing): sorting by dates & phase displaying
* Add `phaseStartDate` and `phaseEndDate` helper functions at `challenge-detail/helper` to get the correct start/end date of a phase. * `TIME_TO_REGISTER`, `TIME_TO_SUBMIT` and `MOST_RECENT` sorting functions updated to reflect above change. * `ProgressBarTooltip` and `ChallengeCard/Status` updated to display correct phase start/end dates. Addresses topcoder-platform#4715, topcoder-platform#4716
1 parent 03e0bcc commit 5cd686e

File tree

4 files changed

+74
-19
lines changed

4 files changed

+74
-19
lines changed

src/shared/components/challenge-listing/ChallengeCard/Status/index.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { config, Link } from 'topcoder-react-utils';
77
import { TABS as DETAIL_TABS } from 'actions/page/challenge-details';
88
import 'moment-duration-format';
99
import {
10-
getTimeLeft,
10+
getTimeLeft, phaseEndDate,
1111
} from 'utils/challenge-detail/helper';
1212

1313
import ChallengeProgressBar from '../../ChallengeProgressBar';
@@ -271,7 +271,7 @@ export default function ChallengeStatus(props) {
271271
<ChallengeProgressBar
272272
color="green"
273273
value={getPhaseProgress(statusPhase)}
274-
isLate={moment().isAfter(statusPhase.scheduledEndDate)}
274+
isLate={moment().isAfter(phaseEndDate(statusPhase))}
275275
/>
276276
<div styleName="time-left">
277277
{getTimeLeft(statusPhase, 'to go').text}

src/shared/components/challenge-listing/Tooltips/ProgressBarTooltip/index.jsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import React from 'react';
1818
import PT from 'prop-types';
1919
import Tooltip from 'components/Tooltip';
2020
import LoaderIcon from '../../../Loader/Loader';
21+
import { phaseStartDate, phaseEndDate } from '../../../../utils/challenge-detail/helper';
2122
import './style.scss';
2223

2324
const getDate = date => moment(date).format('MMM DD');
@@ -95,31 +96,32 @@ function Tip(props) {
9596
if (!c || _.isEmpty(c)) return <div />;
9697

9798
const allPhases = c.phases || [];
98-
const endPhaseDate = Math.max(...allPhases.map(d => new Date(d.scheduledEndDate)));
99+
const endPhaseDate = Math.max(...allPhases.map(d => phaseEndDate(d)));
99100
const registrationPhase = allPhases.find(phase => phase.name === 'Registration');
100101
const submissionPhase = allPhases.find(phase => phase.name === 'Submission');
102+
const checkpointPhase = allPhases.find(phase => phase.name === 'Checkpoint Submission');
101103

102104
if (registrationPhase) {
103105
steps.push({
104-
date: new Date(registrationPhase.scheduledStartDate),
106+
date: phaseStartDate(registrationPhase),
105107
name: 'Start',
106108
});
107109
}
108-
if (c.checkpointSubmissionEndDate) {
110+
if (checkpointPhase) {
109111
steps.push({
110-
date: new Date(c.checkpointSubmissionEndDate),
112+
date: phaseEndDate(checkpointPhase),
111113
name: 'Checkpoint',
112114
});
113115
}
114116
const iterativeReviewPhase = allPhases.find(phase => phase.isOpen && phase.name === 'Iterative Review');
115117
if (iterativeReviewPhase) {
116118
steps.push({
117-
date: new Date(iterativeReviewPhase.scheduledEndDate),
119+
date: phaseEndDate(iterativeReviewPhase),
118120
name: 'Iterative Review',
119121
});
120122
} else if (submissionPhase) {
121123
steps.push({
122-
date: new Date(submissionPhase.scheduledEndDate),
124+
date: phaseEndDate(submissionPhase),
123125
name: 'Submission',
124126
});
125127
}

src/shared/utils/challenge-detail/helper.jsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,36 @@ export function getChallengeTypeAbbr(track, challengeTypes) {
3232
return null;
3333
}
3434

35+
/**
36+
* Returns phase's end date.
37+
* @param {Object} phase
38+
* @return {Date}
39+
*/
40+
export function phaseEndDate(phase) {
41+
// Case 1: phase is still open. take the `scheduledEndDate`
42+
// Case 2: phase is not open but `scheduledStartDate` is a future date.
43+
// This means phase is not yet started. So take the `scheduledEndDate`
44+
if (phase.isOpen || moment(phase.scheduledStartDate).isAfter()) {
45+
return new Date(phase.scheduledEndDate);
46+
}
47+
// for other cases, take the `actualEndDate` as phase is already closed
48+
return new Date(phase.actualEndDate);
49+
}
50+
51+
/**
52+
* Returns phase's start date.
53+
* @param {Object} phase
54+
* @return {Date}
55+
*/
56+
export function phaseStartDate(phase) {
57+
// Case 1: Phase is not yet started. take the `scheduledStartDate`
58+
if (phase.isOpen !== true && moment(phase.scheduledStartDate).isAfter()) {
59+
return new Date(phase.scheduledStartDate);
60+
}
61+
// For all other cases, take the `actualStartDate` as phase is already started
62+
return new Date(phase.actualStartDate);
63+
}
64+
3565
/**
3666
* Get end date
3767
* @param {Object} challenge challenge info
@@ -42,7 +72,7 @@ export function getEndDate(challenge) {
4272
if (type === 'First2Finish' && challenge.status === 'Completed') {
4373
phases = challenge.phases.filter(p => p.phaseType === 'Iterative Review' && p.phaseStatus === 'Closed');
4474
}
45-
const endPhaseDate = Math.max(...phases.map(d => new Date(d.scheduledEndDate)));
75+
const endPhaseDate = Math.max(...phases.map(d => phaseEndDate(d)));
4676
return moment(endPhaseDate).format('MMM DD');
4777
}
4878

@@ -65,7 +95,7 @@ export function getTimeLeft(
6595
return { late: false, text: FF_TIME_LEFT_MSG };
6696
}
6797

68-
let time = moment(phase.scheduledEndDate).diff();
98+
let time = moment(phaseEndDate(phase)).diff();
6999
const late = time < 0;
70100
if (late) time = -time;
71101

src/shared/utils/challenge-listing/sort.js

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import moment from 'moment';
66
import { find, sumBy } from 'lodash';
7+
import { phaseStartDate, phaseEndDate } from '../challenge-detail/helper';
78

89
export const SORTS = {
910
CURRENT_PHASE: 'current-phase',
@@ -27,12 +28,14 @@ export default {
2728
[SORTS.MOST_RECENT]: {
2829
func: (a, b) => {
2930
const getRegistrationStartDate = (challenge) => {
31+
// extract the registration phase from `challenge.phases`,
32+
// as `challenge.registrationStartDate` returned from API is not reliable
3033
const registrationPhase = find(challenge.phases, p => p.name === 'Registration');
31-
return registrationPhase.actualStartDate || registrationPhase.scheduledStartDate;
34+
return moment(phaseStartDate(registrationPhase));
3235
};
3336
const aRegistrationStartDate = getRegistrationStartDate(a);
3437
const bRegistrationStartDate = getRegistrationStartDate(b);
35-
return moment(bRegistrationStartDate).diff(aRegistrationStartDate);
38+
return bRegistrationStartDate.diff(aRegistrationStartDate);
3639
},
3740
name: 'Most recent',
3841
},
@@ -51,12 +54,20 @@ export default {
5154
[SORTS.TIME_TO_REGISTER]: {
5255
func: (a, b) => {
5356
const getRegistrationEndDate = (challenge) => {
57+
// extract the registration phase from `challenge.phases`,
58+
// as `challenge.registrationEndDate` returned from API is not reliable
5459
const registrationPhase = find(challenge.phases, p => p.name === 'Registration');
55-
return registrationPhase.actualEndDate || registrationPhase.scheduledEndDate;
60+
const submissionPhase = find(challenge.phases, p => p.name === 'Submission');
61+
// case 1: registration phase exists
62+
if (registrationPhase) {
63+
return moment(phaseEndDate(registrationPhase));
64+
}
65+
// case 2: registration phase doesn't exist. Take submission phase instead.
66+
return moment(phaseEndDate(submissionPhase));
5667
};
5768

58-
const aDate = moment(getRegistrationEndDate(a) || a.submissionEndTimestamp);
59-
const bDate = moment(getRegistrationEndDate(b) || b.submissionEndTimestamp);
69+
const aDate = getRegistrationEndDate(a);
70+
const bDate = getRegistrationEndDate(b);
6071

6172
if (aDate.isBefore() && bDate.isAfter()) return 1;
6273
if (aDate.isAfter() && bDate.isBefore()) return -1;
@@ -68,11 +79,23 @@ export default {
6879
},
6980
[SORTS.TIME_TO_SUBMIT]: {
7081
func: (a, b) => {
71-
function nextSubEndDate(o) {
72-
if (o.checkpointSubmissionEndDate && moment(o.checkpointSubmissionEndDate).isAfter()) {
73-
return moment(o.checkpointSubmissionEndDate);
82+
function nextSubEndDate(challenge) {
83+
// extract the submission and checkpoint (if any) phases from `challenge.phases`,
84+
// as `challenge.submissionEndDate` returned from API is not reliable
85+
const checkpointPhase = find(challenge.phases, p => p.name === 'Checkpoint Submission');
86+
const submissionPhase = find(challenge.phases, p => p.name === 'Submission');
87+
// Case 1: challenge has checkpoint submission phase
88+
if (!!checkpointPhase === true) {
89+
// Case 1.1: checkpoint submission phase is still open.
90+
// then take the `scheduledEndDate` of this phase.
91+
// Case 1.2: checkpoint submission phase is closed
92+
// but its `scheduledStartDate` is a future date.
93+
// This means this phase is not yet started. Take the `scheduledEndDate` of this phase.
94+
if (checkpointPhase.isOpen || moment(checkpointPhase.scheduledStartDate).isAfter()) {
95+
return moment(checkpointPhase.scheduledEndDate);
96+
}
7497
}
75-
return moment(o.submissionEndTimestamp);
98+
return moment(phaseEndDate(submissionPhase));
7699
}
77100

78101
const aDate = nextSubEndDate(a);

0 commit comments

Comments
 (0)