Skip to content

Commit ae2f640

Browse files
authored
Merge pull request #2278 from topcoder-platform/fix/PM-6305-checkpoint-deletion
PM-6305: Gate Design deletion by submission phase
2 parents b375e3d + b2bf908 commit ae2f640

2 files changed

Lines changed: 71 additions & 8 deletions

File tree

src/apps/opportunities/src/pages/ChallengeDetailsPage.flows.spec.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,59 @@ describe('ChallengeDetailsPage member flows', () => {
13541354
.not.toBeDisabled())
13551355
})
13561356

1357+
it.each([
1358+
['Submission', 'contest-submission', 'checkpoint-submission'],
1359+
['Checkpoint Submission', 'checkpoint-submission', 'contest-submission'],
1360+
])(
1361+
'only enables the matching submission type during the Design %s phase',
1362+
(activePhase, enabledSubmissionId, disabledSubmissionId) => {
1363+
mockProfile = { handle: 'coder', userId: 123 }
1364+
mockRegistration = { id: 'resource-id' }
1365+
mockChallenge = {
1366+
...mockChallenge,
1367+
phases: [
1368+
{ isOpen: activePhase === 'Checkpoint Submission', name: 'Checkpoint Submission' },
1369+
{ isOpen: activePhase === 'Submission', name: 'Submission' },
1370+
],
1371+
track: 'Design',
1372+
}
1373+
mockSubmissions = [
1374+
{
1375+
createdAt: '2026-06-03T09:30:00.000Z',
1376+
id: 'contest-submission',
1377+
type: 'CONTEST_SUBMISSION',
1378+
},
1379+
{
1380+
createdAt: '2026-06-02T09:30:00.000Z',
1381+
id: 'checkpoint-submission',
1382+
type: 'CHECKPOINT_SUBMISSION',
1383+
},
1384+
]
1385+
1386+
renderPage()
1387+
fireEvent.click(screen.getByRole('tab', { name: 'My Submissions' }))
1388+
1389+
expect(screen.getByRole('button', { name: `Delete submission ${enabledSubmissionId}` }))
1390+
.toBeEnabled()
1391+
const disabledDeleteButton = screen.getByRole('button', {
1392+
name: `Delete submission ${disabledSubmissionId}`,
1393+
})
1394+
expect(disabledDeleteButton)
1395+
.toBeDisabled()
1396+
expect(disabledDeleteButton)
1397+
.toHaveAttribute('title', 'Submission deletion is closed')
1398+
expect(screen.getByRole('button', { name: `Download submission ${disabledSubmissionId}` }))
1399+
.toBeEnabled()
1400+
expect(screen.getByRole('link', {
1401+
name: `Open submission ${disabledSubmissionId} in Review App`,
1402+
}))
1403+
.toBeInTheDocument()
1404+
fireEvent.click(disabledDeleteButton)
1405+
expect(mockDeleteSubmission)
1406+
.not.toHaveBeenCalled()
1407+
},
1408+
)
1409+
13571410
it('disables Design submission deletion after the submission phase closes', () => {
13581411
mockProfile = { handle: 'coder', userId: 123 }
13591412
mockRegistration = { id: 'resource-id' }

src/apps/opportunities/src/pages/ChallengeDetailsPage.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,21 +114,32 @@ const STALE_REGISTRATION_MESSAGE
114114
const ACTIVE_SUBMISSIONS_REFRESH_INTERVAL_MS = 30_000
115115

116116
/**
117-
* Determines whether a Design submission can still be removed while an
118-
* authored submission phase is open.
117+
* Determines whether a Design submission can still be removed while the
118+
* phase that produced that submission is open.
119119
*
120120
* @param challenge challenge with expanded or compact current-phase data.
121-
* @returns true during Submission or Checkpoint Submission only.
121+
* @param submission authored submission whose phase ownership is checked.
122+
* @returns true only when the submission type matches the active authored phase.
122123
* @throws Does not throw.
123124
*/
124-
export function challengeAllowsDesignSubmissionDeletion(challenge: ChallengeOpportunity): boolean {
125+
export function challengeAllowsDesignSubmissionDeletion(
126+
challenge: ChallengeOpportunity,
127+
submission: ChallengeSubmission,
128+
): boolean {
125129
const openPhaseKeys = [
126130
...(challenge.phases ?? [])
127131
.filter(phase => phase.isOpen === true)
128132
.map(phase => challengeCatalogKey(phase.name)),
129133
...(challenge.currentPhaseNames ?? []).map(challengeCatalogKey),
130134
]
131-
return openPhaseKeys.some(key => key === 'submission' || key === 'checkpointsubmission')
135+
const submissionTypeKey = challengeCatalogKey(submission.type)
136+
const requiredPhaseKey = submissionTypeKey === 'checkpointsubmission'
137+
? 'checkpointsubmission'
138+
: submissionTypeKey === 'contestsubmission'
139+
? 'submission'
140+
: undefined
141+
142+
return !!requiredPhaseKey && openPhaseKeys.includes(requiredPhaseKey)
132143
}
133144

134145
interface SortableColumnHeaderProps {
@@ -1309,9 +1320,6 @@ const SubmissionsTab: FC<SubmissionsTabProps> = props => {
13091320
props.challenge,
13101321
submissions,
13111322
)
1312-
const designDeletionAllowed = isDesign
1313-
&& challengeAllowsDesignSubmissionDeletion(props.challenge)
1314-
13151323
/**
13161324
* Opens an authorized clean-storage download without exposing private URLs in list data.
13171325
*
@@ -1495,6 +1503,8 @@ const SubmissionsTab: FC<SubmissionsTabProps> = props => {
14951503
const statusClass = progress.status
14961504
? styles[`testStatus${progress.status.replace(' ', '')}`]
14971505
: ''
1506+
const designDeletionAllowed = isDesign
1507+
&& challengeAllowsDesignSubmissionDeletion(props.challenge, submission)
14981508
return (
14991509
<tr key={submission.id}>
15001510
<td data-mobile-label='Submission ID'>

0 commit comments

Comments
 (0)