Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ exports[`Matches shallow shapshot 1`] = `
</thead>
<tbody>
<Submission
allowDelete={false}
challenge={
Object {
"id": "test-challenge",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-env jest */
import {
belongsToActiveSubmissionPhase,
getActiveSubmissionCount,
getActiveSubmissionType,
getSubmissionLimit,
Expand Down Expand Up @@ -132,6 +133,63 @@ describe('active submission phase limits', () => {
});
});

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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export default function SubmissionManagement(props) {
onShowDetails,
challengeUrl,
onlineReviewUrl,
submissionPhaseStartDate,
onDownloadArtifacts,
getSubmissionArtifacts,
getSubmissionScores,
Expand Down Expand Up @@ -191,7 +190,6 @@ export default function SubmissionManagement(props) {
showDetails={showDetails}
track={trackName}
status={challenge.status}
submissionPhaseStartDate={submissionPhaseStartDate}
{...componentConfig}
/>
)
Expand Down Expand Up @@ -251,5 +249,4 @@ SubmissionManagement.propTypes = {
submissionLimitCheckPending: PT.bool,
loadingSubmissions: PT.bool,
challengeUrl: PT.string,
submissionPhaseStartDate: PT.string.isRequired,
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -45,7 +45,6 @@ export default function SubmissionsTable(props) {
onDownload,
onShowDetails,
status,
submissionPhaseStartDate,
onDownloadArtifacts,
getSubmissionArtifacts,
getSubmissionScores,
Expand All @@ -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',
Expand All @@ -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 = (
Expand Down Expand Up @@ -233,6 +229,5 @@ SubmissionsTable.propTypes = {
onDownloadArtifacts: PT.func,
getSubmissionArtifacts: PT.func,
status: PT.string.isRequired,
submissionPhaseStartDate: PT.string.isRequired,
getSubmissionScores: PT.func,
};
8 changes: 0 additions & 8 deletions src/shared/containers/SubmissionManagement/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,6 @@ export class SubmissionManagementPageContainer extends React.Component {
challengesUrl,
deleting,
loadingSubmissionsForChallengeId,
submissionPhaseStartDate,
isLoadingChallenge,
onCancelSubmissionDelete,
onShowDetails,
Expand Down Expand Up @@ -562,7 +561,6 @@ export class SubmissionManagementPageContainer extends React.Component {
submissionLimitCheckPending={submissionLimitCheckPending}
showDetails={showDetails}
submissionWorkflowRuns={submissionWorkflowRuns}
submissionPhaseStartDate={submissionPhaseStartDate}
{...smConfig}
/>
)}
Expand Down Expand Up @@ -672,7 +670,6 @@ SubmissionManagementPageContainer.propTypes = {
toBeDeletedId: PT.string,
deletionSucceed: PT.bool,
onSubmissionDeleteConfirmed: PT.func.isRequired,
submissionPhaseStartDate: PT.string.isRequired,
};

function mapStateToProps(state, props) {
Expand All @@ -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,
Expand All @@ -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,
Expand Down
28 changes: 28 additions & 0 deletions src/shared/utils/challenge-detail/submission-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<Object>} phases Challenge phases.
* @return {Boolean} Whether the submission belongs to the currently open submission phase.
* @throws Does not throw; missing submissions or phases resolve to false.
*/
export function belongsToActiveSubmissionPhase(submission, phases) {
const challengePhases = Array.isArray(phases) ? phases : [];
const hasOpenSubmissionPhase = challengePhases.some(phase => (
phase && phase.isOpen && SUBMISSION_PHASE_NAMES.includes(phase.name)
));

if (!submission || !hasOpenSubmissionPhase) {
return false;
}

return normalizeSubmissionType(submission.type || submission.submissionType)
=== getActiveSubmissionType(challengePhases);
}

/**
* Checks whether concept submission limits apply to a V6 submission type.
*
Expand Down
Loading