diff --git a/common/test/acceptance/pages/lms/instructor_dashboard.py b/common/test/acceptance/pages/lms/instructor_dashboard.py index 8a7b4dcf79..9f4b1d3527 100644 --- a/common/test/acceptance/pages/lms/instructor_dashboard.py +++ b/common/test/acceptance/pages/lms/instructor_dashboard.py @@ -28,6 +28,15 @@ class InstructorDashboardPage(CoursePage): membership_section.wait_for_page() return membership_section + def select_cohort_management(self): + """ + Selects the cohort management tab and returns the CohortManagementSection + """ + self.q(css='a[data-section=cohort_management]').first.click() + cohort_management_section = CohortManagementSection(self.browser) + cohort_management_section.wait_for_page() + return cohort_management_section + def select_data_download(self): """ Selects the data download tab and returns a DataDownloadPage. @@ -84,16 +93,10 @@ class MembershipPage(PageObject): """ return MembershipPageAutoEnrollSection(self.browser) - def select_cohort_management_section(self): - """ - Returns the MembershipPageCohortManagementSection page object. - """ - return MembershipPageCohortManagementSection(self.browser) - -class MembershipPageCohortManagementSection(PageObject): +class CohortManagementSection(PageObject): """ - The cohort management subsection of the Membership section of the Instructor dashboard. + The Cohort Management section of the Instructor dashboard. """ url = None csv_browse_button_selector_css = '.csv-upload #file-upload-form-file' @@ -104,13 +107,13 @@ class MembershipPageCohortManagementSection(PageObject): assignment_type_buttons_css = '.cohort-management-assignment-type-settings input' def is_browser_on_page(self): - return self.q(css='.cohort-management.membership-section').present + return self.q(css='.cohort-management').present def _bounded_selector(self, selector): """ Return `selector`, but limited to the cohort management context. """ - return '.cohort-management.membership-section {}'.format(selector) + return '.cohort-management {}'.format(selector) def _get_cohort_options(self): """ @@ -158,10 +161,10 @@ class MembershipPageCohortManagementSection(PageObject): Return assignment settings disabled message in case of default cohort. """ query = self.q(css=self._bounded_selector('.copy-error')) - if query.present: + if query.visible: return query.text[0] - else: - return '' + + return '' @property def cohort_name_in_header(self): @@ -232,7 +235,11 @@ class MembershipPageCohortManagementSection(PageObject): Adds a new manual cohort with the specified name. If a content group should also be associated, the name of the content group should be specified. """ - create_buttons = self.q(css=self._bounded_selector(".action-create")) + add_cohort_selector = self._bounded_selector(".action-create") + + # We need to wait because sometime add cohort button is not in a state to be clickable. + self.wait_for_element_presence(add_cohort_selector, 'Add Cohort button is present.') + create_buttons = self.q(css=add_cohort_selector) # There are 2 create buttons on the page. The second one is only present when no cohort yet exists # (in which case the first is not visible). Click on the last present create button. create_buttons.results[len(create_buttons.results) - 1].click() @@ -444,6 +451,28 @@ class MembershipPageCohortManagementSection(PageObject): file_input.send_keys(path) self.q(css=self._bounded_selector(self.csv_upload_button_selector_css)).first.click() + @property + def is_cohorted(self): + """ + Returns the state of `Enable Cohorts` checkbox state. + """ + return self.q(css=self._bounded_selector('.cohorts-state')).selected + + @is_cohorted.setter + def is_cohorted(self, state): + """ + Check/Uncheck the `Enable Cohorts` checkbox state. + """ + if state != self.is_cohorted: + self.q(css=self._bounded_selector('.cohorts-state')).first.click() + + def cohort_management_controls_visible(self): + """ + Return the visibility status of cohort management controls(cohort selector section etc). + """ + return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and + self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible) + class MembershipPageAutoEnrollSection(PageObject): """ diff --git a/common/test/acceptance/tests/discussion/test_cohort_management.py b/common/test/acceptance/tests/discussion/test_cohort_management.py index 31e4db463c..7ec69497b9 100644 --- a/common/test/acceptance/tests/discussion/test_cohort_management.py +++ b/common/test/acceptance/tests/discussion/test_cohort_management.py @@ -63,8 +63,7 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin # go to the membership page on the instructor dashboard self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id) self.instructor_dashboard_page.visit() - membership_page = self.instructor_dashboard_page.select_membership() - self.cohort_management_page = membership_page.select_cohort_management_section() + self.cohort_management_page = self.instructor_dashboard_page.select_cohort_management() def verify_cohort_description(self, cohort_name, expected_description): """ @@ -441,9 +440,31 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin self.assertTrue(self.cohort_management_page.is_assignment_settings_disabled) - message = "There must be one cohort to which students can be randomly assigned." + message = "There must be one cohort to which students can automatically be assigned." self.assertEqual(message, self.cohort_management_page.assignment_settings_message) + def test_cohort_enable_disable(self): + """ + Scenario: Cohort Enable/Disable checkbox related functionality is working as intended. + + Given I have a cohorted course with a user. + And I can see the `Enable Cohorts` checkbox is checked. + And cohort management controls are visible. + When I uncheck the `Enable Cohorts` checkbox. + Then I cohort management controls are not visible. + And When I reload the page. + Then I can see the `Enable Cohorts` checkbox is unchecked. + And cohort management controls are not visible. + """ + self.assertTrue(self.cohort_management_page.is_cohorted) + self.assertTrue(self.cohort_management_page.cohort_management_controls_visible()) + self.cohort_management_page.is_cohorted = False + self.assertFalse(self.cohort_management_page.cohort_management_controls_visible()) + self.browser.refresh() + self.cohort_management_page.wait_for_page() + self.assertFalse(self.cohort_management_page.is_cohorted) + self.assertFalse(self.cohort_management_page.cohort_management_controls_visible()) + def test_link_to_data_download(self): """ Scenario: a link is present from the cohort configuration in @@ -656,8 +677,7 @@ class CohortContentGroupAssociationTest(UniqueCourseTest, CohortTestMixin): # go to the membership page on the instructor dashboard self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id) self.instructor_dashboard_page.visit() - membership_page = self.instructor_dashboard_page.select_membership() - self.cohort_management_page = membership_page.select_cohort_management_section() + self.cohort_management_page = self.instructor_dashboard_page.select_cohort_management() def test_no_content_group_linked(self): """ diff --git a/common/test/acceptance/tests/test_cohorted_courseware.py b/common/test/acceptance/tests/test_cohorted_courseware.py index 00f42cd14e..cd1df402b1 100644 --- a/common/test/acceptance/tests/test_cohorted_courseware.py +++ b/common/test/acceptance/tests/test_cohorted_courseware.py @@ -154,8 +154,7 @@ class EndToEndCohortedCoursewareTest(ContainerBase): """ instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id) instructor_dashboard_page.visit() - membership_page = instructor_dashboard_page.select_membership() - cohort_management_page = membership_page.select_cohort_management_section() + cohort_management_page = instructor_dashboard_page.select_cohort_management() def add_cohort_with_student(cohort_name, content_group, student): cohort_management_page.add_cohort(cohort_name, content_group=content_group) diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 512283db94..ed6b7aefea 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -65,9 +65,7 @@ def instructor_dashboard_2(request, course_id): 'finance_admin': CourseFinanceAdminRole(course_key).has_user(request.user), 'sales_admin': CourseSalesAdminRole(course_key).has_user(request.user), 'staff': has_access(request.user, 'staff', course), - 'forum_admin': has_forum_access( - request.user, course_key, FORUM_ROLE_ADMINISTRATOR - ), + 'forum_admin': has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR), } if not access['staff']: @@ -79,6 +77,7 @@ def instructor_dashboard_2(request, course_id): _section_student_admin(course, access), _section_data_download(course, access), _section_analytics(course, access), + _section_cohort_management(course, access), ] #check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course @@ -330,7 +329,22 @@ def _section_membership(course, access): 'modify_access_url': reverse('modify_access', kwargs={'course_id': unicode(course_key)}), 'list_forum_members_url': reverse('list_forum_members', kwargs={'course_id': unicode(course_key)}), 'update_forum_role_membership_url': reverse('update_forum_role_membership', kwargs={'course_id': unicode(course_key)}), - 'cohorts_ajax_url': reverse('cohorts', kwargs={'course_key_string': unicode(course_key)}), + } + return section_data + + +def _section_cohort_management(course, access): + """ Provide data for the corresponding cohort management section """ + course_key = course.id + section_data = { + 'section_key': 'cohort_management', + 'section_display_name': _('Cohort Management'), + 'access': access, + 'course_cohort_settings_url': reverse( + 'course_cohort_settings', + kwargs={'course_key_string': unicode(course_key)} + ), + 'cohorts_url': reverse('cohorts', kwargs={'course_key_string': unicode(course_key)}), 'advanced_settings_url': get_studio_url(course, 'settings/advanced'), 'upload_cohorts_csv_url': reverse('add_users_to_cohorts', kwargs={'course_id': unicode(course_key)}), } diff --git a/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee b/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee index fa7c0d353f..3334bd8fb8 100644 --- a/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee +++ b/lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee @@ -176,6 +176,9 @@ setup_instructor_dashboard_sections = (idash_content) -> , constructor: window.InstructorDashboard.sections.Metrics $element: idash_content.find ".#{CSS_IDASH_SECTION}#metrics" + , + constructor: window.InstructorDashboard.sections.CohortManagement + $element: idash_content.find ".#{CSS_IDASH_SECTION}#cohort_management" ] sections_to_initialize.map ({constructor, $element}) -> diff --git a/lms/static/js/factories/cohorts_factory.js b/lms/static/js/factories/cohorts_factory.js new file mode 100644 index 0000000000..3d04e1fd91 --- /dev/null +++ b/lms/static/js/factories/cohorts_factory.js @@ -0,0 +1,35 @@ +;(function (define, undefined) { + 'use strict'; + define(['jquery', 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/course_cohort_settings'], + function($) { + + return function(contentGroups, studioGroupConfigurationsUrl) { + + var cohorts = new edx.groups.CohortCollection(), + courseCohortSettings = new edx.groups.CourseCohortSettingsModel(); + + var cohortManagementElement = $('.cohort-management'); + + cohorts.url = cohortManagementElement.data('cohorts_url'); + courseCohortSettings.url = cohortManagementElement.data('course_cohort_settings_url'); + + var cohortsView = new edx.groups.CohortsView({ + el: cohortManagementElement, + model: cohorts, + contentGroups: contentGroups, + cohortSettings: courseCohortSettings, + context: { + uploadCohortsCsvUrl: cohortManagementElement.data('upload_cohorts_csv_url'), + studioAdvancedSettingsUrl: cohortManagementElement.data('advanced-settings-url'), + studioGroupConfigurationsUrl: studioGroupConfigurationsUrl + } + }); + cohorts.fetch().done(function() { + courseCohortSettings.fetch().done(function() { + cohortsView.render(); + }) + }); + }; + }); +}).call(this, define || RequireJS.define); + diff --git a/lms/static/js/groups/models/course_cohort_settings.js b/lms/static/js/groups/models/course_cohort_settings.js new file mode 100644 index 0000000000..a87eeceec7 --- /dev/null +++ b/lms/static/js/groups/models/course_cohort_settings.js @@ -0,0 +1,16 @@ +var edx = edx || {}; + +(function(Backbone) { + 'use strict'; + + edx.groups = edx.groups || {}; + + edx.groups.CourseCohortSettingsModel = Backbone.Model.extend({ + idAttribute: 'id', + defaults: { + is_cohorted: false, + cohorted_discussions: [], + always_cohort_inline_discussions: true + } + }); +}).call(this, Backbone); diff --git a/lms/static/js/groups/views/cohorts.js b/lms/static/js/groups/views/cohorts.js index 4815eaa567..ba175097a9 100644 --- a/lms/static/js/groups/views/cohorts.js +++ b/lms/static/js/groups/views/cohorts.js @@ -1,7 +1,7 @@ var edx = edx || {}; (function($, _, Backbone, gettext, interpolate_text, CohortModel, CohortEditorView, CohortFormView, - NotificationModel, NotificationView, FileUploaderView) { + CourseCohortSettingsNotificationView, NotificationModel, NotificationView, FileUploaderView) { 'use strict'; var hiddenClass = 'is-hidden', @@ -12,6 +12,7 @@ var edx = edx || {}; edx.groups.CohortsView = Backbone.View.extend({ events : { 'change .cohort-select': 'onCohortSelected', + 'change .cohorts-state': 'onCohortsEnabledChanged', 'click .action-create': 'showAddCohortForm', 'click .cohort-management-add-form .action-save': 'saveAddCohortForm', 'click .cohort-management-add-form .action-cancel': 'cancelAddCohortForm', @@ -26,19 +27,21 @@ var edx = edx || {}; this.selectorTemplate = _.template($('#cohort-selector-tpl').text()); this.context = options.context; this.contentGroups = options.contentGroups; + this.cohortSettings = options.cohortSettings; model.on('sync', this.onSync, this); - // Update cohort counts when the user clicks back on the membership tab + // Update cohort counts when the user clicks back on the cohort management tab // (for example, after uploading a csv file of cohort assignments and then // checking results on data download tab). - $(this.getSectionCss('membership')).click(function () { + $(this.getSectionCss('cohort_management')).click(function () { model.fetch(); }); }, render: function() { this.$el.html(this.template({ - cohorts: this.model.models + cohorts: this.model.models, + cohortsEnabled: this.cohortSettings.get('is_cohorted') })); this.onSync(); return this; @@ -51,6 +54,13 @@ var edx = edx || {}; })); }, + renderCourseCohortSettingsNotificationView: function() { + var cohortStateMessageNotificationView = new CourseCohortSettingsNotificationView({ + el: $('.cohort-state-message'), + cohortEnabled: this.getCohortsEnabled()}); + cohortStateMessageNotificationView.render(); + }, + onSync: function(model, response, options) { var selectedCohort = this.lastSelectedCohortId && this.model.get(this.lastSelectedCohortId), hasCohorts = this.model.length > 0, @@ -98,6 +108,28 @@ var edx = edx || {}; this.showCohortEditor(selectedCohort); }, + onCohortsEnabledChanged: function(event) { + event.preventDefault(); + this.saveCohortSettings(); + }, + + saveCohortSettings: function() { + var self = this, + cohortSettings, + fieldData = {is_cohorted: this.getCohortsEnabled()}; + cohortSettings = this.cohortSettings; + cohortSettings.save( + fieldData, {wait: true} + ).done(function() { + self.render(); + self.renderCourseCohortSettingsNotificationView(); + }); + }, + + getCohortsEnabled: function() { + return this.$('.cohorts-state').prop('checked'); + }, + showCohortEditor: function(cohort) { this.removeNotification(); if (this.editor) { @@ -242,4 +274,5 @@ var edx = edx || {}; } }); }).call(this, $, _, Backbone, gettext, interpolate_text, edx.groups.CohortModel, edx.groups.CohortEditorView, - edx.groups.CohortFormView, NotificationModel, NotificationView, FileUploaderView); + edx.groups.CohortFormView, edx.groups.CourseCohortSettingsNotificationView, NotificationModel, NotificationView, + FileUploaderView); diff --git a/lms/static/js/groups/views/course_cohort_settings_notification.js b/lms/static/js/groups/views/course_cohort_settings_notification.js new file mode 100644 index 0000000000..1708b1e532 --- /dev/null +++ b/lms/static/js/groups/views/course_cohort_settings_notification.js @@ -0,0 +1,35 @@ +var edx = edx || {}; + +(function($, _, Backbone, gettext) { + 'use strict'; + + edx.groups = edx.groups || {}; + + edx.groups.CourseCohortSettingsNotificationView = Backbone.View.extend({ + initialize: function(options) { + this.template = _.template($('#cohort-state-tpl').text()); + this.cohortEnabled = options.cohortEnabled; + }, + + render: function() { + this.$el.html(this.template({})); + this.showCohortStateMessage(); + return this; + }, + + showCohortStateMessage: function () { + var actionToggleMessage = this.$('.action-toggle-message'); + + // The following lines are necessary to re-trigger the CSS animation on span.action-toggle-message + actionToggleMessage.removeClass('is-fleeting'); + actionToggleMessage.offset().width = actionToggleMessage.offset().width; + actionToggleMessage.addClass('is-fleeting'); + + if (this.cohortEnabled) { + actionToggleMessage.text(gettext('Cohorts Enabled')); + } else { + actionToggleMessage.text(gettext('Cohorts Disabled')); + } + } + }); +}).call(this, $, _, Backbone, gettext); diff --git a/lms/static/js/instructor_dashboard/cohort_management.js b/lms/static/js/instructor_dashboard/cohort_management.js new file mode 100644 index 0000000000..1d820ba2cc --- /dev/null +++ b/lms/static/js/instructor_dashboard/cohort_management.js @@ -0,0 +1,29 @@ +(function() { + var CohortManagement; + + CohortManagement = (function() { + + function CohortManagement($section) { + this.$section = $section; + this.$section.data('wrapper', this); + } + + CohortManagement.prototype.onClickTitle = function() {}; + + return CohortManagement; + + })(); + + _.defaults(window, { + InstructorDashboard: {} + }); + + _.defaults(window.InstructorDashboard, { + sections: {} + }); + + _.defaults(window.InstructorDashboard.sections, { + CohortManagement: CohortManagement + }); + +}).call(this); diff --git a/lms/static/js/spec/groups/views/cohorts_spec.js b/lms/static/js/spec/groups/views/cohorts_spec.js index 4aacf9caf0..3123bef275 100644 --- a/lms/static/js/spec/groups/views/cohorts_spec.js +++ b/lms/static/js/spec/groups/views/cohorts_spec.js @@ -1,15 +1,18 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpers/template_helpers', - 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/content_group'], - function (Backbone, $, AjaxHelpers, TemplateHelpers, CohortsView, CohortCollection, ContentGroupModel) { + 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/content_group', + 'js/groups/models/course_cohort_settings', 'js/groups/views/course_cohort_settings_notification'], + function (Backbone, $, AjaxHelpers, TemplateHelpers, CohortsView, CohortCollection, ContentGroupModel, + CourseCohortSettingsModel, CourseCohortSettingsNotificationView) { 'use strict'; describe("Cohorts View", function () { var catLoversInitialCount = 123, dogLoversInitialCount = 456, unknownUserMessage, - createMockCohort, createMockCohorts, createMockContentGroups, createCohortsView, cohortsView, - requests, respondToRefresh, verifyMessage, verifyNoMessage, verifyDetailedMessage, verifyHeader, - expectCohortAddRequest, getAddModal, selectContentGroup, clearContentGroup, saveFormAndExpectErrors, - MOCK_COHORTED_USER_PARTITION_ID, MOCK_UPLOAD_COHORTS_CSV_URL, MOCK_STUDIO_ADVANCED_SETTINGS_URL, - MOCK_STUDIO_GROUP_CONFIGURATIONS_URL, MOCK_MANUAL_ASSIGNMENT, MOCK_RANDOM_ASSIGNMENT; + createMockCohort, createMockCohorts, createMockContentGroups, createCohortSettings, createCohortsView, + cohortsView, requests, respondToRefresh, verifyMessage, verifyNoMessage, verifyDetailedMessage, + verifyHeader, expectCohortAddRequest, getAddModal, selectContentGroup, clearContentGroup, + saveFormAndExpectErrors, createMockCohortSettings, MOCK_COHORTED_USER_PARTITION_ID, + MOCK_UPLOAD_COHORTS_CSV_URL, MOCK_STUDIO_ADVANCED_SETTINGS_URL, MOCK_STUDIO_GROUP_CONFIGURATIONS_URL, + MOCK_MANUAL_ASSIGNMENT, MOCK_RANDOM_ASSIGNMENT; MOCK_MANUAL_ASSIGNMENT = 'manual'; MOCK_RANDOM_ASSIGNMENT = 'random'; @@ -49,17 +52,35 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe ]; }; + createMockCohortSettings = function (isCohorted, cohortedDiscussions, alwaysCohortInlineDiscussions) { + return { + id: 0, + is_cohorted: isCohorted || false, + cohorted_discussions: cohortedDiscussions || [], + always_cohort_inline_discussions: alwaysCohortInlineDiscussions || true + }; + }; + + createCohortSettings = function (isCohorted, cohortedDiscussions, alwaysCohortInlineDiscussions) { + return new CourseCohortSettingsModel( + createMockCohortSettings(isCohorted, cohortedDiscussions, alwaysCohortInlineDiscussions) + ); + }; + createCohortsView = function (test, options) { - var cohortsJson, cohorts, contentGroups; + var cohortsJson, cohorts, contentGroups, cohortSettings; options = options || {}; cohortsJson = options.cohorts ? {cohorts: options.cohorts} : createMockCohorts(); cohorts = new CohortCollection(cohortsJson, {parse: true}); contentGroups = options.contentGroups || createMockContentGroups(); + cohortSettings = options.cohortSettings || createCohortSettings(true); + cohortSettings.url = '/mock_service/cohorts/settings'; cohorts.url = '/mock_service/cohorts'; requests = AjaxHelpers.requests(test); cohortsView = new CohortsView({ model: cohorts, contentGroups: contentGroups, + cohortSettings: cohortSettings, context: { uploadCohortsCsvUrl: MOCK_UPLOAD_COHORTS_CSV_URL, studioAdvancedSettingsUrl: MOCK_STUDIO_ADVANCED_SETTINGS_URL, @@ -177,13 +198,14 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }; beforeEach(function () { - setFixtures('
'); + setFixtures('
'); TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohorts'); TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-form'); TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-selector'); TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-editor'); TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-group-header'); TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/notification'); + TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-state'); TemplateHelpers.installTemplate('templates/file-upload'); }); @@ -202,7 +224,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe it("syncs data when membership tab is clicked", function() { createCohortsView(this, {selectCohort: 1}); verifyHeader(1, 'Cat Lovers', catLoversInitialCount); - $(cohortsView.getSectionCss("membership")).click(); + $(cohortsView.getSectionCss("cohort_management")).click(); AjaxHelpers.expectRequest(requests, 'GET', '/mock_service/cohorts'); respondToRefresh(1001, 2); verifyHeader(1, 'Cat Lovers', 1001); @@ -255,6 +277,54 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }); }); + describe("Course Cohort Settings", function () { + it('enable/disable working correctly', function () { + createCohortsView(this, {cohortSettings: createCohortSettings(false)}); + + expect(cohortsView.$('.cohorts-state').prop('checked')).toBeFalsy(); + + cohortsView.$('.cohorts-state').prop('checked', true).change(); + AjaxHelpers.expectJsonRequest( + requests, 'PUT', '/mock_service/cohorts/settings', + createMockCohortSettings(true, [], true) + ); + AjaxHelpers.respondWithJson( + requests, + createMockCohortSettings(true) + ); + expect(cohortsView.$('.cohorts-state').prop('checked')).toBeTruthy(); + + cohortsView.$('.cohorts-state').prop('checked', false).change(); + AjaxHelpers.expectJsonRequest( + requests, 'PUT', '/mock_service/cohorts/settings', + createMockCohortSettings(false, [], true) + ); + AjaxHelpers.respondWithJson( + requests, + createMockCohortSettings(false) + ); + expect(cohortsView.$('.cohorts-state').prop('checked')).toBeFalsy(); + }); + + + it('Course Cohort Settings Notification View renders correctly', function () { + var createCourseCohortSettingsNotificationView = function (is_cohorted) { + var notificationView = new CourseCohortSettingsNotificationView({ + el: $('.cohort-state-message'), + cohortEnabled: is_cohorted}); + notificationView.render(); + return notificationView; + }; + + var notificationView = createCourseCohortSettingsNotificationView(true); + expect(notificationView.$('.action-toggle-message').text().trim()).toBe('Cohorts Enabled'); + + notificationView = createCourseCohortSettingsNotificationView(false); + expect(notificationView.$('.action-toggle-message').text().trim()).toBe('Cohorts Disabled'); + }); + + }); + describe("Cohort Group Header", function () { it("renders header correctly", function () { var cohortName = 'Transformers', @@ -867,7 +937,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe // We have a single random cohort so we should not be allowed to change it assignment type expect(cohortsView.$('.cohort-management-assignment-type-settings')).toHaveClass('is-disabled'); - expect(cohortsView.$('.copy-error').text()).toContain("There must be one cohort to which students can be randomly assigned."); + expect(cohortsView.$('.copy-error').text()).toContain("There must be one cohort to which students can automatically be assigned."); }); it("cancel settings works", function() { diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index caf1f418a8..0c2b9fcbca 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -67,6 +67,8 @@ 'js/views/notification': 'js/views/notification', 'js/groups/models/cohort': 'js/groups/models/cohort', 'js/groups/models/content_group': 'js/groups/models/content_group', + 'js/groups/models/course_cohort_settings': 'js/groups/models/course_cohort_settings', + 'js/groups/views/course_cohort_settings_notification': 'js/groups/views/course_cohort_settings_notification', 'js/groups/collections/cohort': 'js/groups/collections/cohort', 'js/groups/views/cohort_editor': 'js/groups/views/cohort_editor', 'js/groups/views/cohort_form': 'js/groups/views/cohort_form', @@ -294,6 +296,14 @@ exports: 'edx.groups.ContentGroupModel', deps: ['backbone'] }, + 'js/groups/models/course_cohort_settings': { + exports: 'edx.groups.CourseCohortSettingsModel', + deps: ['backbone'] + }, + 'js/groups/views/course_cohort_settings_notification': { + exports: 'edx.groups.CourseCohortSettingsNotificationView', + deps: ['backbone'] + }, 'js/groups/collections/cohort': { exports: 'edx.groups.CohortCollection', deps: ['backbone', 'js/groups/models/cohort'] diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index 98cf31d895..5ea18fe818 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -435,6 +435,248 @@ } } + .batch-enrollment, .batch-beta-testers { + textarea { + margin-top: 0.2em; + height: auto; + width: 90%; + } + + input { + margin-right: ($baseline/4); + } + + .request-res-section { + margin-top: 1.5em; + + h3 { + color: #646464; + } + + ul { + margin: 0; + margin-top: 0.5em; + padding: 0; + list-style-type: none; + line-height: 1.5em; + } + } + } + // Auto Enroll Csv Section + .auto_enroll_csv { + .results { + + } + .enrollment_signup_button { + @include margin-right($baseline/4); + } + // Custom File upload + .customBrowseBtn { + margin: ($baseline/2) 0; + display: inline-block; + .file-browse { + position:relative; + overflow:hidden; + display: inline; + @include margin-left(-5px); + span.browse{ + @include button(simple, $blue); + @include margin-right($baseline); + padding: 6px ($baseline/2); + font-size: 12px; + border-radius: 0 3px 3px 0; + } + input.file_field { + position:absolute; + @include right(0); + top:0; + margin:0; + padding:0; + cursor:pointer; + opacity:0; + filter:alpha(opacity=0); + } + } + & > span, & input[disabled]{ + vertical-align: middle; + } + input[disabled] { + @include border-radius(4px 0 0 4px); + @include padding(6px 6px 5px); + border: 1px solid $lightGrey1; + cursor: not-allowed; + } + } + } + + .enroll-option { + margin: ($baseline/2) 0; + position: relative; + + label { + border-bottom: 1px dotted $base-font-color; + } + + .hint { + @extend %t-copy-sub2; + display: none; + position: absolute; + top: 15px; + @include left($baseline*10); + padding: ($baseline/2); + width: 50%; + background-color: $light-gray; + box-shadow: 2px 2px 3px $shadow; + + .hint-caret { + display: block; + position: absolute; + top: 0; + @include left(-15px); + @include border-right(8px solid $light-gray); + @include border-left(8px solid transparent); + border-top: 8px solid $light-gray; + border-bottom: 8px solid transparent; + } + } + } + + label[for="auto-enroll"]:hover + .auto-enroll-hint { + display: block; + } + + label[for="auto-enroll-beta"]:hover + .auto-enroll-beta-hint { + width: 30%; + display: block; + } + + label[for="email-students"]:hover + .email-students-hint { + display: block; + } + + label[for="email-students-beta"]:hover + .email-students-beta-hint { + width: 30%; + display: block; + } + + .enroll-actions { + margin-top: $baseline; + } + + .member-lists-management { + + .wrapper-member-select { + padding: ($baseline/2); + background-color: $light-gray; + } + + .member-lists-selector { + display: block; + margin: ($baseline/4) 0; + padding: ($baseline/4); + } + + .auth-list-container { + display: none; + margin-bottom: ($baseline*1.5); + + &.active { + display: block; + } + + .member-list-widget { + + .header { + @include box-sizing(border-box); + @include border-top-radius(3); + position: relative; + padding: ($baseline/2); + background-color: #efefef; + border: 1px solid $light-gray; + display: none; // hiding to prefer dropdown as header + } + + .title { + @include font-size(16); + } + + .label, + .form-label { + @extend %t-copy-sub1; + color: $lighter-base-font-color; + } + + .info { + @include box-sizing(border-box); + padding: ($baseline/2); + border: 1px solid $light-gray; + color: $lighter-base-font-color; + line-height: 1.3em; + font-size: .85em; + } + + .member-list { + @include box-sizing(border-box); + + table { + width: 100%; + } + + thead { + background-color: $light-gray; + } + + tr { + border-bottom: 1px solid $light-gray; + } + + td { + @extend %t-copy-sub1; + vertical-align: middle; + padding: ($baseline/2) ($baseline/4); + @include border-left(1px solid $light-gray); + @include border-right(1px solid $light-gray); + word-wrap: break-word; + } + } + + .bottom-bar { + @include box-sizing(border-box); + @include border-bottom-radius(3); + position: relative; + padding: ($baseline/2); + margin-top: -1px; + border: 1px solid $light-gray; + background-color: #efefef; + box-shadow: inset #bbb 0px 1px 1px 0px; + } + + // .add-field + + input[type="button"].add { + @include idashbutton($blue); + position: absolute; + @include right($baseline); + } + } + + .revoke { + color: $lighter-base-font-color; + cursor: pointer; + + &:hover, &:focus { + color: $alert-color; + } + } + } + } +} + + +// view - cohort management +// -------------------- +.instructor-dashboard-wrapper-2 section.idash-section#cohort_management { + // cohort management %cohort-management-form { @@ -733,245 +975,6 @@ } } - - - .batch-enrollment, .batch-beta-testers { - textarea { - margin-top: 0.2em; - height: auto; - width: 90%; - } - - input { - margin-right: ($baseline/4); - } - - .request-res-section { - margin-top: 1.5em; - - h3 { - color: #646464; - } - - ul { - margin: 0; - margin-top: 0.5em; - padding: 0; - list-style-type: none; - line-height: 1.5em; - } - } - } - // Auto Enroll Csv Section - .auto_enroll_csv { - .results { - - } - .enrollment_signup_button { - @include margin-right($baseline/4); - } - // Custom File upload - .customBrowseBtn { - margin: ($baseline/2) 0; - display: inline-block; - .file-browse { - position:relative; - overflow:hidden; - display: inline; - @include margin-left(-5px); - span.browse{ - @include button(simple, $blue); - @include margin-right($baseline); - padding: 6px ($baseline/2); - font-size: 12px; - border-radius: 0 3px 3px 0; - } - input.file_field { - position:absolute; - @include right(0); - top:0; - margin:0; - padding:0; - cursor:pointer; - opacity:0; - filter:alpha(opacity=0); - } - } - & > span, & input[disabled]{ - vertical-align: middle; - } - input[disabled] { - @include border-radius(4px 0 0 4px); - @include padding(6px 6px 5px); - border: 1px solid $lightGrey1; - cursor: not-allowed; - } - } - } - - .enroll-option { - margin: ($baseline/2) 0; - position: relative; - - label { - border-bottom: 1px dotted $base-font-color; - } - - .hint { - @extend %t-copy-sub2; - display: none; - position: absolute; - top: 15px; - @include left($baseline*10); - padding: ($baseline/2); - width: 50%; - background-color: $light-gray; - box-shadow: 2px 2px 3px $shadow; - - .hint-caret { - display: block; - position: absolute; - top: 0; - @include left(-15px); - @include border-right(8px solid $light-gray); - @include border-left(8px solid transparent); - border-top: 8px solid $light-gray; - border-bottom: 8px solid transparent; - } - } - } - - label[for="auto-enroll"]:hover + .auto-enroll-hint { - display: block; - } - - label[for="auto-enroll-beta"]:hover + .auto-enroll-beta-hint { - width: 30%; - display: block; - } - - - label[for="email-students"]:hover + .email-students-hint { - display: block; - } - - label[for="email-students-beta"]:hover + .email-students-beta-hint { - width: 30%; - display: block; - } - - .enroll-actions { - margin-top: $baseline; - } - - .member-lists-management { - - .wrapper-member-select { - padding: ($baseline/2); - background-color: $light-gray; - } - - .member-lists-selector { - display: block; - margin: ($baseline/4) 0; - padding: ($baseline/4); - } - - .auth-list-container { - display: none; - margin-bottom: ($baseline*1.5); - - &.active { - display: block; - } - - .member-list-widget { - - .header { - @include box-sizing(border-box); - @include border-top-radius(3); - position: relative; - padding: ($baseline/2); - background-color: #efefef; - border: 1px solid $light-gray; - display: none; // hiding to prefer dropdown as header - } - - .title { - @include font-size(16); - } - - .label, - .form-label { - @extend %t-copy-sub1; - color: $lighter-base-font-color; - } - - .info { - @include box-sizing(border-box); - padding: ($baseline/2); - border: 1px solid $light-gray; - color: $lighter-base-font-color; - line-height: 1.3em; - font-size: .85em; - } - - .member-list { - @include box-sizing(border-box); - - table { - width: 100%; - } - - thead { - background-color: $light-gray; - } - - tr { - border-bottom: 1px solid $light-gray; - } - - td { - @extend %t-copy-sub1; - vertical-align: middle; - padding: ($baseline/2) ($baseline/4); - @include border-left(1px solid $light-gray); - @include border-right(1px solid $light-gray); - word-wrap: break-word; - } - } - - .bottom-bar { - @include box-sizing(border-box); - @include border-bottom-radius(3); - position: relative; - padding: ($baseline/2); - margin-top: -1px; - border: 1px solid $light-gray; - background-color: #efefef; - box-shadow: inset #bbb 0px 1px 1px 0px; - } - - // .add-field - - input[type="button"].add { - @include idashbutton($blue); - position: absolute; - @include right($baseline); - } - } - - .revoke { - color: $lighter-base-font-color; - cursor: pointer; - - &:hover, &:focus { - color: $alert-color; - } - } - } - } - .has-other-input-text { // Given to groups which have an 'other' input that appears when needed display: inline-block; @@ -1118,6 +1121,7 @@ } } + // view - student admin // -------------------- .instructor-dashboard-wrapper-2 section.idash-section#student_admin > { diff --git a/lms/templates/instructor/instructor_dashboard_2/cohort-form.underscore b/lms/templates/instructor/instructor_dashboard_2/cohort-form.underscore index 20049ecde5..8d7df60bf0 100644 --- a/lms/templates/instructor/instructor_dashboard_2/cohort-form.underscore +++ b/lms/templates/instructor/instructor_dashboard_2/cohort-form.underscore @@ -38,7 +38,7 @@ <%- gettext('Students in this cohort are:') %>