diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss
index ec7051428..5b04888b9 100644
--- a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss
+++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss
@@ -34,7 +34,7 @@
.filterWrap {
min-width: 280px;
max-width: 360px;
-
+
@include ltemd {
max-width: unset;
min-width: unset;
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 fbb797c1b..c4bfce2e9 100644
--- a/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.module.scss
+++ b/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.module.scss
@@ -118,6 +118,38 @@
}
}
+.testStatusCell {
+ text-align: center !important;
+}
+
+.testStatusIcon {
+ align-items: center;
+ display: inline-flex;
+ height: 20px;
+ justify-content: center;
+ vertical-align: middle;
+ width: 20px;
+
+ svg {
+ display: block;
+ fill: currentcolor;
+ height: 18px;
+ width: 18px;
+ }
+}
+
+.testStatusInProgress {
+ color: #d97706;
+}
+
+.testStatusSuccess {
+ color: #137d60;
+}
+
+.testStatusFailed {
+ color: #ea1900;
+}
+
.level-1 {
color: #555 !important;
}
@@ -144,6 +176,6 @@
}
.table {
- min-width: 1080px;
+ min-width: 1180px;
}
}
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 c264c8dbb..993fca3b8 100644
--- a/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.spec.tsx
+++ b/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.spec.tsx
@@ -4,6 +4,13 @@ import { render, screen } from '@testing-library/react'
import { SubmissionsTable } from './SubmissionsTable'
jest.mock('~/libs/ui', () => ({
+ IconOutline: {
+ ClockIcon: (): JSX.Element => ,
+ XCircleIcon: (): JSX.Element => ,
+ },
+ IconSolid: {
+ CheckCircleIcon: (): JSX.Element => ,
+ },
LoadingSpinner: () =>
Loading
,
}), {
virtual: true,
@@ -21,6 +28,28 @@ jest.mock('../../utils', () => ({
getSubmissionInitialScore: (submission: { review?: Array<{ initialScore?: number }> }) => (
submission.review?.[0]?.initialScore ?? 0
),
+ getSubmissionTestProgress: (
+ submission: {
+ reviewSummation?: Array<{
+ metadata?: {
+ testProcess?: 'provisional' | 'system'
+ testProgress?: number
+ testStatus?: 'FAILED' | 'IN PROGRESS' | 'SUCCESS'
+ }
+ }>
+ },
+ ) => {
+ const metadata = submission.reviewSummation?.[0]?.metadata
+ const progress = metadata?.testProgress
+
+ return {
+ process: metadata?.testProcess,
+ progressPercent: typeof progress === 'number'
+ ? `${Math.round(progress * 100)}%`
+ : undefined,
+ status: metadata?.testStatus,
+ }
+ },
}))
jest.mock('../../assets/icons/IconDownloadArtifacts.svg', () => ({
ReactComponent: () => ,
@@ -142,4 +171,85 @@ describe('SubmissionsTable', () => {
expect(screen.getByRole('button', { name: 'Download submission artifacts' }))
.toBeTruthy()
})
+
+ it('renders marathon test progress columns when enabled', () => {
+ render(
+ ,
+ )
+
+ expect(screen.getByText('Current tests process'))
+ .toBeTruthy()
+ expect(screen.getByText('Test status'))
+ .toBeTruthy()
+ expect(screen.getByText('Test progress'))
+ .toBeTruthy()
+ expect(screen.getByText('75%'))
+ .toBeTruthy()
+ expect(screen.getByText('100%'))
+ .toBeTruthy()
+ expect(screen.getByText('20%'))
+ .toBeTruthy()
+ expect(screen.getByRole('img', { name: 'Test status: IN PROGRESS' }))
+ .toBeTruthy()
+ expect(screen.getByRole('img', { name: 'Test status: SUCCESS' }))
+ .toBeTruthy()
+ expect(screen.getByRole('img', { name: 'Test status: FAILED' }))
+ .toBeTruthy()
+ })
})
diff --git a/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.tsx b/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.tsx
index 2d89b244f..f5f05757a 100644
--- a/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.tsx
+++ b/src/apps/work/src/lib/components/SubmissionsTable/SubmissionsTable.tsx
@@ -1,10 +1,15 @@
import {
FC,
MouseEvent,
+ ReactElement,
} from 'react'
import classNames from 'classnames'
-import { LoadingSpinner } from '~/libs/ui'
+import {
+ IconOutline,
+ IconSolid,
+ LoadingSpinner,
+} from '~/libs/ui'
import { COMMUNITY_APP_URL, REVIEW_APP_URL } from '../../constants'
import { ReactComponent as IconDownloadArtifacts } from '../../assets/icons/IconDownloadArtifacts.svg'
@@ -15,6 +20,7 @@ import {
getRatingLevel,
getSubmissionFinalScore,
getSubmissionInitialScore,
+ getSubmissionTestProgress,
} from '../../utils'
import styles from './SubmissionsTable.module.scss'
@@ -45,6 +51,7 @@ interface SubmissionsTableProps {
onSort: (fieldName: SubmissionSortBy) => void
sortBy: SubmissionSortBy
sortOrder: SortOrder
+ showMarathonMatchTestProgress?: boolean
submissionDownloadLoading?: Record
submissions: Submission[]
}
@@ -70,6 +77,21 @@ const BASE_COLUMNS: ColumnConfig[] = [
label: 'Initial / Final Score',
sortable: true,
},
+]
+
+const MARATHON_MATCH_TEST_COLUMNS: ColumnConfig[] = [
+ {
+ label: 'Current tests process',
+ },
+ {
+ label: 'Test status',
+ },
+ {
+ label: 'Test progress',
+ },
+]
+
+const TRAILING_COLUMNS: ColumnConfig[] = [
{
fieldName: 'submissionId',
label: 'Submission ID (UUID)',
@@ -80,6 +102,25 @@ const BASE_COLUMNS: ColumnConfig[] = [
},
]
+/**
+ * Builds the table columns for standard and marathon submission rows.
+ * @param showMarathonMatchTestProgress Whether marathon test-progress metadata should be displayed.
+ * @returns Column config used by the table header and empty/loading colspans.
+ * Used by `SubmissionsTable` to insert marathon-only progress columns before actions.
+ */
+function getColumns(showMarathonMatchTestProgress: boolean): ColumnConfig[] {
+ return showMarathonMatchTestProgress
+ ? [
+ ...BASE_COLUMNS,
+ ...MARATHON_MATCH_TEST_COLUMNS,
+ ...TRAILING_COLUMNS,
+ ]
+ : [
+ ...BASE_COLUMNS,
+ ...TRAILING_COLUMNS,
+ ]
+}
+
function getCreatedAt(submission: Submission): string {
return submission.createdAt
|| submission.created
@@ -139,10 +180,59 @@ function getEmailDisplay(
: '-'
}
+/**
+ * Renders the marathon test status icon for a submission row.
+ * @param status Normalized test status from review summation metadata.
+ * @returns Status icon element or `undefined` when no status is available.
+ * Used by `SubmissionsTable` to keep empty status cells blank.
+ */
+function renderTestStatusIcon(status: string | undefined): ReactElement | undefined {
+ if (status === 'IN PROGRESS') {
+ return (
+
+
+
+ )
+ }
+
+ if (status === 'SUCCESS') {
+ return (
+
+
+
+ )
+ }
+
+ if (status === 'FAILED') {
+ return (
+
+
+
+ )
+ }
+
+ return undefined
+}
+
export const SubmissionsTable: FC = (
props: SubmissionsTableProps,
) => {
- const columns = BASE_COLUMNS
+ const columns = getColumns(!!props.showMarathonMatchTestProgress)
function handleSortButtonClick(event: MouseEvent): void {
const sortBy = event.currentTarget.dataset.fieldName as SubmissionSortBy | undefined
@@ -225,6 +315,9 @@ export const SubmissionsTable: FC = (
const submissionDate = formatDateTime(getCreatedAt(submission))
const initialScore = formatScore(getSubmissionInitialScore(submission))
const finalScore = formatScore(getSubmissionFinalScore(submission))
+ const testProgress = props.showMarathonMatchTestProgress
+ ? getSubmissionTestProgress(submission)
+ : undefined
const reviewTab = submission.type === 'CHECKPOINT_SUBMISSION'
? 'checkpoint-submission'
: 'submission'
@@ -276,6 +369,24 @@ export const SubmissionsTable: FC = (
+ {props.showMarathonMatchTestProgress
+ ? (
+ <>
+
+ {testProgress?.process || ''}
+ |
+
+
+ {renderTestStatusIcon(testProgress?.status)}
+ |
+
+
+ {testProgress?.progressPercent || ''}
+ |
+ >
+ )
+ : undefined}
+
{submission.id}
|
diff --git a/src/apps/work/src/lib/models/Submission.model.ts b/src/apps/work/src/lib/models/Submission.model.ts
index 835a0618e..8efed38cc 100644
--- a/src/apps/work/src/lib/models/Submission.model.ts
+++ b/src/apps/work/src/lib/models/Submission.model.ts
@@ -1,4 +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 interface SubmissionReview {
createdAt?: string
@@ -14,6 +16,29 @@ export interface SubmissionReview {
typeId?: string
}
+export interface ReviewSummationTestProgressDetails {
+ completedTests?: number
+ failedTests?: number
+ message?: string
+ progress?: number
+ reviewId?: string
+ status?: MarathonMatchTestStatus
+ testProcess?: MarathonMatchTestProcess
+ totalTests?: number
+ updatedAt?: string
+ [key: string]: unknown
+}
+
+export interface ReviewSummationMetadata {
+ reviewTypeId?: string
+ testProcess?: MarathonMatchTestProcess
+ testProgress?: number
+ testProgressDetails?: ReviewSummationTestProgressDetails
+ testStatus?: MarathonMatchTestStatus
+ testType?: MarathonMatchTestProcess
+ [key: string]: unknown
+}
+
export interface ReviewSummation {
aggregateScore?: number
createdAt?: string
@@ -22,7 +47,9 @@ export interface ReviewSummation {
isPassing?: boolean
isProvisional?: boolean
memberId?: string
+ metadata?: ReviewSummationMetadata
submissionId?: string
+ updatedAt?: string
}
export interface Submission {
diff --git a/src/apps/work/src/lib/services/submissions.service.ts b/src/apps/work/src/lib/services/submissions.service.ts
index a9c93af4c..408be9e9f 100644
--- a/src/apps/work/src/lib/services/submissions.service.ts
+++ b/src/apps/work/src/lib/services/submissions.service.ts
@@ -6,6 +6,8 @@ import {
import { SUBMISSIONS_API_URL } from '../constants'
import {
ReviewSummation,
+ ReviewSummationMetadata,
+ ReviewSummationTestProgressDetails,
Submission,
SubmissionReview,
} from '../models'
@@ -148,6 +150,61 @@ function normalizeReview(review: unknown): SubmissionReview | undefined {
}
}
+/**
+ * Normalizes marathon progress detail metadata from Review API summations.
+ * @param details Raw `metadata.testProgressDetails` value from a review summation.
+ * @returns Typed progress detail metadata or `undefined` when absent.
+ * Used by `normalizeReviewSummationMetadata` before data reaches the submissions table.
+ */
+function normalizeReviewSummationTestProgressDetails(
+ details: unknown,
+): ReviewSummationTestProgressDetails | undefined {
+ if (typeof details !== 'object' || !details || Array.isArray(details)) {
+ return undefined
+ }
+
+ const typedDetails = details as UnknownRecord
+
+ return {
+ ...typedDetails,
+ completedTests: toOptionalNumber(typedDetails.completedTests),
+ failedTests: toOptionalNumber(typedDetails.failedTests),
+ message: toOptionalString(typedDetails.message),
+ progress: toOptionalNumber(typedDetails.progress),
+ reviewId: toOptionalString(typedDetails.reviewId),
+ status: toOptionalString(typedDetails.status),
+ testProcess: toOptionalString(typedDetails.testProcess),
+ totalTests: toOptionalNumber(typedDetails.totalTests),
+ updatedAt: toOptionalString(typedDetails.updatedAt),
+ }
+}
+
+/**
+ * Normalizes Review API summation metadata while preserving unknown custom fields.
+ * @param metadata Raw `metadata` object returned on a review summation.
+ * @returns Typed metadata with marathon test process/status/progress fields normalized.
+ * Used by `normalizeReviewSummation` so UI code can render marathon progress columns.
+ */
+function normalizeReviewSummationMetadata(metadata: unknown): ReviewSummationMetadata | undefined {
+ if (typeof metadata !== 'object' || !metadata || Array.isArray(metadata)) {
+ return undefined
+ }
+
+ const typedMetadata = metadata as UnknownRecord
+
+ return {
+ ...typedMetadata,
+ reviewTypeId: toOptionalString(typedMetadata.reviewTypeId),
+ testProcess: toOptionalString(typedMetadata.testProcess),
+ testProgress: toOptionalNumber(typedMetadata.testProgress),
+ testProgressDetails: normalizeReviewSummationTestProgressDetails(
+ typedMetadata.testProgressDetails,
+ ),
+ testStatus: toOptionalString(typedMetadata.testStatus),
+ testType: toOptionalString(typedMetadata.testType),
+ }
+}
+
function normalizeReviewSummation(reviewSummation: unknown): ReviewSummation | undefined {
if (typeof reviewSummation !== 'object' || !reviewSummation) {
return undefined
@@ -163,7 +220,9 @@ function normalizeReviewSummation(reviewSummation: unknown): ReviewSummation | u
isPassing: toOptionalBoolean(typedReviewSummation.isPassing),
isProvisional: toOptionalBoolean(typedReviewSummation.isProvisional),
memberId: toOptionalString(typedReviewSummation.memberId),
+ metadata: normalizeReviewSummationMetadata(typedReviewSummation.metadata),
submissionId: toOptionalString(typedReviewSummation.submissionId),
+ updatedAt: toOptionalString(typedReviewSummation.updatedAt),
}
}
diff --git a/src/apps/work/src/lib/utils/challenge.utils.ts b/src/apps/work/src/lib/utils/challenge.utils.ts
index 544ad95ad..11b79d221 100644
--- a/src/apps/work/src/lib/utils/challenge.utils.ts
+++ b/src/apps/work/src/lib/utils/challenge.utils.ts
@@ -24,6 +24,23 @@ interface ScoredSubmissionLike {
submissions?: SubmissionScore[]
}
+/**
+ * Display metadata for marathon test progress columns.
+ */
+export interface SubmissionTestProgressDisplay {
+ process?: 'provisional' | 'system'
+ progressPercent?: string
+ status?: 'FAILED' | 'IN PROGRESS' | 'SUCCESS'
+}
+
+interface SubmissionTestProgressCandidate extends SubmissionTestProgressDisplay {
+ inProgressPriority: number
+ processPriority: number
+ progress?: number
+ statusPriority: number
+ updatedAt: number
+}
+
function getPhaseStartDate(phase: ChallengePhase): string {
const phaseStartDate = phase.actualStartDate || phase.scheduledStartDate
@@ -122,6 +139,197 @@ function getChallengeTypeName(type: string | ChallengeTypeRef | undefined): stri
return type.name
}
+/**
+ * Normalizes review summation metadata test process aliases for marathon display.
+ * @param value Metadata process or legacy test type value from Review API.
+ * @returns `provisional` or `system` when the value identifies a tracked process.
+ * Used by `getSubmissionTestProgress` to avoid showing example-test metadata.
+ */
+function normalizeTestProcess(value: unknown): 'provisional' | 'system' | undefined {
+ const normalized = typeof value === 'string'
+ ? value.trim()
+ .toLowerCase()
+ : ''
+
+ if (normalized === 'system' || normalized === 'final') {
+ return 'system'
+ }
+
+ if (normalized === 'provisional') {
+ return 'provisional'
+ }
+
+ return undefined
+}
+
+/**
+ * Normalizes review summation metadata test status for marathon display.
+ * @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.
+ */
+function normalizeTestStatus(value: unknown): 'FAILED' | 'IN PROGRESS' | 'SUCCESS' | undefined {
+ const normalized = typeof value === 'string'
+ ? value.trim()
+ .toUpperCase()
+ : ''
+
+ if (normalized === 'FAILED' || normalized === 'IN PROGRESS' || normalized === 'SUCCESS') {
+ return normalized
+ }
+
+ return undefined
+}
+
+/**
+ * Normalizes review summation metadata progress into the supported 0-to-1 range.
+ * @param value Metadata progress value from Review API.
+ * @returns Clamped numeric progress or `undefined` when no finite value exists.
+ * Used by `getSubmissionTestProgress` to format percent text.
+ */
+function normalizeTestProgress(value: unknown): number | undefined {
+ const progress = typeof value === 'string'
+ ? Number(value)
+ : value
+
+ if (typeof progress !== 'number' || !Number.isFinite(progress)) {
+ return undefined
+ }
+
+ return Math.min(Math.max(progress, 0), 1)
+}
+
+/**
+ * Resolves the best timestamp available for ordering test progress summations.
+ * @param entry Review summation containing marathon metadata.
+ * @returns Epoch milliseconds or 0 when no parseable timestamp exists.
+ * Used by `getSubmissionTestProgress` to choose the latest non-running process.
+ */
+function getTestProgressUpdatedAt(entry: ReviewSummation): number {
+ const updatedAt = entry.metadata?.testProgressDetails?.updatedAt
+ || entry.updatedAt
+ || entry.createdAt
+ || ''
+ const parsedTimestamp = Date.parse(updatedAt)
+
+ return Number.isFinite(parsedTimestamp)
+ ? parsedTimestamp
+ : 0
+}
+
+/**
+ * Builds a sortable marathon test-progress candidate from review summation metadata.
+ * @param entry Review summation returned with optional metadata from Review API.
+ * @returns Candidate display data or `undefined` when no marathon progress metadata exists.
+ * Used by `getSubmissionTestProgress` to select the current process for a submission row.
+ */
+function toSubmissionTestProgressCandidate(
+ entry: ReviewSummation,
+): SubmissionTestProgressCandidate | undefined {
+ const process = normalizeTestProcess(
+ entry.metadata?.testProcess
+ ?? entry.metadata?.testType,
+ )
+ const status = normalizeTestStatus(entry.metadata?.testStatus)
+ const progress = normalizeTestProgress(entry.metadata?.testProgress)
+ let statusPriority = 0
+
+ if (status === 'FAILED') {
+ statusPriority = 2
+ } else if (status === 'SUCCESS') {
+ statusPriority = 1
+ }
+
+ if (!process && !status && progress === undefined) {
+ return undefined
+ }
+
+ return {
+ inProgressPriority: status === 'IN PROGRESS'
+ ? 1
+ : 0,
+ process,
+ processPriority: process === 'system'
+ ? 1
+ : 0,
+ progress,
+ progressPercent: progress === undefined
+ ? undefined
+ : `${Math.round(progress * 100)}%`,
+ status,
+ statusPriority,
+ updatedAt: getTestProgressUpdatedAt(entry),
+ }
+}
+
+/**
+ * Returns display-ready marathon match test progress for one submission.
+ * @param submission Submission-like object containing Review API summations.
+ * @returns Current process, status, and percent text when present in summation metadata.
+ * Used by `SubmissionsTable` to render marathon-only test progress columns.
+ */
+export function getSubmissionTestProgress(
+ submission: Pick,
+): SubmissionTestProgressDisplay {
+ const candidates = (submission.reviewSummation || [])
+ .map(entry => toSubmissionTestProgressCandidate(entry))
+ .filter((entry): entry is SubmissionTestProgressCandidate => !!entry)
+ .sort((first, second) => (
+ second.inProgressPriority - first.inProgressPriority
+ || second.updatedAt - first.updatedAt
+ || second.processPriority - first.processPriority
+ || second.statusPriority - first.statusPriority
+ ))
+
+ if (!candidates.length) {
+ return {}
+ }
+
+ const current = candidates[0]
+
+ return {
+ process: current.process,
+ progressPercent: current.progressPercent,
+ status: current.status,
+ }
+}
+
+/**
+ * Normalizes challenge type labels for equality checks.
+ * @param value Challenge type name, abbreviation, or tag value.
+ * @returns Lowercase alphanumeric text with separators removed.
+ * Used by `isMarathonMatchChallenge` to compare inconsistent API payload shapes.
+ */
+function normalizeChallengeTypeToken(value: unknown): string {
+ return typeof value === 'string'
+ ? value.replace(/[^a-zA-Z0-9]/g, '')
+ .toLowerCase()
+ : ''
+}
+
+/**
+ * Returns whether the challenge is a Marathon Match.
+ * @param challenge Challenge payload from the challenge API.
+ * @returns `true` when the type or tags identify Marathon Match.
+ * Used by the submissions view to enable marathon-only test progress columns.
+ */
+export function isMarathonMatchChallenge(challenge: Pick): boolean {
+ const typeName = getChallengeTypeName(challenge.type)
+ const typeAbbreviation = typeof challenge.type === 'object'
+ ? challenge.type?.abbreviation
+ : undefined
+ const typeTokens = [
+ typeName,
+ typeAbbreviation,
+ ...(Array.isArray(challenge.tags)
+ ? challenge.tags
+ : []),
+ ].map(normalizeChallengeTypeToken)
+
+ return typeTokens.includes('marathonmatch')
+ || typeTokens.includes('mm')
+}
+
export function getStatusText(status?: string, selfService: boolean = false): string {
const normalizedStatus = normalizeStatus(status)
diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.tsx
index 8d6476b35..2000c0e57 100644
--- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.tsx
+++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.tsx
@@ -32,6 +32,7 @@ import {
canDownloadSubmissions,
getSubmissionFinalScore,
getSubmissionInitialScore,
+ isMarathonMatchChallenge,
showErrorToast,
} from '../../../../../lib/utils'
import { ReactComponent as LockIcon } from '../../../../../lib/assets/icons/lock.svg'
@@ -353,6 +354,7 @@ export const SubmissionsSection: FC = (
const workAppContext = useContext(WorkAppContext)
const canDownload = canDownloadSubmissions(workAppContext.userRoles)
+ const isMarathonMatch = isMarathonMatchChallenge(props.challenge)
const submissionsResult = useFetchSubmissions(
props.challengeId,
@@ -648,6 +650,7 @@ export const SubmissionsSection: FC = (
onSort={handleSort}
sortBy={sortBy}
sortOrder={sortOrder}
+ showMarathonMatchTestProgress={isMarathonMatch}
submissionDownloadLoading={downloadSubmissionResult.isLoading}
submissions={paginatedSubmissions}
/>
@@ -667,6 +670,7 @@ export const SubmissionsSection: FC = (
onSort={handleSort}
sortBy={sortBy}
sortOrder={sortOrder}
+ showMarathonMatchTestProgress={isMarathonMatch}
submissionDownloadLoading={downloadSubmissionResult.isLoading}
submissions={sortedCheckpointSubmissions}
/>