diff --git a/.env b/.env
index e54ac766d..8bbb8c16d 100644
--- a/.env
+++ b/.env
@@ -5,6 +5,7 @@ CREDENTIALS_BASE_URL=''
CSRF_TOKEN_API_PATH=''
DISCOVERY_API_BASE_URL=''
ECOMMERCE_BASE_URL=''
+EXAMS_BASE_URL=''
FAVICON_URL=''
LANGUAGE_PREFERENCE_COOKIE_NAME=''
LMS_BASE_URL=''
diff --git a/.env.development b/.env.development
index 06d577997..3bfeb2543 100644
--- a/.env.development
+++ b/.env.development
@@ -4,6 +4,7 @@ BASE_URL='localhost:2001'
CREDENTIALS_BASE_URL='http://localhost:18150'
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DISCOVERY_API_BASE_URL=
+EXAMS_BASE_URL=
ECOMMERCE_BASE_URL='http://localhost:18130'
FAVICON_URL='https://edx-cdn.org/v3/default/favicon.ico'
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
diff --git a/.env.test b/.env.test
index 68bcb3840..91cf00cfe 100644
--- a/.env.test
+++ b/.env.test
@@ -4,6 +4,7 @@ CREDENTIALS_BASE_URL='http://localhost:18150'
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DISCOVERY_API_BASE_URL='http://localhost:18381'
ECOMMERCE_BASE_URL='http://localhost:18130'
+EXAMS_BASE_URL=
FAVICON_URL='https://edx-cdn.org/v3/default/favicon.ico'
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
LMS_BASE_URL='http://localhost:18000'
diff --git a/src/data/services/ExamsApiService.js b/src/data/services/ExamsApiService.js
new file mode 100644
index 000000000..10a6a7526
--- /dev/null
+++ b/src/data/services/ExamsApiService.js
@@ -0,0 +1,34 @@
+import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
+import { getConfig } from '@edx/frontend-platform';
+
+class ExamsApiService {
+ static isAvailable() {
+ return !!this.getExamsBaseUrl();
+ }
+
+ static getExamsBaseUrl() {
+ return getConfig().EXAMS_BASE_URL;
+ }
+
+ static getExamConfigurationUrl(courseId) {
+ return `${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${courseId}`;
+ }
+
+ static getAvailableProviders() {
+ const apiClient = getAuthenticatedHttpClient();
+ const providersUrl = `${ExamsApiService.getExamsBaseUrl()}/api/v1/providers`;
+ return apiClient.get(providersUrl);
+ }
+
+ static getCourseExamConfiguration(courseId) {
+ const apiClient = getAuthenticatedHttpClient();
+ return apiClient.get(this.getExamConfigurationUrl(courseId));
+ }
+
+ static saveCourseExamConfiguration(courseId, dataToSave) {
+ const apiClient = getAuthenticatedHttpClient();
+ return apiClient.patch(this.getExamConfigurationUrl(courseId), dataToSave);
+ }
+}
+
+export default ExamsApiService;
diff --git a/src/index.jsx b/src/index.jsx
index 643689a6f..41153d52e 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -47,6 +47,7 @@ initialize({
SUPPORT_URL: process.env.SUPPORT_URL || null,
SUPPORT_EMAIL: process.env.SUPPORT_EMAIL || null,
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
+ EXAMS_BASE_URL: process.env.EXAMS_BASE_URL || null,
CALCULATOR_HELP_URL: process.env.CALCULATOR_HELP_URL || null,
ENABLE_PROGRESS_GRAPH_SETTINGS: process.env.ENABLE_PROGRESS_GRAPH_SETTINGS || 'false',
ENABLE_TEAM_TYPE_SETTING: process.env.ENABLE_TEAM_TYPE_SETTING === 'true',
diff --git a/src/proctored-exam-settings/ProctoredExamSettings.jsx b/src/proctored-exam-settings/ProctoredExamSettings.jsx
index 9a2790c47..3f8bab35d 100644
--- a/src/proctored-exam-settings/ProctoredExamSettings.jsx
+++ b/src/proctored-exam-settings/ProctoredExamSettings.jsx
@@ -15,6 +15,7 @@ import {
import { getConfig } from '@edx/frontend-platform';
import messages from './ProctoredExamSettings.messages';
+import ExamsApiService from '../data/services/ExamsApiService';
import StudioApiService from '../data/services/StudioApiService';
import Loading from '../generic/Loading';
import ConnectionErrorAlert from '../generic/ConnectionErrorAlert';
@@ -33,8 +34,10 @@ function ProctoredExamSettings({ courseId, intl }) {
const [loadingPermissionError, setLoadingPermissionError] = useState(false);
const [enableProctoredExams, setEnableProctoredExams] = useState(true);
const [allowOptingOut, setAllowOptingOut] = useState(false);
+ const [allowLtiProviders, setAllowLtiProviders] = useState(false);
const [proctoringProvider, setProctoringProvider] = useState('');
const [availableProctoringProviders, setAvailableProctoringProviders] = useState([]);
+ const [ltiProctoringProviders, setLtiProctoringProviders] = useState([]);
const [proctortrackEscalationEmail, setProctortrackEscalationEmail] = useState('');
const [createZendeskTickets, setCreateZendeskTickets] = useState(false);
const [courseStartDate, setCourseStartDate] = useState('');
@@ -89,24 +92,41 @@ function ProctoredExamSettings({ courseId, intl }) {
}
}
+ function isLtiProvider(provider) {
+ return ltiProctoringProviders.some(p => p.name === provider);
+ }
+
function postSettingsBackToServer() {
- const dataToPostBack = {
+ const providerIsLti = isLtiProvider(proctoringProvider);
+ const studioDataToPostBack = {
proctored_exam_settings: {
enable_proctored_exams: enableProctoredExams,
- proctoring_provider: proctoringProvider,
+ // lti providers are managed outside edx-platform, lti_external indicates this
+ proctoring_provider: providerIsLti ? 'lti_external' : proctoringProvider,
create_zendesk_tickets: createZendeskTickets,
},
};
if (isEdxStaff) {
- dataToPostBack.proctored_exam_settings.allow_proctoring_opt_out = allowOptingOut;
+ studioDataToPostBack.proctored_exam_settings.allow_proctoring_opt_out = allowOptingOut;
}
if (proctoringProvider === 'proctortrack') {
- dataToPostBack.proctored_exam_settings.proctoring_escalation_email = proctortrackEscalationEmail === '' ? null : proctortrackEscalationEmail;
+ studioDataToPostBack.proctored_exam_settings.proctoring_escalation_email = proctortrackEscalationEmail === '' ? null : proctortrackEscalationEmail;
}
setSubmissionInProgress(true);
- StudioApiService.saveProctoredExamSettingsData(courseId, dataToPostBack).then(() => {
+
+ // only save back to exam service if necessary
+ const saveOperations = [StudioApiService.saveProctoredExamSettingsData(courseId, studioDataToPostBack)];
+ if (allowLtiProviders && ExamsApiService.isAvailable()) {
+ saveOperations.push(
+ ExamsApiService.saveCourseExamConfiguration(
+ courseId, { provider: providerIsLti ? proctoringProvider : null },
+ ),
+ );
+ }
+ Promise.all(saveOperations)
+ .then(() => {
setSaveSuccess(true);
setSaveError(false);
setSubmissionInProgress(false);
@@ -172,6 +192,11 @@ function ProctoredExamSettings({ courseId, intl }) {
return markDisabled;
}
+ function getProviderDisplayLabel(provider) {
+ // if a display label exists for this provider return it
+ return ltiProctoringProviders.find(p => p.name === provider)?.verbose_name || provider;
+ }
+
function getProctoringProviderOptions(providers) {
return providers.map(provider => (
));
}
@@ -338,7 +363,7 @@ function ProctoredExamSettings({ courseId, intl }) {
)}
{/* CREATE ZENDESK TICKETS */}
- { isEdxStaff && enableProctoredExams && (
+ { isEdxStaff && enableProctoredExams && !isLtiProvider(proctoringProvider) && (