diff --git a/plugins/course-apps/proctoring/Settings.jsx b/plugins/course-apps/proctoring/Settings.jsx index 645bc95dd0..40a8ac9c4f 100644 --- a/plugins/course-apps/proctoring/Settings.jsx +++ b/plugins/course-apps/proctoring/Settings.jsx @@ -41,6 +41,7 @@ const ProctoringSettings = ({ onClose }) => { const [loadingPermissionError, setLoadingPermissionError] = useState(false); const [allowLtiProviders, setAllowLtiProviders] = useState(false); const [availableProctoringProviders, setAvailableProctoringProviders] = useState([]); + const [requiresEscalationEmailProviders, setRequiresEscalationEmailProviders] = useState([]); const [ltiProctoringProviders, setLtiProctoringProviders] = useState([]); const [courseStartDate, setCourseStartDate] = useState(''); const [saveSuccess, setSaveSuccess] = useState(false); @@ -84,12 +85,9 @@ const ProctoringSettings = ({ onClose }) => { } else if (name === 'proctoringProvider') { const newFormValues = { ...formValues, proctoringProvider: value }; - if (value === 'proctortrack') { - setFormValues({ ...newFormValues, createZendeskTickets: false }); + if (requiresEscalationEmailProviders.includes(value)) { + setFormValues({ ...newFormValues }); setShowEscalationEmail(true); - } else if (value === 'software_secure') { - setFormValues({ ...newFormValues, createZendeskTickets: true }); - setShowEscalationEmail(false); } else if (isLtiProvider(value)) { setFormValues(newFormValues); setShowEscalationEmail(true); @@ -123,7 +121,7 @@ const ProctoringSettings = ({ onClose }) => { studioDataToPostBack.proctored_exam_settings.allow_proctoring_opt_out = formValues.allowOptingOut; } - if (formValues.proctoringProvider === 'proctortrack') { + if (requiresEscalationEmailProviders.includes(formValues.proctoringProvider)) { studioDataToPostBack.proctored_exam_settings.proctoring_escalation_email = formValues.escalationEmail === '' ? null : formValues.escalationEmail; } @@ -160,7 +158,7 @@ const ProctoringSettings = ({ onClose }) => { event.preventDefault(); const isLtiProviderSelected = isLtiProvider(formValues.proctoringProvider); if ( - (formValues.proctoringProvider === 'proctortrack' || isLtiProviderSelected) + (requiresEscalationEmailProviders.includes(formValues.proctoringProvider) || isLtiProviderSelected) && !EmailValidator.validate(formValues.escalationEmail) && !(formValues.escalationEmail === '' && !formValues.enableProctoredExams) ) { @@ -527,6 +525,7 @@ const ProctoringSettings = ({ onClose }) => { setSubmissionInProgress(false); setCourseStartDate(settingsResponse.data.course_start_date); setAvailableProctoringProviders(settingsResponse.data.available_proctoring_providers); + setRequiresEscalationEmailProviders(settingsResponse.data.requires_escalation_email_providers); // The list of providers returned by studio settings are the default behavior. If lti_external // is available as an option display the list of LTI providers returned by the exam service. @@ -554,10 +553,11 @@ const ProctoringSettings = ({ onClose }) => { selectedProvider = proctoredExamSettings.proctoring_provider; } - const isProctortrack = selectedProvider === 'proctortrack'; + const requiresEscalationEmailProvidersList = settingsResponse.data.requires_escalation_email_providers; + const isEscalationEmailRequired = requiresEscalationEmailProvidersList.includes(selectedProvider); const ltiProviderSelected = proctoringProvidersLti.some(p => p.name === selectedProvider); - if (isProctortrack || ltiProviderSelected) { + if (isEscalationEmailRequired || ltiProviderSelected) { setShowEscalationEmail(true); } diff --git a/plugins/course-apps/proctoring/Settings.test.jsx b/plugins/course-apps/proctoring/Settings.test.jsx index 65c6ca7b71..b50e80922a 100644 --- a/plugins/course-apps/proctoring/Settings.test.jsx +++ b/plugins/course-apps/proctoring/Settings.test.jsx @@ -86,7 +86,8 @@ describe('ProctoredExamSettings', () => { proctoring_escalation_email: 'test@example.com', create_zendesk_tickets: true, }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc', 'lti_external'], + available_proctoring_providers: ['software_secure', 'mockproc', 'lti_external'], + requires_escalation_email_providers: ['test_lti'], course_start_date: '2070-01-01T00:00:00Z', }); } @@ -104,13 +105,13 @@ describe('ProctoredExamSettings', () => { await act(async () => render(intlWrapper())); }); - it('Updates Zendesk ticket field if proctortrack is provider', async () => { + it('Updates Zendesk ticket field if software_secure is provider', async () => { await waitFor(() => { screen.getByDisplayValue('mockproc'); }); const selectElement = screen.getByDisplayValue('mockproc'); - fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); - const zendeskTicketInput = screen.getByTestId('createZendeskTicketsNo'); + fireEvent.change(selectElement, { target: { value: 'software_secure' } }); + const zendeskTicketInput = screen.getByTestId('createZendeskTicketsYes'); expect(zendeskTicketInput.checked).toEqual(true); }); @@ -147,7 +148,8 @@ describe('ProctoredExamSettings', () => { proctoring_escalation_email: 'test@example.com', create_zendesk_tickets: true, }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], + available_proctoring_providers: ['software_secure', 'mockproc'], + requires_escalation_email_providers: [], course_start_date: '2070-01-01T00:00:00Z', }); @@ -199,7 +201,7 @@ describe('ProctoredExamSettings', () => { }); describe('Validation with invalid escalation email', () => { - const proctoringProvidersRequiringEscalationEmail = ['proctortrack', 'test_lti']; + const proctoringProvidersRequiringEscalationEmail = ['test_lti']; beforeEach(async () => { axiosMock.onGet( @@ -208,14 +210,22 @@ describe('ProctoredExamSettings', () => { proctored_exam_settings: { enable_proctored_exams: true, allow_proctoring_opt_out: false, - proctoring_provider: 'proctortrack', + proctoring_provider: 'lti_external', proctoring_escalation_email: 'test@example.com', create_zendesk_tickets: true, }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc', 'lti_external'], + available_proctoring_providers: ['software_secure', 'mockproc', 'lti_external'], + requires_escalation_email_providers: ['test_lti'], course_start_date: '2070-01-01T00:00:00Z', }); + axiosMock.onGet( + `${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`, + ).reply(200, { + provider: 'test_lti', + escalation_email: 'test@example.com', + }); + axiosMock.onPatch( ExamsApiService.getExamConfigurationUrl(defaultProps.courseId), ).reply(204, {}); @@ -230,7 +240,7 @@ describe('ProctoredExamSettings', () => { proctoringProvidersRequiringEscalationEmail.forEach(provider => { it(`Creates an alert when no proctoring escalation email is provided with ${provider} selected`, async () => { await waitFor(() => { - screen.getByDisplayValue('proctortrack'); + screen.getByDisplayValue('LTI Provider'); }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); @@ -251,10 +261,10 @@ describe('ProctoredExamSettings', () => { it(`Creates an alert when invalid proctoring escalation email is provided with ${provider} selected`, async () => { await waitFor(() => { - screen.getByDisplayValue('proctortrack'); + screen.getByDisplayValue('LTI Provider'); }); - const selectElement = screen.getByDisplayValue('proctortrack'); + const selectElement = screen.getByDisplayValue('LTI Provider'); fireEvent.change(selectElement, { target: { value: provider } }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); @@ -277,7 +287,7 @@ describe('ProctoredExamSettings', () => { it('Creates an alert when invalid proctoring escalation email is provided with proctoring disabled', async () => { await waitFor(() => { - screen.getByDisplayValue('proctortrack'); + screen.getByDisplayValue('LTI Provider'); }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } }); @@ -295,7 +305,7 @@ describe('ProctoredExamSettings', () => { it('Has no error when empty proctoring escalation email is provided with proctoring disabled', async () => { await waitFor(() => { - screen.getByDisplayValue('proctortrack'); + screen.getByDisplayValue('LTI Provider'); }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); @@ -318,7 +328,7 @@ describe('ProctoredExamSettings', () => { it(`Has no error when valid proctoring escalation email is provided with ${provider} selected`, async () => { await waitFor(() => { - screen.getByDisplayValue('proctortrack'); + screen.getByDisplayValue('LTI Provider'); }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo@bar.com' } }); @@ -339,9 +349,9 @@ describe('ProctoredExamSettings', () => { it(`Escalation email field hidden when proctoring backend is not ${provider}`, async () => { await waitFor(() => { - screen.getByDisplayValue('proctortrack'); + screen.getByDisplayValue('LTI Provider'); }); - const proctoringBackendSelect = screen.getByDisplayValue('proctortrack'); + const proctoringBackendSelect = screen.getByDisplayValue('LTI Provider'); const selectEscalationEmailElement = screen.getByTestId('escalationEmail'); expect(selectEscalationEmailElement.value).toEqual('test@example.com'); fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } }); @@ -350,13 +360,13 @@ describe('ProctoredExamSettings', () => { it(`Escalation email Field Show when proctoring backend is switched back to ${provider}`, async () => { await waitFor(() => { - screen.getByDisplayValue('proctortrack'); + screen.getByDisplayValue('LTI Provider'); }); - const proctoringBackendSelect = screen.getByDisplayValue('proctortrack'); + const proctoringBackendSelect = screen.getByDisplayValue('LTI Provider'); let selectEscalationEmailElement = screen.getByTestId('escalationEmail'); fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } }); expect(screen.queryByTestId('escalationEmail')).toBeNull(); - fireEvent.change(proctoringBackendSelect, { target: { value: 'proctortrack' } }); + fireEvent.change(proctoringBackendSelect, { target: { value: provider } }); expect(screen.queryByTestId('escalationEmail')).toBeDefined(); selectEscalationEmailElement = screen.getByTestId('escalationEmail'); expect(selectEscalationEmailElement.value).toEqual('test@example.com'); @@ -364,7 +374,7 @@ describe('ProctoredExamSettings', () => { it('Submits form when "Enter" key is hit in the escalation email field', async () => { await waitFor(() => { - screen.getByDisplayValue('proctortrack'); + screen.getByDisplayValue('LTI Provider'); }); const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com'); fireEvent.change(selectEscalationEmailElement, { target: { value: '' } }); @@ -384,7 +394,8 @@ describe('ProctoredExamSettings', () => { proctoring_escalation_email: 'test@example.com', create_zendesk_tickets: true, }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], + available_proctoring_providers: ['software_secure', 'mockproc'], + requires_escalation_email_providers: [], course_start_date: '2099-01-01T00:00:00Z', }; @@ -396,7 +407,8 @@ describe('ProctoredExamSettings', () => { proctoring_escalation_email: 'test@example.com', create_zendesk_tickets: true, }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], + available_proctoring_providers: ['software_secure', 'mockproc'], + requires_escalation_email_providers: [], course_start_date: '2013-01-01T00:00:00Z', }; @@ -409,7 +421,7 @@ describe('ProctoredExamSettings', () => { setupApp(isAdmin); mockCourseData(mockGetPastCourseData); await act(async () => render(intlWrapper())); - const providerOption = screen.getByTestId('proctortrack'); + const providerOption = screen.getByTestId('software_secure'); expect(providerOption.hasAttribute('disabled')).toEqual(true); }); @@ -418,7 +430,7 @@ describe('ProctoredExamSettings', () => { setupApp(isAdmin); mockCourseData(mockGetFutureCourseData); await act(async () => render(intlWrapper())); - const providerOption = screen.getByTestId('proctortrack'); + const providerOption = screen.getByTestId('software_secure'); expect(providerOption.hasAttribute('disabled')).toEqual(false); }); @@ -428,7 +440,7 @@ describe('ProctoredExamSettings', () => { setupApp(isAdmin, org); mockCourseData(mockGetFutureCourseData); await act(async () => render(intlWrapper())); - const providerOption = screen.getByTestId('proctortrack'); + const providerOption = screen.getByTestId('software_secure'); expect(providerOption.hasAttribute('disabled')).toEqual(false); }); @@ -437,7 +449,7 @@ describe('ProctoredExamSettings', () => { setupApp(isAdmin); mockCourseData(mockGetPastCourseData); await act(async () => render(intlWrapper())); - const providerOption = screen.getByTestId('proctortrack'); + const providerOption = screen.getByTestId('software_secure'); expect(providerOption.hasAttribute('disabled')).toEqual(false); }); @@ -446,14 +458,14 @@ describe('ProctoredExamSettings', () => { setupApp(isAdmin); mockCourseData(mockGetFutureCourseData); await act(async () => render(intlWrapper())); - const providerOption = screen.getByTestId('proctortrack'); + const providerOption = screen.getByTestId('software_secure'); expect(providerOption.hasAttribute('disabled')).toEqual(false); }); it('Does not include lti_external as a selectable option', async () => { const courseData = { ...mockGetFutureCourseData, - available_proctoring_providers: ['lti_external', 'proctortrack', 'mockproc'], + available_proctoring_providers: ['lti_external', 'mockproc'], }; mockCourseData(courseData); await act(async () => render(intlWrapper())); @@ -466,7 +478,7 @@ describe('ProctoredExamSettings', () => { it('Includes lti proctoring provider options when lti_external is allowed by studio', async () => { const courseData = { ...mockGetFutureCourseData, - available_proctoring_providers: ['lti_external', 'proctortrack', 'mockproc'], + available_proctoring_providers: ['lti_external', 'mockproc'], }; mockCourseData(courseData); await act(async () => render(intlWrapper())); @@ -507,7 +519,7 @@ describe('ProctoredExamSettings', () => { it('Selected LTI proctoring provider is shown on page load', async () => { const courseData = { ...mockGetFutureCourseData }; - courseData.available_proctoring_providers = ['lti_external', 'proctortrack', 'mockproc']; + courseData.available_proctoring_providers = ['lti_external', 'mockproc']; courseData.proctored_exam_settings.proctoring_provider = 'lti_external'; mockCourseData(courseData); axiosMock.onGet( @@ -605,15 +617,31 @@ describe('ProctoredExamSettings', () => { expect(submitButton).toHaveAttribute('disabled'); }); - it('Makes API call successfully with proctoring_escalation_email if proctortrack', async () => { + it('Makes API call successfully with proctoring_escalation_email if test_lti', async () => { + // Setup mock to include test_lti as available provider + axiosMock.onGet( + StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), + ).reply(200, { + proctored_exam_settings: { + enable_proctored_exams: true, + allow_proctoring_opt_out: false, + proctoring_provider: 'mockproc', + proctoring_escalation_email: 'test@example.com', + create_zendesk_tickets: true, + }, + available_proctoring_providers: ['software_secure', 'mockproc', 'lti_external'], + requires_escalation_email_providers: ['test_lti'], + course_start_date: '2070-01-01T00:00:00Z', + }); + await act(async () => render(intlWrapper())); - // Make a change to the provider to proctortrack and set the email + // Make a change to the provider to test_lti and set the email const selectElement = screen.getByDisplayValue('mockproc'); - fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); + fireEvent.change(selectElement, { target: { value: 'test_lti' } }); const escalationEmail = screen.getByTestId('escalationEmail'); expect(escalationEmail.value).toEqual('test@example.com'); - fireEvent.change(escalationEmail, { target: { value: 'proctortrack@example.com' } }); - expect(escalationEmail.value).toEqual('proctortrack@example.com'); + fireEvent.change(escalationEmail, { target: { value: 'test_lti@example.com' } }); + expect(escalationEmail.value).toEqual('test_lti@example.com'); const submitButton = screen.getByTestId('submissionButton'); fireEvent.click(submitButton); expect(axiosMock.history.post.length).toBe(1); @@ -621,11 +649,16 @@ describe('ProctoredExamSettings', () => { proctored_exam_settings: { enable_proctored_exams: true, allow_proctoring_opt_out: false, - proctoring_provider: 'proctortrack', - proctoring_escalation_email: 'proctortrack@example.com', - create_zendesk_tickets: false, + proctoring_provider: 'lti_external', + proctoring_escalation_email: 'test_lti@example.com', + create_zendesk_tickets: true, }, }); + expect(axiosMock.history.patch.length).toBe(1); + expect(JSON.parse(axiosMock.history.patch[0].data)).toEqual({ + provider: 'test_lti', + escalation_email: 'test_lti@example.com', + }); await waitFor(() => { const errorAlert = screen.getByTestId('saveSuccess'); @@ -636,10 +669,10 @@ describe('ProctoredExamSettings', () => { }); }); - it('Makes API call successfully without proctoring_escalation_email if not proctortrack', async () => { + it('Makes API call successfully without proctoring_escalation_email if not requiring escalation email', async () => { await act(async () => render(intlWrapper())); - // make sure we have not selected proctortrack as the proctoring provider + // make sure we have not selected a provider requiring escalation email expect(screen.getByDisplayValue('mockproc')).toBeDefined(); const submitButton = screen.getByTestId('submissionButton'); @@ -691,6 +724,7 @@ describe('ProctoredExamSettings', () => { enable_proctored_exams: true, allow_proctoring_opt_out: false, proctoring_provider: 'lti_external', + proctoring_escalation_email: 'test_lti@example.com', create_zendesk_tickets: true, }, }); @@ -745,7 +779,8 @@ describe('ProctoredExamSettings', () => { proctoring_escalation_email: 'test@example.com', create_zendesk_tickets: true, }, - available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'], + available_proctoring_providers: ['software_secure', 'mockproc'], + requires_escalation_email_providers: [], course_start_date: '2070-01-01T00:00:00Z', }); @@ -870,16 +905,15 @@ describe('ProctoredExamSettings', () => { await act(async () => render(intlWrapper())); // Make a change to the proctoring provider const selectElement = screen.getByDisplayValue('mockproc'); - fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); + fireEvent.change(selectElement, { target: { value: 'software_secure' } }); const submitButton = screen.getByTestId('submissionButton'); fireEvent.click(submitButton); expect(axiosMock.history.post.length).toBe(1); expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({ proctored_exam_settings: { enable_proctored_exams: true, - proctoring_provider: 'proctortrack', - proctoring_escalation_email: 'test@example.com', - create_zendesk_tickets: false, + proctoring_provider: 'software_secure', + create_zendesk_tickets: true, }, }); });