diff --git a/cms/djangoapps/contentstore/config/waffle.py b/cms/djangoapps/contentstore/config/waffle.py index 4682473a3c..6457e77b03 100644 --- a/cms/djangoapps/contentstore/config/waffle.py +++ b/cms/djangoapps/contentstore/config/waffle.py @@ -36,3 +36,9 @@ ENABLE_CHECKLISTS_QUALITY = CourseWaffleFlag( flag_name=u'enable_checklists_quality', flag_undefined_default=True ) + +SHOW_REVIEW_RULES_FLAG = CourseWaffleFlag( + waffle_namespace=waffle_flags(), + flag_name=u'show_review_rules', + flag_undefined_default=False +) diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 88d2a030c7..71d4cf44db 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -69,6 +69,7 @@ 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, does_backend_support_onboarding +from cms.djangoapps.contentstore.config.waffle import SHOW_REVIEW_RULES_FLAG __all__ = [ 'orphan_handler', 'xblock_handler', 'xblock_view_handler', 'xblock_outline_handler', 'xblock_container_handler' @@ -1218,7 +1219,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F xblock_info.update({ 'enable_proctored_exams': xblock.enable_proctored_exams, 'create_zendesk_tickets': xblock.create_zendesk_tickets, - 'enable_timed_exams': xblock.enable_timed_exams + 'enable_timed_exams': xblock.enable_timed_exams, }) elif xblock.category == 'sequential': rules_url = settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('online_proctoring_rules', "") @@ -1239,6 +1240,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F 'default_time_limit_minutes': xblock.default_time_limit_minutes, 'proctoring_exam_configuration_link': proctoring_exam_configuration_link, 'supports_onboarding': supports_onboarding, + 'show_review_rules': SHOW_REVIEW_RULES_FLAG.is_enabled(xblock.location.course_key), }) # Update with gating info 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 a3965bef17..af26914fbf 100644 --- a/cms/static/js/spec/views/pages/course_outline_spec.js +++ b/cms/static/js/spec/views/pages/course_outline_spec.js @@ -14,7 +14,7 @@ describe('CourseOutlinePage', function() { selectVisibilitySettings, selectAdvancedSettings, createMockCourseJSON, createMockSectionJSON, createMockSubsectionJSON, verifyTypePublishable, mockCourseJSON, mockEmptyCourseJSON, setSelfPaced, mockSingleSectionCourseJSON, createMockVerticalJSON, createMockIndexJSON, mockCourseEntranceExamJSON, - selectOnboardingExam, + selectOnboardingExam, createMockCourseJSONWithReviewRules,mockCourseJSONWithReviewRules, mockOutlinePage = readFixtures('templates/mock/mock-course-outline-page.underscore'), mockRerunNotification = readFixtures('templates/mock/mock-course-rerun-notification.underscore'); @@ -44,6 +44,33 @@ describe('CourseOutlinePage', function() { }, options, {child_info: {children: children}}); }; + createMockCourseJSONWithReviewRules = function(options, children) { + return $.extend(true, {}, { + id: 'mock-course', + display_name: 'Mock Course', + category: 'course', + enable_proctored_exams: true, + enable_timed_exams: true, + studio_url: '/course/slashes:MockCourse', + is_container: true, + has_changes: false, + published: true, + edited_on: 'Jul 02, 2014 at 20:56 UTC', + edited_by: 'MockUser', + has_explicit_staff_lock: false, + child_info: { + category: 'chapter', + display_name: 'Section', + children: [] + }, + user_partitions: [], + show_review_rules: true, + user_partition_info: {}, + highlights_enabled: true, + highlights_enabled_for_messaging: false + }, options, {child_info: {children: children}}); + }; + createMockSectionJSON = function(options, children) { return $.extend(true, {}, { id: 'mock-section', @@ -281,6 +308,13 @@ describe('CourseOutlinePage', function() { ]) ]) ]); + mockCourseJSONWithReviewRules = createMockCourseJSONWithReviewRules({}, [ + createMockSectionJSON({}, [ + createMockSubsectionJSON({}, [ + createMockVerticalJSON() + ]) + ]) + ]); mockEmptyCourseJSON = createMockCourseJSON(); mockSingleSectionCourseJSON = createMockCourseJSON({}, [ createMockSectionJSON() @@ -987,7 +1021,7 @@ describe('CourseOutlinePage', function() { var getDisplayNameWrapper, setEditModalValues, setContentVisibility, mockServerValuesJson, selectDisableSpecialExams, selectTimedExam, selectProctoredExam, selectPracticeExam, selectPrerequisite, selectLastPrerequisiteSubsection, checkOptionFieldVisibility, - defaultModalSettings, getMockNoPrereqOrExamsCourseJSON, expectShowCorrectness; + defaultModalSettings, modalSettingsWithExamReviewRules, getMockNoPrereqOrExamsCourseJSON, expectShowCorrectness; getDisplayNameWrapper = function() { return getItemHeaders('subsection').find('.wrapper-xblock-field'); @@ -1075,7 +1109,19 @@ describe('CourseOutlinePage', function() { due: null, is_practice_exam: false, is_time_limited: false, - exam_review_rules: '', + is_proctored_enabled: false, + default_time_limit_minutes: null, + is_onboarding_exam: false + } + }; + + modalSettingsWithExamReviewRules = { + graderType: 'notgraded', + isPrereq: false, + metadata: { + due: null, + is_practice_exam: false, + is_time_limited: false, is_proctored_enabled: false, default_time_limit_minutes: null, is_onboarding_exam: false @@ -1277,7 +1323,6 @@ describe('CourseOutlinePage', function() { visible_to_staff_only: null, start: '2014-07-09T00:00:00.000Z', due: '2014-07-10T00:00:00.000Z', - exam_review_rules: '', is_time_limited: true, is_practice_exam: false, is_proctored_enabled: false, @@ -1320,6 +1365,14 @@ describe('CourseOutlinePage', function() { expectShowCorrectness('never'); }); + it('review rules exists', function() { + createCourseOutlinePage(this, mockCourseJSONWithReviewRules, false); + outlinePage.$('.outline-subsection .configure-button').click(); + $('.wrapper-modal-window .action-save').click(); + AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/mock-subsection', modalSettingsWithExamReviewRules); + expect(requests[0].requestHeaders['X-HTTP-Method-Override']).toBe('PATCH'); + }); + it('can hide time limit and hide after due fields when the None radio box is selected', function() { createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); @@ -1363,6 +1416,7 @@ describe('CourseOutlinePage', function() { is_proctored_exam: true, default_time_limit_minutes: 150, supports_onboarding: true, + show_review_rules: true }, [ ]) ]) @@ -1452,7 +1506,26 @@ describe('CourseOutlinePage', function() { }); it('can select the Proctored exam option', function() { - createCourseOutlinePage(this, mockCourseJSON, false); + 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, + show_review_rules: true, + }, [ + ]) + ]) + ]); + createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); setEditModalValues('7/9/2014', '7/10/2014', 'Lab'); selectVisibilitySettings(); diff --git a/cms/templates/js/timed-examination-preference-editor.underscore b/cms/templates/js/timed-examination-preference-editor.underscore index 9683c71e43..6c2f480f87 100644 --- a/cms/templates/js/timed-examination-preference-editor.underscore +++ b/cms/templates/js/timed-examination-preference-editor.underscore @@ -47,30 +47,33 @@

<%- gettext('Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.') %>

-
-