From af319f3c9a8a0fbc36522a7ee43971770e57aef8 Mon Sep 17 00:00:00 2001 From: Michael Roytman Date: Tue, 29 Jan 2019 14:36:55 -0500 Subject: [PATCH] Update version of edx-proctoring and update copy to refer to onboarding exams in Studio for proctoring backends that support onboarding exams. --- cms/djangoapps/contentstore/proctoring.py | 5 +- .../contentstore/tests/test_proctoring.py | 37 ++++- cms/djangoapps/contentstore/views/item.py | 5 +- .../contentstore/views/tests/test_item.py | 9 +- .../models/settings/course_metadata.py | 1 + .../spec/views/pages/course_outline_spec.js | 133 +++++++++++++++++- .../js/views/modals/course_outline_modals.js | 100 ++++++------- cms/templates/js/course-outline.underscore | 12 +- ...d-examination-preference-editor.underscore | 24 +++- common/lib/xmodule/xmodule/seq_module.py | 9 ++ requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/testing.txt | 2 +- 13 files changed, 266 insertions(+), 75 deletions(-) diff --git a/cms/djangoapps/contentstore/proctoring.py b/cms/djangoapps/contentstore/proctoring.py index d8dc235ec1..b5bc115b10 100644 --- a/cms/djangoapps/contentstore/proctoring.py +++ b/cms/djangoapps/contentstore/proctoring.py @@ -76,7 +76,8 @@ def register_special_exams(course_key): 'time_limit_mins': timed_exam.default_time_limit_minutes, 'due_date': timed_exam.due, 'is_proctored': timed_exam.is_proctored_exam, - 'is_practice_exam': timed_exam.is_practice_exam, + # backends that support onboarding exams will treat onboarding exams as practice + 'is_practice_exam': timed_exam.is_practice_exam or timed_exam.is_onboarding_exam, 'is_active': True, 'hide_after_due': timed_exam.hide_after_due, 'backend': course.proctoring_provider, @@ -106,7 +107,7 @@ def register_special_exams(course_key): } # only create/update exam policy for the proctored exams - if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam: + if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam and not timed_exam.is_onboarding_exam: try: update_review_policy(**exam_review_policy_metadata) except ProctoredExamReviewPolicyNotFoundException: diff --git a/cms/djangoapps/contentstore/tests/test_proctoring.py b/cms/djangoapps/contentstore/tests/test_proctoring.py index f273517c45..ef2ad670c4 100644 --- a/cms/djangoapps/contentstore/tests/test_proctoring.py +++ b/cms/djangoapps/contentstore/tests/test_proctoring.py @@ -61,10 +61,43 @@ class TestProctoredExams(ModuleStoreTestCase): self.assertEqual(exam['exam_name'], sequence.display_name) self.assertEqual(exam['time_limit_mins'], sequence.default_time_limit_minutes) self.assertEqual(exam['is_proctored'], sequence.is_proctored_exam) - self.assertEqual(exam['is_practice_exam'], sequence.is_practice_exam) + self.assertEqual(exam['is_practice_exam'], sequence.is_practice_exam or sequence.is_onboarding_exam) self.assertEqual(exam['is_active'], expected_active) self.assertEqual(exam['backend'], self.course.proctoring_provider) + @ddt.data( + (False, True), + (True, False), + ) + @ddt.unpack + def test_onboarding_exam_is_practice_exam(self, is_practice_exam, is_onboarding_exam): + """ + Check that an onboarding exam is treated as a practice exam when + communicating with the edx-proctoring subsystem. + """ + default_time_limit_minutes = 10 + is_proctored_exam = True + + chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section') + sequence = ItemFactory.create( + parent=chapter, + category='sequential', + display_name='Test Proctored Exam', + graded=True, + is_time_limited=True, + default_time_limit_minutes=default_time_limit_minutes, + is_proctored_exam=is_proctored_exam, + is_practice_exam=is_practice_exam, + due=datetime.now(UTC) + timedelta(minutes=default_time_limit_minutes + 1), + exam_review_rules="allow_use_of_paper", + hide_after_due=True, + is_onboarding_exam=is_onboarding_exam, + ) + + listen_for_course_publish(self, self.course.id) + + self._verify_exam_data(sequence, True) + @ddt.data( (True, False, True, False, False), (False, False, True, False, False), @@ -93,6 +126,7 @@ class TestProctoredExams(ModuleStoreTestCase): due=datetime.now(UTC) + timedelta(minutes=default_time_limit_minutes + 1), exam_review_rules="allow_use_of_paper", hide_after_due=hide_after_due, + is_onboarding_exam=False, ) listen_for_course_publish(self, self.course.id) @@ -125,6 +159,7 @@ class TestProctoredExams(ModuleStoreTestCase): default_time_limit_minutes=10, is_proctored_exam=True, hide_after_due=False, + is_onboarding_exam=False, ) listen_for_course_publish(self, self.course.id) diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 4e43966fa0..88d2a030c7 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -68,7 +68,7 @@ from xmodule.modulestore.inheritance import own_metadata from xmodule.services import ConfigurationService, SettingsService from xmodule.tabs import CourseTabList from xmodule.x_module import DEPRECATION_VSCOMPAT_EVENT, PREVIEW_VIEWS, STUDENT_VIEW, STUDIO_VIEW -from edx_proctoring.api import get_exam_configuration_dashboard_url +from edx_proctoring.api import get_exam_configuration_dashboard_url, does_backend_support_onboarding __all__ = [ 'orphan_handler', 'xblock_handler', 'xblock_view_handler', 'xblock_outline_handler', 'xblock_container_handler' @@ -1222,6 +1222,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F }) elif xblock.category == 'sequential': rules_url = settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('online_proctoring_rules', "") + supports_onboarding = does_backend_support_onboarding(course.proctoring_provider) proctoring_exam_configuration_link = None if xblock.is_proctored_exam: @@ -1232,10 +1233,12 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F 'is_proctored_exam': xblock.is_proctored_exam, 'online_proctoring_rules': rules_url, 'is_practice_exam': xblock.is_practice_exam, + 'is_onboarding_exam': xblock.is_onboarding_exam, 'is_time_limited': xblock.is_time_limited, 'exam_review_rules': xblock.exam_review_rules, 'default_time_limit_minutes': xblock.default_time_limit_minutes, 'proctoring_exam_configuration_link': proctoring_exam_configuration_link, + 'supports_onboarding': supports_onboarding, }) # Update with gating info diff --git a/cms/djangoapps/contentstore/views/tests/test_item.py b/cms/djangoapps/contentstore/views/tests/test_item.py index 70e9d91742..5bc8d82cac 100644 --- a/cms/djangoapps/contentstore/views/tests/test_item.py +++ b/cms/djangoapps/contentstore/views/tests/test_item.py @@ -2786,8 +2786,10 @@ class TestXBlockInfo(ItemTest): self.assertIsNone(xblock_info.get('child_info', None)) @patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True}) + @patch('contentstore.views.item.does_backend_support_onboarding') @patch('contentstore.views.item.get_exam_configuration_dashboard_url') - def test_proctored_exam_xblock_info(self, get_exam_configuration_dashboard_url_patch): + def test_proctored_exam_xblock_info(self, get_exam_configuration_dashboard_url_patch, + does_backend_support_onboarding_patch): self.course.enable_proctored_exams = True self.course.save() self.store.update_item(self.course, self.user.id) @@ -2805,11 +2807,12 @@ class TestXBlockInfo(ItemTest): parent_location=self.chapter.location, category='sequential', display_name="Test Lesson 1", user_id=self.user.id, is_proctored_exam=True, is_time_limited=True, - default_time_limit_minutes=100 + default_time_limit_minutes=100, is_onboarding_exam=False ) sequential = modulestore().get_item(sequential.location) get_exam_configuration_dashboard_url_patch.return_value = 'test_url' + does_backend_support_onboarding_patch.return_value = True xblock_info = create_xblock_info( sequential, include_child_info=True, @@ -2820,6 +2823,8 @@ class TestXBlockInfo(ItemTest): self.assertEqual(xblock_info['is_time_limited'], True) self.assertEqual(xblock_info['default_time_limit_minutes'], 100) self.assertEqual(xblock_info['proctoring_exam_configuration_link'], 'test_url') + self.assertEqual(xblock_info['supports_onboarding'], True) + self.assertEqual(xblock_info['is_onboarding_exam'], False) get_exam_configuration_dashboard_url_patch.assert_called_with(self.course.id, xblock_info['id']) diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index 79a78b14e3..5c77a702f6 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -65,6 +65,7 @@ class CourseMetadata(object): 'chrome', 'default_tab', 'highlights_enabled_for_messaging', + 'is_onboarding_exam', ] @classmethod diff --git a/cms/static/js/spec/views/pages/course_outline_spec.js b/cms/static/js/spec/views/pages/course_outline_spec.js index dc5fec49db..a3965bef17 100644 --- a/cms/static/js/spec/views/pages/course_outline_spec.js +++ b/cms/static/js/spec/views/pages/course_outline_spec.js @@ -14,6 +14,7 @@ describe('CourseOutlinePage', function() { selectVisibilitySettings, selectAdvancedSettings, createMockCourseJSON, createMockSectionJSON, createMockSubsectionJSON, verifyTypePublishable, mockCourseJSON, mockEmptyCourseJSON, setSelfPaced, mockSingleSectionCourseJSON, createMockVerticalJSON, createMockIndexJSON, mockCourseEntranceExamJSON, + selectOnboardingExam, mockOutlinePage = readFixtures('templates/mock/mock-course-outline-page.underscore'), mockRerunNotification = readFixtures('templates/mock/mock-course-rerun-notification.underscore'); @@ -1025,6 +1026,12 @@ describe('CourseOutlinePage', function() { $('.field-time-limit input').trigger('focusout'); }; + selectOnboardingExam = function(time_limit) { + $('input.onboarding_exam').prop('checked', true).trigger('change'); + $('.field-time-limit input').val(time_limit); + $('.field-time-limit input').trigger('focusout'); + }; + selectPrerequisite = function() { $('#is_prereq').prop('checked', true).trigger('change'); }; @@ -1070,7 +1077,8 @@ describe('CourseOutlinePage', function() { is_time_limited: false, exam_review_rules: '', is_proctored_enabled: false, - default_time_limit_minutes: null + default_time_limit_minutes: null, + is_onboarding_exam: false } }; @@ -1274,7 +1282,8 @@ describe('CourseOutlinePage', function() { is_practice_exam: false, is_proctored_enabled: false, default_time_limit_minutes: 150, - hide_after_due: true + hide_after_due: true, + is_onboarding_exam: false, } }); expect(requests[0].requestHeaders['X-HTTP-Method-Override']).toBe('PATCH'); @@ -1339,6 +1348,96 @@ describe('CourseOutlinePage', function() { $('.wrapper-modal-window .action-save').click(); }); + it('can select the onboarding exam when a course supports onboarding', function () { + var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [ + createMockSectionJSON({ + has_changes: true, + enable_proctored_exams: true, + enable_timed_exams: true + + }, [ + createMockSubsectionJSON({ + has_changes: true, + is_time_limited: true, + is_practice_exam: true, + is_proctored_exam: true, + default_time_limit_minutes: 150, + supports_onboarding: true, + }, [ + ]) + ]) + ]); + + createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); + outlinePage.$('.outline-subsection .configure-button').click(); + setEditModalValues('7/9/2014', '7/10/2014', 'Lab'); + selectVisibilitySettings(); + setContentVisibility('staff_only'); + selectAdvancedSettings(); + selectOnboardingExam('00:30'); + + // time limit should be visible, review rules should be hidden + checkOptionFieldVisibility(true, false); + + $('.wrapper-modal-window .action-save').click(); + }); + + it('does not show practice exam option when course supports onboarding', function() { + var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [ + createMockSectionJSON({ + has_changes: true, + enable_proctored_exams: true, + enable_timed_exams: true + + }, [ + createMockSubsectionJSON({ + has_changes: true, + is_time_limited: true, + is_practice_exam: true, + is_proctored_exam: true, + default_time_limit_minutes: 150, + supports_onboarding: true, + }, [ + ]) + ]) + ]); + + createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); + outlinePage.$('.outline-subsection .configure-button').click(); + selectAdvancedSettings(); + expect($('input.practice_exam')).not.toExist(); + expect($('input.onboarding_exam')).toExist(); + expect($('.field-time-limit input').val()).toBe('02:30'); + }); + + it('does not show onboarding exam option when course does not support onboarding', function() { + var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [ + createMockSectionJSON({ + has_changes: true, + enable_proctored_exams: true, + enable_timed_exams: true + + }, [ + createMockSubsectionJSON({ + has_changes: true, + is_time_limited: true, + is_practice_exam: true, + is_proctored_exam: true, + default_time_limit_minutes: 150, + supports_onboarding: false, + }, [ + ]) + ]) + ]); + + createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); + outlinePage.$('.outline-subsection .configure-button').click(); + selectAdvancedSettings(); + expect($('input.practice_exam')).toExist(); + expect($('input.onboarding_exam')).not.toExist(); + expect($('.field-time-limit input').val()).toBe('02:30'); + }); + it('can select the timed exam', function() { createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); @@ -1525,6 +1624,36 @@ describe('CourseOutlinePage', function() { expect($('.field-time-limit input').val()).toBe('02:30'); }); + it('can show a saved onboarding exam correctly', function() { + var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [ + createMockSectionJSON({ + has_changes: true, + enable_proctored_exams: true, + enable_timed_exams: true + + }, [ + createMockSubsectionJSON({ + has_changes: true, + is_time_limited: true, + is_practice_exam: false, + is_proctored_exam: true, + default_time_limit_minutes: 150, + supports_onboarding: true, + is_onboarding_exam: true + }, [ + ]) + ]) + ]); + createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); + outlinePage.$('.outline-subsection .configure-button').click(); + selectAdvancedSettings(); + expect($('input.timed_exam').is(':checked')).toBe(false); + expect($('input.proctored_exam').is(':checked')).toBe(false); + expect($('input.no_special_exam').is(':checked')).toBe(false); + expect($('input.onboarding_exam').is(':checked')).toBe(true); + expect($('.field-time-limit input').val()).toBe('02:30'); + }); + it('does not show proctored settings if proctored exams not enabled', function() { var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [ createMockSectionJSON({ diff --git a/cms/static/js/views/modals/course_outline_modals.js b/cms/static/js/views/modals/course_outline_modals.js index 6bff80e977..1d95a8e294 100644 --- a/cms/static/js/views/modals/course_outline_modals.js +++ b/cms/static/js/views/modals/course_outline_modals.js @@ -70,8 +70,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', }, save: function(event) { + var requestData; + event.preventDefault(); - var requestData = this.getRequestData(); + requestData = this.getRequestData(); if (!_.isEqual(requestData, {metadata: {}})) { XBlockViewUtils.updateXBlockFields(this.model, requestData, { success: this.options.onSave @@ -122,10 +124,11 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', }, initializeEditors: function() { + var tabsTemplate; var tabs = this.options.tabs; if (tabs && tabs.length > 0) { if (tabs.length > 1) { - var tabsTemplate = this.loadTemplate('settings-modal-tabs'); + tabsTemplate = this.loadTemplate('settings-modal-tabs'); HtmlUtils.setHtml(this.$('.modal-section'), HtmlUtils.HTML(tabsTemplate({tabs: tabs}))); _.each(this.options.tabs, function(tab) { this.options.editors.push.apply( @@ -411,9 +414,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', className: 'edit-settings-timed-examination', events: { 'change input.no_special_exam': 'notTimedExam', - 'change input.timed_exam': 'setTimedExam', - 'change input.practice_exam': 'setPracticeExam', + 'change input.timed_exam': 'setSpecialExamWithoutRules', + 'change input.practice_exam': 'setSpecialExamWithoutRules', 'change input.proctored_exam': 'setProctoredExam', + 'change input.onboarding_exam': 'setSpecialExamWithoutRules', 'focusout .field-time-limit input': 'timeLimitFocusout' }, notTimedExam: function(event) { @@ -433,11 +437,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', this.$('.field-exam-review-rules').hide(); } }, - setTimedExam: function(event) { - event.preventDefault(); - this.selectSpecialExam(false); - }, - setPracticeExam: function(event) { + setSpecialExamWithoutRules: function(event) { event.preventDefault(); this.selectSpecialExam(false); }, @@ -446,8 +446,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', this.selectSpecialExam(true); }, timeLimitFocusout: function(event) { + var selectedTimeLimit; + event.preventDefault(); - var selectedTimeLimit = $(event.currentTarget).val(); + selectedTimeLimit = $(event.currentTarget).val(); if (!this.isValidTimeLimit(selectedTimeLimit)) { $(event.currentTarget).val('00:30'); } @@ -462,24 +464,26 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', }); this.setExamType(this.model.get('is_time_limited'), this.model.get('is_proctored_exam'), - this.model.get('is_practice_exam')); + this.model.get('is_practice_exam'), this.model.get('is_onboarding_exam')); this.setExamTime(this.model.get('default_time_limit_minutes')); this.setReviewRules(this.model.get('exam_review_rules')); }, - setExamType: function(is_time_limited, is_proctored_exam, is_practice_exam) { + setExamType: function(isTimeLimited, isProctoredExam, isPracticeExam, isOnboardingExam) { this.$('.field-time-limit').hide(); this.$('.field-exam-review-rules').hide(); - if (!is_time_limited) { + if (!isTimeLimited) { this.$('input.no_special_exam').prop('checked', true); return; } this.$('.field-time-limit').show(); - if (this.options.enable_proctored_exams && is_proctored_exam) { - if (is_practice_exam) { + if (this.options.enable_proctored_exams && isProctoredExam) { + if (isOnboardingExam) { + this.$('input.onboarding_exam').prop('checked', true); + } else if (isPracticeExam) { this.$('input.practice_exam').prop('checked', true); } else { this.$('input.proctored_exam').prop('checked', true); @@ -499,9 +503,9 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', setReviewRules: function(value) { this.$('.field-exam-review-rules textarea').val(value); }, - isValidTimeLimit: function(time_limit) { + isValidTimeLimit: function(timeLimit) { var pattern = new RegExp('^\\d{1,2}:[0-5][0-9]$'); - return pattern.test(time_limit) && time_limit !== '00:00'; + return pattern.test(timeLimit) && timeLimit !== '00:00'; }, getExamTimeLimit: function() { return this.$('.field-time-limit input').val(); @@ -513,48 +517,32 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', actualMinutesStr = '00'.substring(0, 2 - actualMinutesStr.length) + actualMinutesStr; return hoursStr + ':' + actualMinutesStr; }, - convertTimeLimitToMinutes: function(time_limit) { - var time = time_limit.split(':'); - var total_time = (parseInt(time[0]) * 60) + parseInt(time[1]); - return total_time; + convertTimeLimitToMinutes: function(timeLimit) { + var time = timeLimit.split(':'); + var totalTime = (parseInt(time[0], 10) * 60) + parseInt(time[1], 10); + return totalTime; }, getRequestData: function() { - var is_time_limited; - var is_practice_exam; - var is_proctored_exam; - var time_limit = this.getExamTimeLimit(); - var exam_review_rules = this.$('.field-exam-review-rules textarea').val(); - - if (this.$('input.no_special_exam').is(':checked')) { - is_time_limited = false; - is_practice_exam = false; - is_proctored_exam = false; - } else if (this.$('input.timed_exam').is(':checked')) { - is_time_limited = true; - is_practice_exam = false; - is_proctored_exam = false; - } else if (this.$('input.proctored_exam').is(':checked')) { - is_time_limited = true; - is_practice_exam = false; - is_proctored_exam = true; - } else if (this.$('input.practice_exam').is(':checked')) { - is_time_limited = true; - is_practice_exam = true; - is_proctored_exam = true; - } + var isNoSpecialExamChecked = this.$('input.no_special_exam').is(':checked'); + var isProctoredExamChecked = this.$('input.proctored_exam').is(':checked'); + var isPracticeExamChecked = this.$('input.practice_exam').is(':checked'); + var isOnboardingExamChecked = this.$('input.onboarding_exam').is(':checked'); + var timeLimit = this.getExamTimeLimit(); + var examReviewRules = this.$('.field-exam-review-rules textarea').val(); return { metadata: { - is_practice_exam: is_practice_exam, - is_time_limited: is_time_limited, - exam_review_rules: exam_review_rules, + is_practice_exam: isPracticeExamChecked, + is_time_limited: !isNoSpecialExamChecked, + exam_review_rules: examReviewRules, // We have to use the legacy field name // as the Ajax handler directly populates // the xBlocks fields. We will have to // update this call site when we migrate // seq_module.py to use 'is_proctored_exam' - is_proctored_enabled: is_proctored_exam, - default_time_limit_minutes: this.convertTimeLimitToMinutes(time_limit) + is_proctored_enabled: isProctoredExamChecked || isPracticeExamChecked || isOnboardingExamChecked, + default_time_limit_minutes: this.convertTimeLimitToMinutes(timeLimit), + is_onboarding_exam: isOnboardingExamChecked } }; } @@ -569,10 +557,12 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', 'keyup #prereq_min_score': 'validateScoreAndCompletion' }, afterRender: function() { + var prereq, prereqMinScore, prereqMinCompletion; + AbstractEditor.prototype.afterRender.call(this); - var prereq = this.model.get('prereq') || ''; - var prereqMinScore = this.model.get('prereq_min_score') || '100'; - var prereqMinCompletion = this.model.get('prereq_min_completion') || '100'; + prereq = this.model.get('prereq') || ''; + prereqMinScore = this.model.get('prereq_min_score') || '100'; + prereqMinCompletion = this.model.get('prereq_min_completion') || '100'; this.$('#is_prereq').prop('checked', this.model.get('is_prereq')); this.$('#prereq option[value="' + prereq + '"]').prop('selected', true); this.$('#prereq_min_score').val(prereqMinScore); @@ -853,9 +843,9 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', }, toggleUnlockWarning: function() { + var display; var warning = this.$('.staff-lock .tip-warning'); if (warning) { - var display; if (this.currentVisibility() !== 'staff_only') { display = 'block'; } else { @@ -868,8 +858,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', }, getRequestData: function() { + var metadata; + if (this.hasChanges()) { - var metadata = {}; + metadata = {}; if (this.currentVisibility() === 'staff_only') { metadata.visible_to_staff_only = true; metadata.hide_after_due = null; diff --git a/cms/templates/js/course-outline.underscore b/cms/templates/js/course-outline.underscore index b201328c9a..20c3149ddb 100644 --- a/cms/templates/js/course-outline.underscore +++ b/cms/templates/js/course-outline.underscore @@ -84,15 +84,19 @@ if (xblockInfo.get('graded')) { var is_proctored_exam = xblockInfo.get('is_proctored_exam'); var is_practice_exam = xblockInfo.get('is_practice_exam'); +var is_onboarding_exam = xblockInfo.get('is_onboarding_exam'); +var exam_value; if (is_proctored_exam) { - if (is_practice_exam) { - var exam_value = gettext('Practice proctored Exam'); + if (is_onboarding_exam) { + exam_value = gettext('Onboarding Exam'); + } else if (is_practice_exam) { + exam_value = gettext('Practice proctored Exam'); } else { - var exam_value = gettext('Proctored Exam'); + exam_value = gettext('Proctored Exam'); } } else { - var exam_value = gettext('Timed Exam'); + exam_value = gettext('Timed Exam'); } %> <% if (parentInfo) { %> diff --git a/cms/templates/js/timed-examination-preference-editor.underscore b/cms/templates/js/timed-examination-preference-editor.underscore index 8853fce368..92f0838de6 100644 --- a/cms/templates/js/timed-examination-preference-editor.underscore +++ b/cms/templates/js/timed-examination-preference-editor.underscore @@ -19,12 +19,23 @@ <%- gettext('Proctored') %>

