-
Notifications
You must be signed in to change notification settings - Fork 21
Allow admins the ability to reprocess F2F and TG Task submissions #1316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,8 @@ import { | |
| useManageBusEventProps, | ||
| useManageChallengeSubmissions, | ||
| useManageChallengeSubmissionsProps, | ||
| useManageSubmissionReprocess, | ||
| useManageSubmissionReprocessProps, | ||
| } from '../../lib/hooks' | ||
| import { | ||
| ActionLoading, | ||
|
|
@@ -26,7 +28,7 @@ import { | |
| TableLoading, | ||
| TableNoRecord, | ||
| } from '../../lib' | ||
| import { checkIsMM } from '../../lib/utils' | ||
| import { checkIsMM, getSubmissionReprocessTopic } from '../../lib/utils' | ||
|
|
||
| import styles from './ManageSubmissionPage.module.scss' | ||
|
|
||
|
|
@@ -46,6 +48,10 @@ export const ManageSubmissionPage: FC<Props> = (props: Props) => { | |
| challengeInfo, | ||
| }: useFetchChallengeProps = useFetchChallenge(challengeId) | ||
| const isMM = useMemo(() => checkIsMM(challengeInfo), [challengeInfo]) | ||
| const submissionReprocessTopic = useMemo( | ||
| () => getSubmissionReprocessTopic(challengeInfo), | ||
| [challengeInfo], | ||
| ) | ||
|
|
||
| const { | ||
| isLoading: isLoadingSubmission, | ||
|
|
@@ -71,6 +77,12 @@ export const ManageSubmissionPage: FC<Props> = (props: Props) => { | |
| isLoadingBool: isDoingAvScanBool, | ||
| doPostBusEvent: doPostBusEventAvScan, | ||
| }: useManageAVScanProps = useManageAVScan() | ||
| const { | ||
| isLoading: isReprocessingSubmission, | ||
| isLoadingBool: isReprocessingSubmissionBool, | ||
| doReprocessSubmission, | ||
| }: useManageSubmissionReprocessProps | ||
| = useManageSubmissionReprocess(submissionReprocessTopic) | ||
|
|
||
| const isLoading = isLoadingSubmission || isLoadingChallenge | ||
|
|
||
|
|
@@ -111,13 +123,21 @@ export const ManageSubmissionPage: FC<Props> = (props: Props) => { | |
| showSubmissionHistory={showSubmissionHistory} | ||
| setShowSubmissionHistory={setShowSubmissionHistory} | ||
| isMM={isMM} | ||
| isReprocessingSubmission={ | ||
| isReprocessingSubmission | ||
| } | ||
| doReprocessSubmission={doReprocessSubmission} | ||
| canReprocessSubmission={Boolean( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| submissionReprocessTopic, | ||
| )} | ||
| /> | ||
|
|
||
| {(isDoingAvScanBool | ||
| || isDownloadingSubmissionBool | ||
| || isRemovingSubmissionBool | ||
| || isRunningTestBool | ||
| || isRemovingReviewSummationsBool) && ( | ||
| || isRemovingReviewSummationsBool | ||
| || isReprocessingSubmissionBool) && ( | ||
| <ActionLoading /> | ||
| )} | ||
| </div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,8 @@ import { | |
| RequestBusAPI, | ||
| RequestBusAPIAVScan, | ||
| RequestBusAPIAVScanPayload, | ||
| RequestBusAPIReprocess, | ||
| SubmissionReprocessPayload, | ||
| } from '../lib/models' | ||
|
|
||
| /** | ||
|
|
@@ -47,3 +49,26 @@ export const CREATE_BUS_EVENT_AV_RESCAN = ( | |
| .toISOString(), | ||
| topic: 'avscan.action.scan', | ||
| }) | ||
|
|
||
| export const SUBMISSION_REPROCESS_TOPICS = { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| FIRST2FINISH: 'first2finish.submission.received', | ||
| TOPGEAR_TASK: 'topgear.submission.received', | ||
| } | ||
|
|
||
| /** | ||
| * Create data for submission reprocess bus event | ||
| * @param topic kafka topic | ||
| * @param payload submission data | ||
| * @returns data for bus event | ||
| */ | ||
| export const CREATE_BUS_EVENT_REPROCESS_SUBMISSION = ( | ||
| topic: string, | ||
| payload: SubmissionReprocessPayload, | ||
| ): RequestBusAPIReprocess => ({ | ||
| 'mime-type': 'application/json', | ||
| originator: 'review-api-v6', | ||
| payload, | ||
| timestamp: new Date() | ||
| .toISOString(), | ||
| topic, | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,9 @@ interface Props { | |
| downloadSubmission: (submissionId: string) => void | ||
| isDoingAvScan: IsRemovingType | ||
| doPostBusEventAvScan: (submission: Submission) => void | ||
| isReprocessingSubmission: IsRemovingType | ||
| doReprocessSubmission: (submission: Submission) => void | ||
| canReprocessSubmission?: boolean | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| } | ||
|
|
||
| export const SubmissionTable: FC<Props> = (props: Props) => { | ||
|
|
@@ -229,6 +232,15 @@ export const SubmissionTable: FC<Props> = (props: Props) => { | |
| doPostBusEventAvScan={function doPostBusEventAvScan() { | ||
| props.doPostBusEventAvScan(data) | ||
| }} | ||
| canReprocessSubmission={ | ||
| props.canReprocessSubmission | ||
| } | ||
| isReprocessingSubmission={ | ||
| props.isReprocessingSubmission | ||
| } | ||
| doReprocessSubmission={function doReprocessSubmission() { | ||
| props.doReprocessSubmission(data) | ||
| }} | ||
| isDownloading={props.isDownloading} | ||
| downloadSubmission={function downloadSubmission() { | ||
| props.downloadSubmission(data.id) | ||
|
|
@@ -258,6 +270,9 @@ export const SubmissionTable: FC<Props> = (props: Props) => { | |
| props.downloadSubmission, | ||
| props.isDoingAvScan, | ||
| props.doPostBusEventAvScan, | ||
| props.isReprocessingSubmission, | ||
| props.doReprocessSubmission, | ||
| props.canReprocessSubmission, | ||
| ], | ||
| ) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,9 @@ interface Props { | |
| downloadSubmission: () => void | ||
| isDoingAvScan: IsRemovingType | ||
| doPostBusEventAvScan: () => void | ||
| canReprocessSubmission?: boolean | ||
| isReprocessingSubmission: IsRemovingType | ||
| doReprocessSubmission: () => void | ||
| } | ||
|
|
||
| export const SubmissionTableActionsNonMM: FC<Props> = (props: Props) => ( | ||
|
|
@@ -39,6 +42,17 @@ export const SubmissionTableActionsNonMM: FC<Props> = (props: Props) => ( | |
| AV Rescan | ||
| </Button> | ||
| )} | ||
| {props.canReprocessSubmission && ( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
| <Button | ||
| onClick={function onClick() { | ||
| props.doReprocessSubmission() | ||
| }} | ||
| primary | ||
| disabled={props.isReprocessingSubmission[props.data.id]} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| > | ||
| Reprocess submission | ||
| </Button> | ||
| )} | ||
| </div> | ||
| ) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| /** | ||
| * Manage submission reprocess event | ||
| */ | ||
| import { useCallback, useMemo, useState } from 'react' | ||
| import { toast } from 'react-toastify' | ||
| import _ from 'lodash' | ||
|
|
||
| import { | ||
| CREATE_BUS_EVENT_REPROCESS_SUBMISSION, | ||
| } from '../../config/busEvent.config' | ||
| import { createSubmissionReprocessPayload, reqToBusAPI } from '../services' | ||
| import { handleError } from '../utils' | ||
| import { IsRemovingType, Submission } from '../models' | ||
|
|
||
| export interface useManageSubmissionReprocessProps { | ||
| isLoading: IsRemovingType | ||
| isLoadingBool: boolean | ||
| doReprocessSubmission: (submission: Submission) => Promise<void> | ||
| } | ||
|
|
||
| /** | ||
| * Manage submission reprocess | ||
| */ | ||
| export function useManageSubmissionReprocess( | ||
| topic?: string, | ||
| ): useManageSubmissionReprocessProps { | ||
| const [isLoading, setIsLoading] = useState<IsRemovingType>({}) | ||
| const isLoadingBool = useMemo( | ||
| () => _.some(isLoading, value => value === true), | ||
| [isLoading], | ||
| ) | ||
|
|
||
| const doReprocessSubmission = useCallback( | ||
| async (submission: Submission) => { | ||
| if (!topic) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| toast.error( | ||
| 'Submission reprocess is only available for specific challenge types.', | ||
| { | ||
| toastId: 'Reprocess submission', | ||
| }, | ||
| ) | ||
| return | ||
| } | ||
|
|
||
| setIsLoading(previous => ({ | ||
| ...previous, | ||
| [submission.id]: true, | ||
| })) | ||
|
|
||
| try { | ||
| const payload | ||
| = await createSubmissionReprocessPayload(submission) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| const data = CREATE_BUS_EVENT_REPROCESS_SUBMISSION( | ||
| topic, | ||
| payload, | ||
| ) | ||
| await reqToBusAPI(data) | ||
| toast.success('Reprocess submission request sent', { | ||
| toastId: 'Reprocess submission', | ||
| }) | ||
| } catch (error) { | ||
| handleError(error as Error) | ||
| } finally { | ||
| setIsLoading(previous => ({ | ||
| ...previous, | ||
| [submission.id]: false, | ||
| })) | ||
| } | ||
| }, | ||
| [topic], | ||
| ) | ||
|
|
||
| return { | ||
| doReprocessSubmission, | ||
| isLoading, | ||
| isLoadingBool, | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,10 @@ | ||
| import { RequestBusAPI } from './RequestBusAPI.model' | ||
| import { RequestBusAPIAVScan } from './RequestBusAPIAVScan.model' | ||
| import { RequestBusAPIReprocess } from './RequestBusAPIReprocess.model' | ||
|
|
||
| /** | ||
| * Common type for bus api request | ||
| */ | ||
| export type CommonRequestBusAPI = RequestBusAPI | RequestBusAPIAVScan | ||
| export type CommonRequestBusAPI = RequestBusAPI | ||
| | RequestBusAPIAVScan | ||
| | RequestBusAPIReprocess |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| /** | ||
| * Request to reprocess submission bus api | ||
| */ | ||
| export interface SubmissionReprocessPayload { | ||
| submissionId: string | ||
| challengeId: string | ||
| submissionUrl: string | ||
| memberHandle: string | ||
| memberId: string | ||
| submittedDate: string | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| } | ||
|
|
||
| export interface RequestBusAPIReprocess { | ||
| topic: string | ||
| originator: string | ||
| timestamp: string | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| 'mime-type': string | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| payload: SubmissionReprocessPayload | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ import { | |
| MemberSubmission, | ||
| RequestBusAPIAVScanPayload, | ||
| Submission, | ||
| SubmissionReprocessPayload, | ||
| ValidateS3URIResult, | ||
| } from '../models' | ||
| import { validateS3URI } from '../utils' | ||
|
|
@@ -125,3 +126,62 @@ export const createAvScanSubmissionPayload = async ( | |
| url, | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Create submission reprocess payload | ||
| * @param submissionInfo submission info | ||
| * @returns resolves to the reprocess payload | ||
| */ | ||
| export const createSubmissionReprocessPayload = async ( | ||
| submissionInfo: Submission, | ||
| ): Promise<SubmissionReprocessPayload> => { | ||
| const submissionId = submissionInfo.id | ||
| const challengeId = submissionInfo.challengeId | ||
| const submissionUrl = submissionInfo.url | ||
| const memberId = submissionInfo.memberId | ||
| const memberHandle = submissionInfo.submitterHandle ?? submissionInfo.createdBy | ||
| const submittedDateValue = submissionInfo.submittedDate | ||
|
|
||
| if (!submissionId) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| throw new Error('Submission id is not valid') | ||
| } | ||
|
|
||
| const normalizedChallengeId = challengeId | ||
| ? challengeId.toString() | ||
| : '' | ||
| if (!normalizedChallengeId) { | ||
| throw new Error('Challenge id is not valid') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| } | ||
|
|
||
| if (!submissionUrl) { | ||
| throw new Error('Submission url is not valid') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| } | ||
|
|
||
| const normalizedMemberId = memberId ? memberId.toString() : '' | ||
| if (!normalizedMemberId) { | ||
| throw new Error('Member id is not valid') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| } | ||
|
|
||
| if (!memberHandle) { | ||
| throw new Error('Member handle is not valid') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| } | ||
|
|
||
| const submittedDate = submittedDateValue | ||
| ? new Date(submittedDateValue) | ||
| : undefined | ||
| const submittedDateIso = submittedDate && !Number.isNaN(submittedDate.valueOf()) | ||
| ? submittedDate.toISOString() | ||
| : '' | ||
| if (!submittedDateIso) { | ||
| throw new Error('Submitted date is not valid') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| } | ||
|
|
||
| return { | ||
| challengeId: normalizedChallengeId, | ||
| memberHandle, | ||
| memberId: normalizedMemberId, | ||
| submissionId, | ||
| submissionUrl, | ||
| submittedDate: submittedDateIso, | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[⚠️
correctness]The
useManageSubmissionReprocesshook is called withsubmissionReprocessTopic, which is derived fromchallengeInfo. Ensure thatchallengeInfois always in a valid state before using it to derivesubmissionReprocessTopicto prevent potential runtime errors.