Header image for Code Eval

Code Eval

Prompt

What's going to the issue with this block: const resetState = () => { setSelectedPrimaryActions([]); setSelectedPrimaryPolicies([]); setSelectedRelatedActions([]); setDecisionReason(undefined); }; const { data, loading, refetch: refetchJobInfo, } = useGQLManualReviewJobInfoQuery({ variables: { jobIds: closedJob ? [closedJob.id] : jobId ? [jobId] : [] }, fetchPolicy: 'no-cache', }); const [ getNextJob, { data: jobData, loading: jobDataLoading, error: jobDataError }, ] = useGQLDequeueManualReviewJobMutation({ variables: { queueId: queueId! }, fetchPolicy: 'no-cache', onCompleted: (data) => { // Here, we update the URL to include the queue ID, job ID, and lock // token. That way, users are able to send around the URL to others. const { dequeueManualReviewJob } = data; if (dequeueManualReviewJob == null) { // No reviewable job: the queue is drained, or everything left is // skipped by this reviewer. Send them back to the queue list instead // of leaving them on a perpetual loading spinner. navigate('/dashboard/manual_review/queues', { replace: true }); return; } const { job } = dequeueManualReviewJob; navigate( `/dashboard/manual_review/queues/review/${queueId}/${job.id}/${dequeueManualReviewJob.lockToken}`, { replace: true }, ); }, }); useEffect(() => { if ( jobId == null && closedJob == null && !loading && !jobDataLoading && !jobData ) { getNextJob(); } }, [getNextJob, jobId, closedJob, loading, jobDataLoading, jobData]); const [isAdvancingToNextJob, setIsAdvancingToNextJob] = useState(false); // The jobId we deleted by invalidating its last report. Matching it by // identity lets the redirect effect below skip its bounce-to-recent // until we navigate onto the next job, with no timing window. const invalidationDeletedJobIdRef = useRef<string | null>(null); useEffect(() => { // If the job we're viewing no longer exists in this queue, send the // reviewer to recent decisions, unless we deleted it ourselves via // invalidation (handleInvalidated advances to the next job instead). if ( jobId == null || closedJob || loading || isAdvancingToNextJob || invalidationDeletedJobIdRef.current === jobId ) { return; } const stillExists = data?.me?.reviewableQueues .find((queue) => queue.id === queueId) ?.jobs.find((job) => job.id === jobId); if (stillExists) { return; } navigate(`/dashboard/manual_review/recent/?jobId=${jobId}`, { replace: true, }); }, [ jobId, data, loading, navigate, queueId, closedJob, isAdvancingToNextJob, ]); // Drop a stale claim once we've moved to a different job so returning // to the deleted job (e.g. browser back) still routes to recent. useEffect(() => { if ( invalidationDeletedJobIdRef.current != null && invalidationDeletedJobIdRef.current !== jobId ) { invalidationDeletedJobIdRef.current = null; } }, [jobId]); // Modal-driven entry of action parameters. Opening the modal in `create` // mode happens *before* the action is added to `selectedPrimaryActions`, // so cancelling leaves the picker exactly as it was. `edit` mode opens // the modal pre-filled with the values already saved on a selected action, // and Save replaces that single action's payload. type ParamsModalState = | { open: false } | { open: true; mode: 'create'; actionId: string; actionName: string; parameters: ReadonlyArray<GQLActionParameter>; initialValues: ActionParameterValues; // Snapshot of the click context so we can mirror the existing // "deselect Ignore on add" branch without re-deriving it. target: ManualReviewJobEnqueuedPrimaryActionData['target']; } | { open: true; mode: 'edit'; actionId: string; actionName: string; parameters: ReadonlyArray<GQLActionParameter>; initialValues: ActionParameterValues; }; const [paramsModal, setParamsModal] = useState<ParamsModalState>({ open: false, }); const goBackToQueuesPage = () => navigate('/dashboard/manual_review/queues'); const hideModal = () => setModalInfo({ ...modalInfo, visible: false }); const [submitDecision, { loading: submissionLoading }] = useGQLSubmitManualReviewDecisionMutation({ fetchPolicy: 'no-cache', onError: (_e) => { setModalInfo({ visible: true, modalBody: 'Unknown error occured.', footer: [ { title: 'Ok', type: 'primary', onClick: hideModal, }, ], }); }, onCompleted: async (response) => { switch (response.submitManualReviewDecision.__typename) { case 'SubmitDecisionSuccessResponse': { resetState(); await getNextJob(); break; } case 'JobHasAlreadyBeenSubmittedError': { setModalInfo({ visible: true, modalBody: 'This job has already been submitted. Would you like to move to the next job?', footer: [ { title: 'Yes', type: 'primary', onClick: async () => { await getNextJob(); hideModal(); }, }, { title: 'No', type: 'primary', onClick: goBackToQueuesPage, }, ], }); break; } case 'NoJobWithIdInQueueError': { setModalInfo({ visible: true, modalBody: 'We could not find the requested job in this queue.', footer: [ { title: 'Go Back', type: 'primary', onClick: goBackToQueuesPage, }, ], }); break; } case 'SubmittedJobActionNotFoundError': { setModalInfo({ visible: true, modalBody: 'Selected action not found. Please try again.', footer: [ { title: 'Ok', type: 'primary', onClick: hideModal, }, ], }); break; } case 'RecordingJobDecisionFailedError': { setModalInfo({ visible: true, modalBody: 'Job submission failed. Please try again.', footer: [ { title: 'Ok', type: 'primary', onClick: hideModal, }, ], }); break; } case 'MissingRequiredDecisionReasonError': { // Server-side backstop for `requiresDecisionReasonInMrt`. // Normally the canBeSubmitted gate prevents reaching this state, // but the org setting could have flipped between page load and // submit, or the reviewer typed only whitespace (which the gate // also rejects, but a scripted client could still get here). setModalInfo({ visible: true, modalBody: 'This org requires every decision to include a reason. Add a reason and resubmit.', footer: [ { title: 'Ok', type: 'primary', onClick: hideModal, }, ], }); break; } case 'MissingRequiredPolicyForDecisionError': { // Server-side backstop for `requiresPolicyForDecisionsInMrt`. // Normally the canBeSubmitted gate prevents reaching this state, // but the org setting could have flipped between page load and // submit — surface a clear message and let the reviewer fix it. setModalInfo({ visible: true, modalBody: 'This org requires every decision to include at least one policy. Pick a policy and resubmit.', footer: [ { title: 'Ok', type: 'primary', onClick: hideModal, }, ], }); break; } } }, }); const canBeSubmitted = (() => { if (selectedPrimaryActions.length === 0 || submissionLoading) { return false; } // If the user has selected a built-in action, then we don't want to allow // them to select any policies. // NB: THIS IS AN ILLEGAL STATE. IF YOU ARE ENDING UP HERE, OUR STATE // MANAGEMENT IS BROKEN AND NEEDS TO BE UPDATED. Specifically, when a user // selects the 'ignore' action, we clear out any currently selected policies // and reset that state. if ( selectedPrimaryActions.some( (it) => 'type' in it.action && it.action.type === 'IGNORE', ) && selectedPrimaryPolicies.length > 0 ) { return false; } if (data?.myOrg?.requiresPolicyForDecisionsInMrt) { // First check if there are related actions, and if there are, make sure // they include policies if ( selectedRelatedActions.length > 0 && selectedRelatedActions.some((it) => it.policies.length === 0) ) { return false; } // Return false if there are no primary policies selected and if no // built-in action has been selected if ( selectedPrimaryPolicies.length === 0 && selectedPrimaryActions.some((it) => 'id' in it.action) ) { return false; } // return false if more than one appeal action is selected // since ACCEPT/REJECT are the only options if ( selectedPrimaryActions.length > 1 && (selectedPrimaryActions.some( (it) => 'type' in it.action && it.action.type === 'REJECT_APPEAL', ) || selectedPrimaryActions.some( (it) => 'type' in it.action && it.action.type === 'ACCEPT_APPEAL', )) ) { return false; } } // If the org requires a decision reason, and no decision reason has been // provided, return false. The requirement is split (see #757): ignoring a // job (a decision made up solely of the built-in IGNORE action) is governed // by its own setting, separate from acting on a violating job. const isIgnoreDecision = selectedPrimaryActions.every( (it) => 'type' in it.action && it.action.type === 'IGNORE', ); const reasonRequired = isIgnoreDecision ? data?.myOrg?.requiresDecisionReasonOnIgnoreInMrt : data?.myOrg?.requiresDecisionReasonInMrt; if (reasonRequired && !isNonEmptyString(decisionReason)) { return false; } return true; })(); const getActionName = useCallback( (actionId: string) => data?.myOrg?.actions.find((action) => action.id === actionId)?.name ?? 'Unknown', [data?.myOrg], ); // Gate any related-action enqueues that involve a parameterized action // behind the shared `ActionParametersModal`. Items without parameters (or // already carrying a `customMrtApiParamDecisionPayload` from another flow) // pass through unchanged. The modal element is rendered once below. // Hook is declared up here (rather than next to the v2 component props) // because `ManualReviewJobReviewImpl` short-circuits with `throw` further // down, and rules-of-hooks forbid placing hooks after a conditional throw. const enqueueGate = useEnqueueActionGate({ allActions: data?.myOrg?.actions ?? [], onEnqueueActions: (actions) => setSelectedRelatedActions( recomputeSelectedRelatedActions(actions, selectedRelatedActions), ), }); const job = closedJob ? closedJob : jobData ? jobData.dequeueManualReviewJob?.job : data?.me?.reviewableQueues .find((queue) => queue.id === queueId) ?.jobs.find((job) => job.id === jobId); const pendingJobCount = jobData?.dequeueManualReviewJob ? jobData.dequeueManualReviewJob.numPendingJobs : data?.me?.reviewableQueues ? data?.me?.reviewableQueues.find((queue) => queue.id === queueId) ?.pendingJobCount : undefined; const [logSkip] = useGQLLogSkipMutation({ // This is safe because we check it before calling logSkip // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain variables: { input: { queueId: queueId!, jobId: job?.id! } }, fetchPolicy: 'no-cache', }); const advanceToNextJobAfterInvalidation = useCallback(async () => { setIsAdvancingToNextJob(true); try { resetState(); const result = await getNextJob(); if (result.data?.dequeueManualReviewJob == null) { navigate('/dashboard/manual_review/queues'); } } finally { setIsAdvancingToNextJob(false); } }, [getNextJob, navigate, resetState]);