diff --git a/src/course-home/data/__snapshots__/redux.test.js.snap b/src/course-home/data/__snapshots__/redux.test.js.snap
index 74e16997..65459211 100644
--- a/src/course-home/data/__snapshots__/redux.test.js.snap
+++ b/src/course-home/data/__snapshots__/redux.test.js.snap
@@ -12,6 +12,7 @@ Object {
"courseware": Object {
"courseId": null,
"courseStatus": "loading",
+ "proctoredExamsEnabledWaffleFlag": false,
"sequenceId": null,
"sequenceStatus": "loading",
"specialExamsEnabledWaffleFlag": false,
@@ -306,6 +307,7 @@ Object {
"courseware": Object {
"courseId": null,
"courseStatus": "loading",
+ "proctoredExamsEnabledWaffleFlag": false,
"sequenceId": null,
"sequenceStatus": "loading",
"specialExamsEnabledWaffleFlag": false,
@@ -481,6 +483,7 @@ Object {
"courseware": Object {
"courseId": null,
"courseStatus": "loading",
+ "proctoredExamsEnabledWaffleFlag": false,
"sequenceId": null,
"sequenceStatus": "loading",
"specialExamsEnabledWaffleFlag": false,
diff --git a/src/courseware/CoursewareContainer.jsx b/src/courseware/CoursewareContainer.jsx
index 902c5806..05982cc1 100644
--- a/src/courseware/CoursewareContainer.jsx
+++ b/src/courseware/CoursewareContainer.jsx
@@ -122,6 +122,7 @@ class CoursewareContainer extends Component {
courseStatus,
sequenceStatus,
specialExamsEnabledWaffleFlag,
+ proctoredExamsEnabledWaffleFlag,
sequence,
firstSequenceId,
unitViaSequenceId,
@@ -177,7 +178,9 @@ class CoursewareContainer extends Component {
// Check special exam redirect:
// /course/:courseId/:sequenceId(/:unitId) -> :legacyWebUrl
// because special exams are currently still served in the legacy LMS frontend.
- if (!specialExamsEnabledWaffleFlag) {
+ const shouldRedirectProctoredExams = specialExamsEnabledWaffleFlag && sequence.isProctored
+ && !proctoredExamsEnabledWaffleFlag;
+ if (!specialExamsEnabledWaffleFlag || shouldRedirectProctoredExams) {
checkSpecialExamRedirect(sequenceStatus, sequence);
}
@@ -327,6 +330,7 @@ const sequenceShape = PropTypes.shape({
unitIds: PropTypes.arrayOf(PropTypes.string).isRequired,
sectionId: PropTypes.string.isRequired,
isTimeLimited: PropTypes.bool,
+ isProctored: PropTypes.bool,
legacyWebUrl: PropTypes.string,
});
@@ -366,6 +370,7 @@ CoursewareContainer.propTypes = {
fetchCourse: PropTypes.func.isRequired,
fetchSequence: PropTypes.func.isRequired,
specialExamsEnabledWaffleFlag: PropTypes.bool.isRequired,
+ proctoredExamsEnabledWaffleFlag: PropTypes.bool.isRequired,
};
CoursewareContainer.defaultProps = {
@@ -470,6 +475,7 @@ const mapStateToProps = (state) => {
courseStatus,
sequenceStatus,
specialExamsEnabledWaffleFlag,
+ proctoredExamsEnabledWaffleFlag,
} = state.courseware;
return {
@@ -478,6 +484,7 @@ const mapStateToProps = (state) => {
courseStatus,
sequenceStatus,
specialExamsEnabledWaffleFlag,
+ proctoredExamsEnabledWaffleFlag,
course: currentCourseSelector(state),
sequence: currentSequenceSelector(state),
previousSequence: previousSequenceSelector(state),
diff --git a/src/courseware/course/sequence/Sequence.jsx b/src/courseware/course/sequence/Sequence.jsx
index 196982e9..0078cfb2 100644
--- a/src/courseware/course/sequence/Sequence.jsx
+++ b/src/courseware/course/sequence/Sequence.jsx
@@ -48,6 +48,7 @@ function Sequence({
const unit = useModel('units', unitId);
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
const specialExamsEnabledWaffleFlag = useSelector(state => state.courseware.specialExamsEnabledWaffleFlag);
+ const proctoredExamsEnabledWaffleFlag = useSelector(state => state.courseware.proctoredExamsEnabledWaffleFlag);
const shouldDisplaySidebarButton = useWindowSize().width < responsiveBreakpoints.small.minWidth;
const handleNext = () => {
@@ -145,12 +146,18 @@ function Sequence({
because we expect CoursewareContainer to be performing a redirect to the legacy experience while
we're waiting. That redirect may take a few seconds, so we show the spinner in the meantime.
*/
- if (sequenceStatus === 'loaded' && sequence.isTimeLimited && !specialExamsEnabledWaffleFlag) {
- return (
-
- );
+ if (sequenceStatus === 'loaded') {
+ const shouldRedirectSpecialExams = sequence.isTimeLimited && !specialExamsEnabledWaffleFlag;
+ const shouldRedirectProctoredExams = sequence.isProctored && specialExamsEnabledWaffleFlag
+ && !proctoredExamsEnabledWaffleFlag;
+
+ if (shouldRedirectSpecialExams || shouldRedirectProctoredExams) {
+ return (
+
+ );
+ }
}
const gated = sequence && sequence.gatedContent !== undefined && sequence.gatedContent.gated;
diff --git a/src/courseware/data/__factories__/courseMetadata.factory.js b/src/courseware/data/__factories__/courseMetadata.factory.js
index 72bc62ef..2c97f450 100644
--- a/src/courseware/data/__factories__/courseMetadata.factory.js
+++ b/src/courseware/data/__factories__/courseMetadata.factory.js
@@ -58,4 +58,5 @@ Factory.define('courseMetadata')
related_programs: null,
user_needs_integrity_signature: false,
is_mfe_special_exams_enabled: false,
+ is_mfe_proctored_exams_enabled: false,
});
diff --git a/src/courseware/data/api.js b/src/courseware/data/api.js
index c66b0ec3..d47d8c32 100644
--- a/src/courseware/data/api.js
+++ b/src/courseware/data/api.js
@@ -153,6 +153,7 @@ function normalizeMetadata(metadata) {
relatedPrograms: camelCaseObject(metadata.related_programs),
userNeedsIntegritySignature: metadata.user_needs_integrity_signature,
specialExamsEnabledWaffleFlag: metadata.is_mfe_special_exams_enabled,
+ proctoredExamsEnabledWaffleFlag: metadata.is_mfe_proctored_exams_enabled,
};
}
@@ -183,6 +184,7 @@ function normalizeSequenceMetadata(sequence) {
*/
gatedContent: camelCaseObject(sequence.gated_content),
isTimeLimited: sequence.is_time_limited,
+ isProctored: sequence.is_proctored,
// Position comes back from the server 1-indexed. Adjust here.
activeUnitIndex: sequence.position ? sequence.position - 1 : 0,
saveUnitPosition: sequence.save_position,
diff --git a/src/courseware/data/slice.js b/src/courseware/data/slice.js
index 2f3a6b6e..8a515741 100644
--- a/src/courseware/data/slice.js
+++ b/src/courseware/data/slice.js
@@ -14,11 +14,15 @@ const slice = createSlice({
sequenceStatus: 'loading',
sequenceId: null,
specialExamsEnabledWaffleFlag: false,
+ proctoredExamsEnabledWaffleFlag: false,
},
reducers: {
setsSpecialExamsEnabled: (state, { payload }) => {
state.specialExamsEnabledWaffleFlag = payload.specialExamsEnabledWaffleFlag;
},
+ setsProctoredExamsEnabled: (state, { payload }) => {
+ state.proctoredExamsEnabledWaffleFlag = payload.proctoredExamsEnabledWaffleFlag;
+ },
fetchCourseRequest: (state, { payload }) => {
state.courseId = payload.courseId;
state.courseStatus = LOADING;
@@ -52,6 +56,7 @@ const slice = createSlice({
export const {
setsSpecialExamsEnabled,
+ setsProctoredExamsEnabled,
fetchCourseRequest,
fetchCourseSuccess,
fetchCourseFailure,
diff --git a/src/courseware/data/thunks.js b/src/courseware/data/thunks.js
index bea60c89..4c860a88 100644
--- a/src/courseware/data/thunks.js
+++ b/src/courseware/data/thunks.js
@@ -12,6 +12,7 @@ import {
} from '../../generic/model-store';
import {
setsSpecialExamsEnabled,
+ setsProctoredExamsEnabled,
fetchCourseRequest,
fetchCourseSuccess,
fetchCourseFailure,
@@ -36,6 +37,9 @@ export function fetchCourse(courseId) {
dispatch(setsSpecialExamsEnabled({
specialExamsEnabledWaffleFlag: courseMetadataResult.value.specialExamsEnabledWaffleFlag,
}));
+ dispatch(setsProctoredExamsEnabled({
+ proctoredExamsEnabledWaffleFlag: courseMetadataResult.value.proctoredExamsEnabledWaffleFlag,
+ }));
}
if (courseBlocksResult.status === 'fulfilled') {