Skip to content

Commit 85e124e

Browse files
committed
Allow admins the ability to reprocess F2F and TG Task submissions
1 parent 801e22b commit 85e124e

File tree

12 files changed

+279
-14
lines changed

12 files changed

+279
-14
lines changed

src/apps/admin/src/challenge-management/ManageSubmissionPage/ManageSubmissionPage.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
useManageBusEventProps,
1919
useManageChallengeSubmissions,
2020
useManageChallengeSubmissionsProps,
21+
useManageSubmissionReprocess,
22+
useManageSubmissionReprocessProps,
2123
} from '../../lib/hooks'
2224
import {
2325
ActionLoading,
@@ -26,7 +28,7 @@ import {
2628
TableLoading,
2729
TableNoRecord,
2830
} from '../../lib'
29-
import { checkIsMM } from '../../lib/utils'
31+
import { checkIsMM, getSubmissionReprocessTopic } from '../../lib/utils'
3032

3133
import styles from './ManageSubmissionPage.module.scss'
3234

@@ -46,6 +48,10 @@ export const ManageSubmissionPage: FC<Props> = (props: Props) => {
4648
challengeInfo,
4749
}: useFetchChallengeProps = useFetchChallenge(challengeId)
4850
const isMM = useMemo(() => checkIsMM(challengeInfo), [challengeInfo])
51+
const submissionReprocessTopic = useMemo(
52+
() => getSubmissionReprocessTopic(challengeInfo),
53+
[challengeInfo],
54+
)
4955

5056
const {
5157
isLoading: isLoadingSubmission,
@@ -71,6 +77,12 @@ export const ManageSubmissionPage: FC<Props> = (props: Props) => {
7177
isLoadingBool: isDoingAvScanBool,
7278
doPostBusEvent: doPostBusEventAvScan,
7379
}: useManageAVScanProps = useManageAVScan()
80+
const {
81+
isLoading: isReprocessingSubmission,
82+
isLoadingBool: isReprocessingSubmissionBool,
83+
doReprocessSubmission,
84+
}: useManageSubmissionReprocessProps
85+
= useManageSubmissionReprocess(submissionReprocessTopic)
7486

7587
const isLoading = isLoadingSubmission || isLoadingChallenge
7688

@@ -111,13 +123,21 @@ export const ManageSubmissionPage: FC<Props> = (props: Props) => {
111123
showSubmissionHistory={showSubmissionHistory}
112124
setShowSubmissionHistory={setShowSubmissionHistory}
113125
isMM={isMM}
126+
isReprocessingSubmission={
127+
isReprocessingSubmission
128+
}
129+
doReprocessSubmission={doReprocessSubmission}
130+
canReprocessSubmission={Boolean(
131+
submissionReprocessTopic,
132+
)}
114133
/>
115134

116135
{(isDoingAvScanBool
117136
|| isDownloadingSubmissionBool
118137
|| isRemovingSubmissionBool
119138
|| isRunningTestBool
120-
|| isRemovingReviewSummationsBool) && (
139+
|| isRemovingReviewSummationsBool
140+
|| isReprocessingSubmissionBool) && (
121141
<ActionLoading />
122142
)}
123143
</div>

src/apps/admin/src/config/busEvent.config.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
RequestBusAPI,
88
RequestBusAPIAVScan,
99
RequestBusAPIAVScanPayload,
10+
RequestBusAPIReprocess,
11+
SubmissionReprocessPayload,
1012
} from '../lib/models'
1113

1214
/**
@@ -47,3 +49,26 @@ export const CREATE_BUS_EVENT_AV_RESCAN = (
4749
.toISOString(),
4850
topic: 'avscan.action.scan',
4951
})
52+
53+
export const SUBMISSION_REPROCESS_TOPICS = {
54+
FIRST2FINISH: 'first2finish.submission.received',
55+
TOPGEAR_TASK: 'topgear.submission.received',
56+
}
57+
58+
/**
59+
* Create data for submission reprocess bus event
60+
* @param topic kafka topic
61+
* @param payload submission data
62+
* @returns data for bus event
63+
*/
64+
export const CREATE_BUS_EVENT_REPROCESS_SUBMISSION = (
65+
topic: string,
66+
payload: SubmissionReprocessPayload,
67+
): RequestBusAPIReprocess => ({
68+
'mime-type': 'application/json',
69+
originator: 'review-api-v6',
70+
payload,
71+
timestamp: new Date()
72+
.toISOString(),
73+
topic,
74+
})

src/apps/admin/src/lib/components/SubmissionTable/SubmissionTable.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ interface Props {
3636
downloadSubmission: (submissionId: string) => void
3737
isDoingAvScan: IsRemovingType
3838
doPostBusEventAvScan: (submission: Submission) => void
39+
isReprocessingSubmission: IsRemovingType
40+
doReprocessSubmission: (submission: Submission) => void
41+
canReprocessSubmission?: boolean
3942
}
4043

4144
export const SubmissionTable: FC<Props> = (props: Props) => {
@@ -229,6 +232,15 @@ export const SubmissionTable: FC<Props> = (props: Props) => {
229232
doPostBusEventAvScan={function doPostBusEventAvScan() {
230233
props.doPostBusEventAvScan(data)
231234
}}
235+
canReprocessSubmission={
236+
props.canReprocessSubmission
237+
}
238+
isReprocessingSubmission={
239+
props.isReprocessingSubmission
240+
}
241+
doReprocessSubmission={function doReprocessSubmission() {
242+
props.doReprocessSubmission(data)
243+
}}
232244
isDownloading={props.isDownloading}
233245
downloadSubmission={function downloadSubmission() {
234246
props.downloadSubmission(data.id)
@@ -258,6 +270,9 @@ export const SubmissionTable: FC<Props> = (props: Props) => {
258270
props.downloadSubmission,
259271
props.isDoingAvScan,
260272
props.doPostBusEventAvScan,
273+
props.isReprocessingSubmission,
274+
props.doReprocessSubmission,
275+
props.canReprocessSubmission,
261276
],
262277
)
263278

src/apps/admin/src/lib/components/SubmissionTable/SubmissionTableActionsNonMM.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ interface Props {
1515
downloadSubmission: () => void
1616
isDoingAvScan: IsRemovingType
1717
doPostBusEventAvScan: () => void
18+
canReprocessSubmission?: boolean
19+
isReprocessingSubmission: IsRemovingType
20+
doReprocessSubmission: () => void
1821
}
1922

2023
export const SubmissionTableActionsNonMM: FC<Props> = (props: Props) => (
@@ -39,6 +42,17 @@ export const SubmissionTableActionsNonMM: FC<Props> = (props: Props) => (
3942
AV Rescan
4043
</Button>
4144
)}
45+
{props.canReprocessSubmission && (
46+
<Button
47+
onClick={function onClick() {
48+
props.doReprocessSubmission()
49+
}}
50+
primary
51+
disabled={props.isReprocessingSubmission[props.data.id]}
52+
>
53+
Reprocess submission
54+
</Button>
55+
)}
4256
</div>
4357
)
4458

src/apps/admin/src/lib/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ export * from './useAutoScrollTopWhenInit'
3636
export * from './useFetchChallenge'
3737
export * from './useDownloadSubmission'
3838
export * from './useManageAVScan'
39+
export * from './useManageSubmissionReprocess'
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Manage submission reprocess event
3+
*/
4+
import { useCallback, useMemo, useState } from 'react'
5+
import { toast } from 'react-toastify'
6+
import _ from 'lodash'
7+
8+
import {
9+
CREATE_BUS_EVENT_REPROCESS_SUBMISSION,
10+
} from '../../config/busEvent.config'
11+
import { createSubmissionReprocessPayload, reqToBusAPI } from '../services'
12+
import { handleError } from '../utils'
13+
import { IsRemovingType, Submission } from '../models'
14+
15+
export interface useManageSubmissionReprocessProps {
16+
isLoading: IsRemovingType
17+
isLoadingBool: boolean
18+
doReprocessSubmission: (submission: Submission) => Promise<void>
19+
}
20+
21+
/**
22+
* Manage submission reprocess
23+
*/
24+
export function useManageSubmissionReprocess(
25+
topic?: string,
26+
): useManageSubmissionReprocessProps {
27+
const [isLoading, setIsLoading] = useState<IsRemovingType>({})
28+
const isLoadingBool = useMemo(
29+
() => _.some(isLoading, value => value === true),
30+
[isLoading],
31+
)
32+
33+
const doReprocessSubmission = useCallback(
34+
async (submission: Submission) => {
35+
if (!topic) {
36+
toast.error(
37+
'Submission reprocess is only available for specific challenge types.',
38+
{
39+
toastId: 'Reprocess submission',
40+
},
41+
)
42+
return
43+
}
44+
45+
setIsLoading(previous => ({
46+
...previous,
47+
[submission.id]: true,
48+
}))
49+
50+
try {
51+
const payload
52+
= await createSubmissionReprocessPayload(submission)
53+
const data = CREATE_BUS_EVENT_REPROCESS_SUBMISSION(
54+
topic,
55+
payload,
56+
)
57+
await reqToBusAPI(data)
58+
toast.success('Reprocess submission request sent', {
59+
toastId: 'Reprocess submission',
60+
})
61+
} catch (error) {
62+
handleError(error as Error)
63+
} finally {
64+
setIsLoading(previous => ({
65+
...previous,
66+
[submission.id]: false,
67+
}))
68+
}
69+
},
70+
[topic],
71+
)
72+
73+
return {
74+
doReprocessSubmission,
75+
isLoading,
76+
isLoadingBool,
77+
}
78+
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { RequestBusAPI } from './RequestBusAPI.model'
22
import { RequestBusAPIAVScan } from './RequestBusAPIAVScan.model'
3+
import { RequestBusAPIReprocess } from './RequestBusAPIReprocess.model'
34

45
/**
56
* Common type for bus api request
67
*/
7-
export type CommonRequestBusAPI = RequestBusAPI | RequestBusAPIAVScan
8+
export type CommonRequestBusAPI = RequestBusAPI
9+
| RequestBusAPIAVScan
10+
| RequestBusAPIReprocess
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Request to reprocess submission bus api
3+
*/
4+
export interface SubmissionReprocessPayload {
5+
submissionId: string
6+
challengeId: string
7+
submissionUrl: string
8+
memberHandle: string
9+
memberId: string
10+
submittedDate: string
11+
}
12+
13+
export interface RequestBusAPIReprocess {
14+
topic: string
15+
originator: string
16+
timestamp: string
17+
'mime-type': string
18+
payload: SubmissionReprocessPayload
19+
}

src/apps/admin/src/lib/models/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export * from './RoleMemberInfo.model'
3535
export * from './Submission.model'
3636
export * from './RequestBusAPI.model'
3737
export * from './RequestBusAPIAVScan.model'
38+
export * from './RequestBusAPIReprocess.model'
3839
export * from './MemberSubmission.model'
3940
export * from './SubmissionReviewSummation.model'
4041
export * from './SSOUserLogin.model'

src/apps/admin/src/lib/services/submissions.service.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
MemberSubmission,
1111
RequestBusAPIAVScanPayload,
1212
Submission,
13+
SubmissionReprocessPayload,
1314
ValidateS3URIResult,
1415
} from '../models'
1516
import { validateS3URI } from '../utils'
@@ -125,3 +126,62 @@ export const createAvScanSubmissionPayload = async (
125126
url,
126127
}
127128
}
129+
130+
/**
131+
* Create submission reprocess payload
132+
* @param submissionInfo submission info
133+
* @returns resolves to the reprocess payload
134+
*/
135+
export const createSubmissionReprocessPayload = async (
136+
submissionInfo: Submission,
137+
): Promise<SubmissionReprocessPayload> => {
138+
const submissionId = submissionInfo.id
139+
const challengeId = submissionInfo.challengeId
140+
const submissionUrl = submissionInfo.url
141+
const memberId = submissionInfo.memberId
142+
const memberHandle = submissionInfo.submitterHandle ?? submissionInfo.createdBy
143+
const submittedDateValue = submissionInfo.submittedDate
144+
145+
if (!submissionId) {
146+
throw new Error('Submission id is not valid')
147+
}
148+
149+
const normalizedChallengeId = challengeId
150+
? challengeId.toString()
151+
: ''
152+
if (!normalizedChallengeId) {
153+
throw new Error('Challenge id is not valid')
154+
}
155+
156+
if (!submissionUrl) {
157+
throw new Error('Submission url is not valid')
158+
}
159+
160+
const normalizedMemberId = memberId ? memberId.toString() : ''
161+
if (!normalizedMemberId) {
162+
throw new Error('Member id is not valid')
163+
}
164+
165+
if (!memberHandle) {
166+
throw new Error('Member handle is not valid')
167+
}
168+
169+
const submittedDate = submittedDateValue
170+
? new Date(submittedDateValue)
171+
: undefined
172+
const submittedDateIso = submittedDate && !Number.isNaN(submittedDate.valueOf())
173+
? submittedDate.toISOString()
174+
: ''
175+
if (!submittedDateIso) {
176+
throw new Error('Submitted date is not valid')
177+
}
178+
179+
return {
180+
challengeId: normalizedChallengeId,
181+
memberHandle,
182+
memberId: normalizedMemberId,
183+
submissionId,
184+
submissionUrl,
185+
submittedDate: submittedDateIso,
186+
}
187+
}

0 commit comments

Comments
 (0)