<%- gettext('Proctored exams are timed and they record video of each learner taking the exam. The videos are then reviewed to ensure that learners follow all examination rules.') %>

- -

<%- gettext("Use a practice proctored exam to introduce learners to the proctoring tools and processes. Results of a practice exam do not affect a learner's grade.") %>

+ + <% var supports_onboarding = xblockInfo.get('supports_onboarding'); %> + <% if (supports_onboarding) { %> + +

<%- gettext("Use Onboarding to introduce learners to proctoring, verify their identity, and create an onboarding profile. Learners must complete the onboarding profile step prior to taking a proctored exam. Profile reviews take 2+ business days.") %>

+ <% } else { %> + +

<%- gettext("Use a practice proctored exam to introduce learners to the proctoring tools and processes. Results of a practice exam do not affect a learner's grade.") %>

+ <% } %> <% } %>
@@ -45,6 +56,7 @@ <% var online_proctoring_rules = xblockInfo.get('online_proctoring_rules'); %>

<% if (online_proctoring_rules) { %> + % // xss-lint: disable=underscore-not-escaped %> <%= edx.HtmlUtils.interpolateHtml( gettext('Specify any rules or rule exceptions that the proctoring review team should enforce when reviewing the videos. For example, you could specify that calculators are allowed. These specified rules are visible to learners before the learners start the exam, along with the {linkStart}general proctored exam rules{linkEnd}.'), { diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index cf0fa057ef..553d74953f 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -125,6 +125,15 @@ class ProctoringFields(object): scope=Scope.settings, ) + is_onboarding_exam = Boolean( + display_name=_("Is Onboarding Exam"), + help=_( + "This setting indicates whether this exam is an onboarding exam." + ), + default=False, + scope=Scope.settings, + ) + def _get_course(self): """ Return course by course id. diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index ad4e914911..dd90ec57fd 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -122,7 +122,7 @@ edx-oauth2-provider==1.2.2 edx-opaque-keys[django]==0.4.4 edx-organizations==1.0.1 edx-proctoring-proctortrack==1.0.1 -edx-proctoring==1.5.8 +edx-proctoring==1.5.10 edx-rest-api-client==1.9.2 edx-search==1.2.1 edx-submissions==2.1.1 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 26659fe261..8e9cc33490 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -143,7 +143,7 @@ edx-oauth2-provider==1.2.2 edx-opaque-keys[django]==0.4.4 edx-organizations==1.0.1 edx-proctoring-proctortrack==1.0.1 -edx-proctoring==1.5.8 +edx-proctoring==1.5.10 edx-rest-api-client==1.9.2 edx-search==1.2.1 edx-sphinx-theme==1.4.0 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 3054054343..67c169edab 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -138,7 +138,7 @@ edx-oauth2-provider==1.2.2 edx-opaque-keys[django]==0.4.4 edx-organizations==1.0.1 edx-proctoring-proctortrack==1.0.1 -edx-proctoring==1.5.8 +edx-proctoring==1.5.10 edx-rest-api-client==1.9.2 edx-search==1.2.1 edx-submissions==2.1.1