diff --git a/__tests__/shared/components/SubmissionManagement/__snapshots__/SubmissionsTable.jsx.snap b/__tests__/shared/components/SubmissionManagement/__snapshots__/SubmissionsTable.jsx.snap
index d8f5971b3..46edffe3d 100644
--- a/__tests__/shared/components/SubmissionManagement/__snapshots__/SubmissionsTable.jsx.snap
+++ b/__tests__/shared/components/SubmissionManagement/__snapshots__/SubmissionsTable.jsx.snap
@@ -25,6 +25,7 @@ exports[`Matches shallow shapshot 1`] = `
{
});
});
+describe('belongsToActiveSubmissionPhase', () => {
+ test('allows deleting a checkpoint submission while the checkpoint phase is open', () => {
+ const phases = [
+ { isOpen: true, name: 'Checkpoint Submission' },
+ { isOpen: false, name: 'Submission' },
+ ];
+
+ expect(belongsToActiveSubmissionPhase(
+ { id: 'checkpoint-submission', type: 'CHECKPOINT_SUBMISSION' },
+ phases,
+ )).toBe(true);
+ expect(belongsToActiveSubmissionPhase(
+ { id: 'legacy-checkpoint', submissionType: 'checkpoint' },
+ phases,
+ )).toBe(true);
+ });
+
+ test('blocks deleting a checkpoint submission once the submission phase opens', () => {
+ const phases = [
+ { isOpen: false, name: 'Checkpoint Submission' },
+ { isOpen: true, name: 'Submission' },
+ ];
+
+ expect(belongsToActiveSubmissionPhase(
+ { id: 'checkpoint-submission', type: 'CHECKPOINT_SUBMISSION' },
+ phases,
+ )).toBe(false);
+ expect(belongsToActiveSubmissionPhase(
+ { id: 'contest-submission', type: 'CONTEST_SUBMISSION' },
+ phases,
+ )).toBe(true);
+ });
+
+ test('blocks deleting anything once every submission phase is closed', () => {
+ const phases = [
+ { isOpen: false, name: 'Checkpoint Submission' },
+ { isOpen: false, name: 'Submission' },
+ { isOpen: true, name: 'Checkpoint Review' },
+ { isOpen: true, name: 'Review' },
+ ];
+
+ expect(belongsToActiveSubmissionPhase(
+ { id: 'checkpoint-submission', type: 'CHECKPOINT_SUBMISSION' },
+ phases,
+ )).toBe(false);
+ expect(belongsToActiveSubmissionPhase(
+ { id: 'contest-submission', type: 'CONTEST_SUBMISSION' },
+ phases,
+ )).toBe(false);
+ });
+
+ test('resolves to false for missing submissions or phases', () => {
+ expect(belongsToActiveSubmissionPhase(null, [{ isOpen: true, name: 'Submission' }])).toBe(false);
+ expect(belongsToActiveSubmissionPhase({ type: 'CONTEST_SUBMISSION' }, undefined)).toBe(false);
+ });
+});
+
describe('getSubmissionLimitReachedMessage', () => {
test('uses the requested singular limit message', () => {
expect(getSubmissionLimitReachedMessage(1)).toBe(
diff --git a/src/shared/components/SubmissionManagement/SubmissionManagement/index.jsx b/src/shared/components/SubmissionManagement/SubmissionManagement/index.jsx
index 2eaa362d7..faf6902c3 100644
--- a/src/shared/components/SubmissionManagement/SubmissionManagement/index.jsx
+++ b/src/shared/components/SubmissionManagement/SubmissionManagement/index.jsx
@@ -40,7 +40,6 @@ export default function SubmissionManagement(props) {
onShowDetails,
challengeUrl,
onlineReviewUrl,
- submissionPhaseStartDate,
onDownloadArtifacts,
getSubmissionArtifacts,
getSubmissionScores,
@@ -191,7 +190,6 @@ export default function SubmissionManagement(props) {
showDetails={showDetails}
track={trackName}
status={challenge.status}
- submissionPhaseStartDate={submissionPhaseStartDate}
{...componentConfig}
/>
)
@@ -251,5 +249,4 @@ SubmissionManagement.propTypes = {
submissionLimitCheckPending: PT.bool,
loadingSubmissions: PT.bool,
challengeUrl: PT.string,
- submissionPhaseStartDate: PT.string.isRequired,
};
diff --git a/src/shared/components/SubmissionManagement/SubmissionsTable/index.jsx b/src/shared/components/SubmissionManagement/SubmissionsTable/index.jsx
index a9ca5652f..74ccbef21 100644
--- a/src/shared/components/SubmissionManagement/SubmissionsTable/index.jsx
+++ b/src/shared/components/SubmissionManagement/SubmissionsTable/index.jsx
@@ -18,8 +18,8 @@ import _ from 'lodash';
import React, { useCallback, useState } from 'react';
import PT from 'prop-types';
import shortid from 'shortid';
-import moment from 'moment';
import { COMPETITION_TRACKS } from 'utils/tc';
+import { belongsToActiveSubmissionPhase } from 'utils/challenge-detail/submission-limit';
import ScreeningDetails from '../ScreeningDetails';
import DownloadArtifactsModal from '../DownloadArtifactsModal';
import Submission from '../Submission';
@@ -45,7 +45,6 @@ export default function SubmissionsTable(props) {
onDownload,
onShowDetails,
status,
- submissionPhaseStartDate,
onDownloadArtifacts,
getSubmissionArtifacts,
getSubmissionScores,
@@ -72,9 +71,6 @@ export default function SubmissionsTable(props) {
));
} else {
submissionObjects.forEach((subObject) => {
- // submissionPhaseStartDate will be the start date of
- // the current submission/checkpoint or empty string if any other phase
-
const TERMINAL_STATUSES = [
'COMPLETED',
'FAILURE',
@@ -93,8 +89,8 @@ export default function SubmissionsTable(props) {
? true
: workflowRunsForSubmission.every(run => TERMINAL_STATUSES.includes(run.status));
- const allowDelete = submissionPhaseStartDate
- && moment(subObject.submissionDate).isAfter(submissionPhaseStartDate);
+ // Submissions can only be removed while the phase that created them is still open.
+ const allowDelete = belongsToActiveSubmissionPhase(subObject, challenge.phases);
const submission = (
@@ -233,6 +229,5 @@ SubmissionsTable.propTypes = {
onDownloadArtifacts: PT.func,
getSubmissionArtifacts: PT.func,
status: PT.string.isRequired,
- submissionPhaseStartDate: PT.string.isRequired,
getSubmissionScores: PT.func,
};
diff --git a/src/shared/containers/SubmissionManagement/index.jsx b/src/shared/containers/SubmissionManagement/index.jsx
index 13f19e5b6..e9d0306ac 100644
--- a/src/shared/containers/SubmissionManagement/index.jsx
+++ b/src/shared/containers/SubmissionManagement/index.jsx
@@ -483,7 +483,6 @@ export class SubmissionManagementPageContainer extends React.Component {
challengesUrl,
deleting,
loadingSubmissionsForChallengeId,
- submissionPhaseStartDate,
isLoadingChallenge,
onCancelSubmissionDelete,
onShowDetails,
@@ -562,7 +561,6 @@ export class SubmissionManagementPageContainer extends React.Component {
submissionLimitCheckPending={submissionLimitCheckPending}
showDetails={showDetails}
submissionWorkflowRuns={submissionWorkflowRuns}
- submissionPhaseStartDate={submissionPhaseStartDate}
{...smConfig}
/>
)}
@@ -672,7 +670,6 @@ SubmissionManagementPageContainer.propTypes = {
toBeDeletedId: PT.string,
deletionSucceed: PT.bool,
onSubmissionDeleteConfirmed: PT.func.isRequired,
- submissionPhaseStartDate: PT.string.isRequired,
};
function mapStateToProps(state, props) {
@@ -682,9 +679,6 @@ function mapStateToProps(state, props) {
mySubmissions = challengeId === mySubmissions.challengeId
? mySubmissions.v2 : null;
- const allPhases = state.challenge.details.phases || [];
- const submissionPhase = allPhases.find(phase => ['Submission', 'Checkpoint Submission'].includes(phase.name) && phase.isOpen) || {};
-
return {
challengeId: String(challengeId),
challenge: state.challenge.details,
@@ -698,8 +692,6 @@ function mapStateToProps(state, props) {
state.challenge.loadingSubmissionsForChallengeId || '',
mySubmissions,
- submissionPhaseStartDate: submissionPhase.actualStartDate || submissionPhase.scheduledStartDate || '',
-
showDetails: state.page.submissionManagement.showDetails,
submissionWorkflowRuns: state.page.submissionManagement.submissionWorkflowRuns,
diff --git a/src/shared/utils/challenge-detail/submission-limit.js b/src/shared/utils/challenge-detail/submission-limit.js
index 061a9332b..7a008d5fa 100644
--- a/src/shared/utils/challenge-detail/submission-limit.js
+++ b/src/shared/utils/challenge-detail/submission-limit.js
@@ -2,6 +2,7 @@ const SUBMISSION_LIMIT_METADATA_NAME = 'submissionLimit';
const CHECKPOINT_SUBMISSION_TYPE = 'CHECKPOINT_SUBMISSION';
const CONTEST_SUBMISSION_TYPE = 'CONTEST_SUBMISSION';
const FINAL_FIX_SUBMISSION_TYPE = 'STUDIO_FINAL_FIX_SUBMISSION';
+const SUBMISSION_PHASE_NAMES = ['Checkpoint Submission', 'Submission'];
/**
* Converts a metadata value to a positive integer submission limit.
@@ -164,6 +165,33 @@ function normalizeSubmissionType(value) {
return normalizedValue;
}
+/**
+ * Checks whether a submission was created by the currently open submission phase.
+ *
+ * Submitters may only remove a submission while the phase that produced it is still open. A
+ * checkpoint submission therefore stops being deletable as soon as the Checkpoint Submission
+ * phase ends, even though the Submission phase opens right after it, and nothing is deletable
+ * once every submission phase is closed (screening, review, appeals, ...).
+ *
+ * @param {Object} submission Member submission, using the V6 `type` or legacy `submissionType`.
+ * @param {Array