diff --git a/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.module.scss b/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.module.scss
index b0e8634cf..bd8bb596f 100644
--- a/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.module.scss
+++ b/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.module.scss
@@ -150,6 +150,10 @@
color: #ea1900;
}
+.testStatusCancelled {
+ color: #767676;
+}
+
.level-1 {
color: #555 !important;
}
diff --git a/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.spec.tsx b/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.spec.tsx
index ce0827d80..7327b0179 100644
--- a/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.spec.tsx
+++ b/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.spec.tsx
@@ -5,6 +5,7 @@ import { SubmissionsTable } from './SubmissionsTable'
jest.mock('~/libs/ui', () => ({
IconOutline: {
+ BanIcon: (): JSX.Element => ,
ClockIcon: (): JSX.Element => ,
XCircleIcon: (): JSX.Element => ,
},
@@ -55,7 +56,7 @@ jest.mock('../../utils', () => ({
metadata?: {
testProcess?: 'example' | 'provisional' | 'system'
testProgress?: number
- testStatus?: 'FAILED' | 'IN PROGRESS' | 'SUCCESS'
+ testStatus?: 'CANCELLED' | 'FAILED' | 'IN PROGRESS' | 'SUCCESS'
testType?: 'example' | 'provisional' | 'system'
}
}>
@@ -337,6 +338,45 @@ describe('SubmissionsTable', () => {
.toBeTruthy()
})
+ it('renders a cancelled test status without a score for a superseded marathon run', () => {
+ render(
+ ,
+ )
+
+ expect(screen.getByRole('img', { name: 'Test status: CANCELLED' }))
+ .toBeTruthy()
+ expect(screen.queryByText('-1.00'))
+ .toBeNull()
+ })
+
it('renders marathon scores from provisional and system summations only', () => {
render(
+
+
+ )
+ }
+
return undefined
}
@@ -384,17 +408,17 @@ export const SubmissionsTable: FC = (
const emptyScoreValue = props.showMarathonMatchTestProgress
? '-'
: 'N/A'
- const isInitialScoreInProgress = testProgress?.status === 'IN PROGRESS'
+ const isInitialScoreUnscored = isUnscoredTestStatus(testProgress?.status)
&& (
- testProgress.process === 'example'
- || testProgress.process === 'provisional'
+ testProgress?.process === 'example'
+ || testProgress?.process === 'provisional'
)
- const isFinalScoreInProgress = testProgress?.status === 'IN PROGRESS'
- && testProgress.process === 'system'
- const initialScore = isInitialScoreInProgress
+ const isFinalScoreUnscored = isUnscoredTestStatus(testProgress?.status)
+ && testProgress?.process === 'system'
+ const initialScore = isInitialScoreUnscored
? 'N/A'
: formatScore(initialScoreValue, emptyScoreValue)
- const finalScore = isFinalScoreInProgress
+ const finalScore = isFinalScoreUnscored
? 'N/A'
: formatScore(finalScoreValue, emptyScoreValue)
const reviewTab = submission.type === 'CHECKPOINT_SUBMISSION'
diff --git a/src/apps/work/src/lib/models/Submission.model.ts b/src/apps/work/src/lib/models/Submission.model.ts
index c536320dc..c09bc8464 100644
--- a/src/apps/work/src/lib/models/Submission.model.ts
+++ b/src/apps/work/src/lib/models/Submission.model.ts
@@ -1,6 +1,6 @@
export type SubmissionStatus = 'active' | 'completed' | 'deleted' | 'failed' | 'pending' | string
export type MarathonMatchTestProcess = 'provisional' | 'system' | string
-export type MarathonMatchTestStatus = 'FAILED' | 'IN PROGRESS' | 'SUCCESS' | string
+export type MarathonMatchTestStatus = 'CANCELLED' | 'FAILED' | 'IN PROGRESS' | 'SUCCESS' | string
export interface SubmissionReview {
createdAt?: string
diff --git a/src/apps/work/src/lib/utils/challenge.utils.spec.ts b/src/apps/work/src/lib/utils/challenge.utils.spec.ts
index b946bb417..9b98bbfc0 100644
--- a/src/apps/work/src/lib/utils/challenge.utils.spec.ts
+++ b/src/apps/work/src/lib/utils/challenge.utils.spec.ts
@@ -116,6 +116,26 @@ describe('challenge utils', () => {
})
})
+ it('surfaces cancelled provisional runs superseded by a newer submission', () => {
+ expect(getSubmissionTestProgress({
+ reviewSummation: [
+ {
+ isProvisional: true,
+ metadata: {
+ testProcess: 'provisional',
+ testProgress: 1,
+ testStatus: 'CANCELLED',
+ },
+ },
+ ],
+ }))
+ .toEqual({
+ process: 'provisional',
+ progressPercent: '100%',
+ status: 'CANCELLED',
+ })
+ })
+
it('prefers a completed provisional process over a later example process', () => {
expect(getSubmissionTestProgress({
reviewSummation: [
diff --git a/src/apps/work/src/lib/utils/challenge.utils.ts b/src/apps/work/src/lib/utils/challenge.utils.ts
index 2f2758701..98474491a 100644
--- a/src/apps/work/src/lib/utils/challenge.utils.ts
+++ b/src/apps/work/src/lib/utils/challenge.utils.ts
@@ -15,6 +15,7 @@ interface SubmissionScore {
}
type MarathonMatchScoreProcess = 'example' | 'provisional' | 'system'
+type MarathonMatchTestStatusDisplay = 'CANCELLED' | 'FAILED' | 'IN PROGRESS' | 'SUCCESS'
interface ScoredSubmissionLike {
review?: Array<{
@@ -34,7 +35,7 @@ interface ScoredSubmissionLike {
export interface SubmissionTestProgressDisplay {
process?: MarathonMatchScoreProcess
progressPercent?: string
- status?: 'FAILED' | 'IN PROGRESS' | 'SUCCESS'
+ status?: MarathonMatchTestStatusDisplay
}
interface SubmissionTestProgressCandidate extends SubmissionTestProgressDisplay {
@@ -270,14 +271,21 @@ function normalizeTestProcess(value: unknown): MarathonMatchScoreProcess | undef
* @param value Metadata status value from Review API.
* @returns Supported UI status or `undefined` when the status is absent/unknown.
* Used by `getSubmissionTestProgress` before choosing the current summation.
+ * `CANCELLED` marks a scorer that was stopped because the member submitted a
+ * newer solution, so the run is terminal without producing a score.
*/
-function normalizeTestStatus(value: unknown): 'FAILED' | 'IN PROGRESS' | 'SUCCESS' | undefined {
+function normalizeTestStatus(value: unknown): MarathonMatchTestStatusDisplay | undefined {
const normalized = typeof value === 'string'
? value.trim()
.toUpperCase()
: ''
- if (normalized === 'FAILED' || normalized === 'IN PROGRESS' || normalized === 'SUCCESS') {
+ if (
+ normalized === 'CANCELLED'
+ || normalized === 'FAILED'
+ || normalized === 'IN PROGRESS'
+ || normalized === 'SUCCESS'
+ ) {
return normalized
}
@@ -355,7 +363,7 @@ function toSubmissionTestProgressCandidate(
const progress = normalizeTestProgress(entry.metadata?.testProgress)
let statusPriority = 0
- if (status === 'FAILED') {
+ if (status === 'FAILED' || status === 'CANCELLED') {
statusPriority = 2
} else if (status === 'SUCCESS') {
statusPriority = 1