diff --git a/src/constants.js b/src/constants.js index 6d975d6b..0358e2ed 100644 --- a/src/constants.js +++ b/src/constants.js @@ -43,6 +43,7 @@ export const ExamAction = Object.freeze({ ERROR: 'error', RESET: 'reset_attempt', CLICK_DOWNLOAD_SOFTWARE: 'click_download_software', + DECLINE: 'decline', }); export const VerificationStatus = Object.freeze({ diff --git a/src/data/api.js b/src/data/api.js index 5cd6f610..1aa603c5 100644 --- a/src/data/api.js +++ b/src/data/api.js @@ -62,6 +62,10 @@ export async function softwareDownloadAttempt(attemptId) { return updateAttemptStatus(attemptId, ExamAction.CLICK_DOWNLOAD_SOFTWARE); } +export async function declineAttempt(attemptId) { + return updateAttemptStatus(attemptId, ExamAction.DECLINE); +} + export async function fetchExamReviewPolicy(examId) { const url = new URL( `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/proctored_exam/review_policy/exam_id/${examId}/`, diff --git a/src/data/thunks.js b/src/data/thunks.js index 4d64a405..827dbce3 100644 --- a/src/data/thunks.js +++ b/src/data/thunks.js @@ -11,6 +11,7 @@ import { fetchVerificationStatus, fetchExamReviewPolicy, resetAttempt, + declineAttempt, } from './api'; import { isEmpty } from '../helpers'; import { @@ -167,9 +168,16 @@ export function skipProctoringExam() { logError('Failed to skip proctored exam. No exam id.'); return; } - await updateAttemptAfter( - exam.course_id, exam.content_id, createExamAttempt(exam.id, true, false), - )(dispatch); + const attemptId = exam.attempt.attempt_id; + if (attemptId) { + await updateAttemptAfter( + exam.course_id, exam.content_id, declineAttempt(attemptId), + )(dispatch); + } else { + await updateAttemptAfter( + exam.course_id, exam.content_id, createExamAttempt(exam.id, true, false), + )(dispatch); + } }; } diff --git a/src/exam/Exam.jsx b/src/exam/Exam.jsx index 66476893..a09bf267 100644 --- a/src/exam/Exam.jsx +++ b/src/exam/Exam.jsx @@ -21,10 +21,10 @@ const Exam = ({ isTimeLimited, children }) => { const { isLoading, activeAttempt, showTimer, stopExam, exam, expireExam, pollAttempt, apiErrorMsg, pingAttempt, - getVerificationData, getAllowProctoringOptOut, getProctoringSettings, + getVerificationData, getProctoringSettings, } = state; - const { type: examType, content_id: sequenceId, id: examId } = exam || {}; + const { type: examType, id: examId } = exam || {}; useEffect(() => { if (examId) { @@ -32,7 +32,6 @@ const Exam = ({ isTimeLimited, children }) => { } if (examType === ExamType.PROCTORED) { getVerificationData(); - getAllowProctoringOptOut(sequenceId); } // this makes sure useEffect gets called only one time after the exam has been fetched diff --git a/src/instructions/index.jsx b/src/instructions/index.jsx index 356d55db..5d083c2f 100644 --- a/src/instructions/index.jsx +++ b/src/instructions/index.jsx @@ -41,12 +41,8 @@ const Instructions = ({ children }) => { const renderEmptyAttemptInstructions = () => { let component = ; - if (examType === ExamType.PROCTORED) { - if (skipProctoring) { - component = ; - } else if (!prerequisitesPassed) { - component = ; - } + if (examType === ExamType.PROCTORED && !prerequisitesPassed) { + component = ; } return component; }; @@ -59,12 +55,14 @@ const Instructions = ({ children }) => { } switch (true) { + case examType === ExamType.PROCTORED && skipProctoring: + return ; case isEmpty(attempt) || !attempt.attempt_id: return renderEmptyAttemptInstructions(); case attempt.attempt_status === ExamStatus.CREATED: return examType === ExamType.PROCTORED && verificationStatus !== VerificationStatus.APPROVED ? - : ; + : ; case attempt.attempt_status === ExamStatus.DOWNLOAD_SOFTWARE_CLICKED: return ; case attempt.attempt_status === ExamStatus.READY_TO_START: diff --git a/src/instructions/proctored_exam/download-instructions/index.jsx b/src/instructions/proctored_exam/download-instructions/index.jsx index e3a982e9..00871359 100644 --- a/src/instructions/proctored_exam/download-instructions/index.jsx +++ b/src/instructions/proctored_exam/download-instructions/index.jsx @@ -1,4 +1,5 @@ import React, { useContext, useState } from 'react'; +import PropTypes from 'prop-types'; import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { Container } from '@edx/paragon'; import ExamStateContext from '../../../context'; @@ -10,13 +11,15 @@ import ProviderInstructions from './ProviderInstructions'; import DefaultInstructions from './DefaultInstructions'; import DownloadButtons from './DownloadButtons'; import Footer from '../Footer'; +import SkipProctoredExamButton from '../SkipProctoredExamButton'; -const DownloadSoftwareProctoredExamInstructions = ({ intl }) => { +const DownloadSoftwareProctoredExamInstructions = ({ intl, skipProctoredExam }) => { const state = useContext(ExamStateContext); const { proctoringSettings, exam, getExamAttemptsData, + allowProctoringOptOut, } = state; const { attempt, @@ -120,6 +123,7 @@ const DownloadSoftwareProctoredExamInstructions = ({ intl }) => {

)} + {allowProctoringOptOut && }