diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a3862ff21a..f9d007c62e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -28,9 +28,6 @@ LMS: Student Notes: Toggle single note visibility. TNL-660 LMS: Student Notes: Add Notes page. TNL-797 LMS: Student Notes: Add possibility to add/edit/remove notes. TNL-655 -======= -LMS: Extend preview to support cohorted courseware. TNL-651 ->>>>>>> Extend preview to support cohorted courseware Platform: Add group_access field to all xblocks. TNL-670 diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index 23320d1580..1e3a3082f7 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -28,7 +28,6 @@ class CourseMetadata(object): 'graded', 'hide_from_toc', 'pdf_textbooks', - 'user_partitions', 'name', # from xblock 'tags', # from xblock 'visible_to_staff_only', diff --git a/common/test/acceptance/pages/lms/instructor_dashboard.py b/common/test/acceptance/pages/lms/instructor_dashboard.py index 5b251c7140..963921778b 100644 --- a/common/test/acceptance/pages/lms/instructor_dashboard.py +++ b/common/test/acceptance/pages/lms/instructor_dashboard.py @@ -153,7 +153,7 @@ class MembershipPageCohortManagementSection(PageObject): Adds a new manual cohort with the specified name. """ self.q(css=self._bounded_selector("div.cohort-management-nav .action-create")).first.click() - textinput = self.q(css=self._bounded_selector("#cohort-create-name")).results[0] + textinput = self.q(css=self._bounded_selector("#cohort-name")).results[0] textinput.send_keys(cohort_name) self.q(css=self._bounded_selector("div.form-actions .action-save")).first.click() diff --git a/common/test/acceptance/tests/discussion/helpers.py b/common/test/acceptance/tests/discussion/helpers.py index b38a9a36a0..41f83624d9 100644 --- a/common/test/acceptance/tests/discussion/helpers.py +++ b/common/test/acceptance/tests/discussion/helpers.py @@ -3,6 +3,7 @@ Helper functions and classes for discussion tests. """ from uuid import uuid4 +import json from ...fixtures.discussion import ( SingleThreadViewFixture, @@ -68,11 +69,11 @@ class CohortTestMixin(object): """ Adds a cohort group by name, returning the ID for the group. """ - url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/add' - data = {"name": cohort_name} + url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/' + data = json.dumps({"name": cohort_name}) response = course_fixture.session.post(url, data=data, headers=course_fixture.headers) self.assertTrue(response.ok, "Failed to create cohort") - return response.json()['cohort']['id'] + return response.json()['id'] def add_user_to_cohort(self, course_fixture, username, cohort_id): """ diff --git a/lms/static/js/groups/models/content_group.js b/lms/static/js/groups/models/content_group.js new file mode 100644 index 0000000000..d52abe4b8e --- /dev/null +++ b/lms/static/js/groups/models/content_group.js @@ -0,0 +1,14 @@ +var edx = edx || {}; + +(function(Backbone) { + 'use strict'; + + edx.groups = edx.groups || {}; + + edx.groups.ContentGroupModel = Backbone.Model.extend({ + idAttribute: 'id', + defaults: { + name: '' + } + }); +}).call(this, Backbone); diff --git a/lms/static/js/groups/views/cohort_editor.js b/lms/static/js/groups/views/cohort_editor.js index c38af29d14..6b808a1745 100644 --- a/lms/static/js/groups/views/cohort_editor.js +++ b/lms/static/js/groups/views/cohort_editor.js @@ -1,18 +1,22 @@ var edx = edx || {}; -(function(Backbone, _, $, gettext, ngettext, interpolate_text, NotificationModel, NotificationView) { +(function(Backbone, _, $, gettext, ngettext, interpolate_text, CohortFormView, NotificationModel, NotificationView) { 'use strict'; edx.groups = edx.groups || {}; edx.groups.CohortEditorView = Backbone.View.extend({ events : { - "submit .cohort-management-group-add-form": "addStudents" + 'click .wrapper-tabs .tab': 'selectTab', + 'click .tab-content-settings .action-save': 'saveSettings', + 'submit .cohort-management-group-add-form': 'addStudents' }, initialize: function(options) { this.template = _.template($('#cohort-editor-tpl').text()); this.cohorts = options.cohorts; + this.cohortUserPartitionId = options.cohortUserPartitionId; + this.contentGroups = options.contentGroups; this.advanced_settings_url = options.advanced_settings_url; }, @@ -24,11 +28,35 @@ var edx = edx || {}; render: function() { this.$el.html(this.template({ cohort: this.model, + cohortUserPartitionId: this.cohortUserPartitionId, + contentGroups: this.contentGroups, advanced_settings_url: this.advanced_settings_url })); + this.cohortFormView = new CohortFormView({ + model: this.model, + cohortUserPartitionId: this.cohortUserPartitionId, + contentGroups: this.contentGroups + }); + this.cohortFormView.render(); + this.$('.tab-content-settings').append(this.cohortFormView.$el); return this; }, + selectTab: function(event) { + var tabElement = $(event.currentTarget), + tabName = tabElement.data('tab'); + event.preventDefault(); + this.$('.wrapper-tabs .tab').removeClass('is-selected'); + tabElement.addClass('is-selected'); + this.$('.tab-content').addClass('is-hidden'); + this.$('.tab-content-' + tabName).removeClass('is-hidden'); + }, + + saveSettings: function(event) { + event.preventDefault(); + this.cohortFormView.saveForm(); + }, + setCohort: function(cohort) { this.model = cohort; this.render(); @@ -208,4 +236,5 @@ var edx = edx || {}; } } }); -}).call(this, Backbone, _, $, gettext, ngettext, interpolate_text, NotificationModel, NotificationView); +}).call(this, Backbone, _, $, gettext, ngettext, interpolate_text, edx.groups.CohortFormView, + NotificationModel, NotificationView); diff --git a/lms/static/js/groups/views/cohort_form.js b/lms/static/js/groups/views/cohort_form.js new file mode 100644 index 0000000000..be67affc53 --- /dev/null +++ b/lms/static/js/groups/views/cohort_form.js @@ -0,0 +1,128 @@ +var edx = edx || {}; + +(function($, _, Backbone, gettext, interpolate_text, CohortModel, NotificationModel, NotificationView) { + 'use strict'; + + edx.groups = edx.groups || {}; + + edx.groups.CohortFormView = Backbone.View.extend({ + events : { + 'change .cohort-management-details-association-course input': 'onRadioButtonChange', + 'change .input-cohort-group-association': 'onGroupAssociationChange', + 'click .tab-content-settings .action-save': 'saveSettings', + 'submit .cohort-management-group-add-form': 'addStudents' + }, + + initialize: function(options) { + this.template = _.template($('#cohort-form-tpl').text()); + this.cohortUserPartitionId = options.cohortUserPartitionId; + this.contentGroups = options.contentGroups; + }, + + showNotification: function(options, beforeElement) { + var model = new NotificationModel(options); + this.removeNotification(); + this.notification = new NotificationView({ + model: model + }); + this.notification.render(); + if (!beforeElement) { + beforeElement = this.$('.cohort-management-group'); + } + beforeElement.before(this.notification.$el); + }, + + removeNotification: function() { + if (this.notification) { + this.notification.remove(); + } + }, + + render: function() { + this.$el.html(this.template({ + cohort: this.model, + contentGroups: this.contentGroups + })); + return this; + }, + + onRadioButtonChange: function(event) { + var target = $(event.currentTarget), + groupsEnabled = target.val() === 'yes'; + if (!groupsEnabled) { + // If the user has chosen 'no', then clear the selection by setting + // it to the first option ('Choose a content group to associate'). + this.$('.input-cohort-group-association').val('None'); + } + }, + + onGroupAssociationChange: function(event) { + // Since the user has chosen a content group, click the 'Yes' button too + this.$('.cohort-management-details-association-course .radio-yes').click(); + }, + + getSelectedGroupId: function() { + var selectValue = this.$('.input-cohort-group-association').val(); + if (!this.$('.radio-yes').prop('checked') || selectValue === 'None') { + return null; + } + return parseInt(selectValue); + }, + + getUpdatedCohortName: function() { + var cohortName = this.$('.cohort-name').val(); + return cohortName ? cohortName.trim() : this.model.get('name'); + }, + + saveForm: function() { + var self = this, + cohort = this.model, + saveOperation = $.Deferred(), + cohortName, groupId, showMessage, showAddError; + this.removeNotification(); + showMessage = function(message, type) { + self.showNotification( + {type: type || 'confirmation', title: message}, + self.$('.form-fields') + ); + }; + showAddError = function(message, type) { + showMessage(message, 'error'); + }; + cohortName = this.getUpdatedCohortName(); + if (cohortName.length === 0) { + showAddError(gettext('Please enter a name for your new cohort group.')); + saveOperation.reject(); + } else { + groupId = this.getSelectedGroupId(); + cohort.save( + {name: cohortName, user_partition_id: this.cohortUserPartitionId, group_id: groupId}, + {patch: true} + ).done(function(result) { + if (!result.error) { + cohort.id = result.id; + showMessage(gettext('Saved cohort group.')); + saveOperation.resolve(); + } else { + showAddError(result.error); + saveOperation.reject(); + } + }).fail(function(result) { + var errorMessage = null; + try { + var jsonResponse = JSON.parse(result.responseText); + errorMessage = jsonResponse.error; + } catch(e) { + // Ignore the exception and show the default error message instead. + } + if (!errorMessage) { + errorMessage = gettext("We've encountered an error. Please refresh your browser and then try again."); + } + showAddError(errorMessage); + saveOperation.reject(); + }); + } + return saveOperation.promise(); + } + }); +}).call(this, $, _, Backbone, gettext, interpolate_text, edx.groups.CohortModel, NotificationModel, NotificationView); diff --git a/lms/static/js/groups/views/cohorts.js b/lms/static/js/groups/views/cohorts.js index 12cdf91978..d5f02b2c6c 100644 --- a/lms/static/js/groups/views/cohorts.js +++ b/lms/static/js/groups/views/cohorts.js @@ -1,6 +1,6 @@ var edx = edx || {}; -(function($, _, Backbone, gettext, interpolate_text, CohortEditorView, +(function($, _, Backbone, gettext, interpolate_text, CohortModel, CohortEditorView, CohortFormView, NotificationModel, NotificationView, FileUploaderView) { 'use strict'; @@ -13,8 +13,8 @@ var edx = edx || {}; events : { 'change .cohort-select': 'onCohortSelected', 'click .action-create': 'showAddCohortForm', - 'click .action-cancel': 'cancelAddCohortForm', - 'click .action-save': 'saveAddCohortForm', + 'click .cohort-management-add-modal .action-save': 'saveAddCohortForm', + 'click .cohort-management-add-modal .action-cancel': 'cancelAddCohortForm', 'click .link-cross-reference': 'showSection', 'click .toggle-cohort-management-secondary': 'showCsvUpload' }, @@ -24,9 +24,10 @@ var edx = edx || {}; this.template = _.template($('#cohorts-tpl').text()); this.selectorTemplate = _.template($('#cohort-selector-tpl').text()); - this.addCohortFormTemplate = _.template($('#add-cohort-form-tpl').text()); this.advanced_settings_url = options.advanced_settings_url; this.upload_cohorts_csv_url = options.upload_cohorts_csv_url; + this.cohortUserPartitionId = options.cohortUserPartitionId; + this.contentGroups = options.contentGroups; model.on('sync', this.onSync, this); // Update cohort counts when the user clicks back on the membership tab @@ -52,13 +53,17 @@ var edx = edx || {}; })); }, - onSync: function() { + onSync: function(model, response, options) { var selectedCohort = this.lastSelectedCohortId && this.model.get(this.lastSelectedCohortId), hasCohorts = this.model.length > 0, cohortNavElement = this.$('.cohort-management-nav'), - additionalCohortControlElement = this.$('.wrapper-cohort-supplemental'); + additionalCohortControlElement = this.$('.wrapper-cohort-supplemental'), + isModelUpdate = options && options.patch && response.hasOwnProperty('user_partition_id'); this.hideAddCohortForm(); - if (hasCohorts) { + if (isModelUpdate) { + // Refresh the selector in case the model's name changed + this.renderSelector(selectedCohort); + } else if (hasCohorts) { cohortNavElement.removeClass(hiddenClass); additionalCohortControlElement.removeClass(hiddenClass); this.renderSelector(selectedCohort); @@ -99,6 +104,8 @@ var edx = edx || {}; el: this.$('.cohort-management-group'), model: cohort, cohorts: this.model, + cohortUserPartitionId: this.cohortUserPartitionId, + contentGroups: this.contentGroups, advanced_settings_url: this.advanced_settings_url }); this.editor.render(); @@ -122,21 +129,32 @@ var edx = edx || {}; if (this.notification) { this.notification.remove(); } + if (this.cohortFormView) { + this.cohortFormView.removeNotification(); + } }, showAddCohortForm: function(event) { + var newCohort; event.preventDefault(); this.removeNotification(); - this.addCohortForm = $(this.addCohortFormTemplate({})); - this.addCohortForm.insertAfter(this.$('.cohort-management-nav')); + newCohort = new CohortModel(); + newCohort.url = this.model.url; + this.cohortFormView = new CohortFormView({ + model: newCohort, + cohortUserPartitionId: this.cohortUserPartitionId, + contentGroups: this.contentGroups + }); + this.cohortFormView.render(); + this.$('.cohort-management-add-modal').append(this.cohortFormView.$el); this.setCohortEditorVisibility(false); }, hideAddCohortForm: function() { this.setCohortEditorVisibility(true); - if (this.addCohortForm) { - this.addCohortForm.remove(); - this.addCohortForm = null; + if (this.cohortFormView) { + this.cohortFormView.remove(); + this.cohortFormView = null; } }, @@ -151,42 +169,23 @@ var edx = edx || {}; }, saveAddCohortForm: function(event) { - event.preventDefault(); var self = this, - showAddError, - cohortName = this.$('.cohort-create-name').val().trim(); - showAddError = function(message) { - self.showNotification( - {type: 'error', title: message}, - self.$('.cohort-management-create-form-name label') - ); - }; + newCohort = this.cohortFormView.model; + event.preventDefault(); this.removeNotification(); - if (cohortName.length > 0) { - $.post( - this.model.url + '/add', - {name: cohortName} - ).done(function(result) { - if (result.success) { - self.lastSelectedCohortId = result.cohort.id; - self.model.fetch().done(function() { - self.showNotification({ - type: 'confirmation', - title: interpolate_text( - gettext('The {cohortGroupName} cohort group has been created. You can manually add students to this group below.'), - {cohortGroupName: cohortName} - ) - }); - }); - } else { - showAddError(result.msg); - } - }).fail(function() { - showAddError(gettext("We've encountered an error. Please refresh your browser and then try again.")); + this.cohortFormView.saveForm() + .done(function() { + self.lastSelectedCohortId = newCohort.id; + self.model.fetch().done(function() { + self.showNotification({ + type: 'confirmation', + title: interpolate_text( + gettext('The {cohortGroupName} cohort group has been created. You can manually add students to this group below.'), + {cohortGroupName: newCohort.get('name')} + ) + }); }); - } else { - showAddError(gettext('Please enter a name for your new cohort group.')); - } + }); }, cancelAddCohortForm: function(event) { @@ -234,5 +233,5 @@ var edx = edx || {}; return ".instructor-nav .nav-item a[data-section='" + section + "']"; } }); -}).call(this, $, _, Backbone, gettext, interpolate_text, edx.groups.CohortEditorView, - NotificationModel, NotificationView, FileUploaderView); +}).call(this, $, _, Backbone, gettext, interpolate_text, edx.groups.CohortModel, edx.groups.CohortEditorView, + edx.groups.CohortFormView, NotificationModel, NotificationView, FileUploaderView); diff --git a/lms/static/js/spec/groups/views/cohorts_spec.js b/lms/static/js/spec/groups/views/cohorts_spec.js index a592e0b5f3..cf9484599e 100644 --- a/lms/static/js/spec/groups/views/cohorts_spec.js +++ b/lms/static/js/spec/groups/views/cohorts_spec.js @@ -1,16 +1,21 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpers/template_helpers', - 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'string_utils'], - function (Backbone, $, AjaxHelpers, TemplateHelpers, CohortsView, CohortCollection) { + 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/content_group'], + function (Backbone, $, AjaxHelpers, TemplateHelpers, CohortsView, CohortCollection, ContentGroupModel) { + 'use strict'; + describe("Cohorts View", function () { var catLoversInitialCount = 123, dogLoversInitialCount = 456, unknownUserMessage, - createMockCohort, createMockCohorts, createCohortsView, cohortsView, requests, respondToRefresh, - verifyMessage, verifyNoMessage, verifyDetailedMessage, verifyHeader; + createMockCohort, createMockCohorts, createMockContentGroups, createCohortsView, cohortsView, + requests, respondToRefresh, verifyMessage, verifyNoMessage, verifyDetailedMessage, verifyHeader, + expectCohortAddRequest, getAddModal, selectContentGroup, clearContentGroup; - createMockCohort = function (name, id, user_count) { + createMockCohort = function (name, id, userCount, groupId, userPartitionId) { return { id: id || 1, name: name, - user_count: user_count || 0 + user_count: userCount || 0, + group_id: groupId, + user_partition_id: userPartitionId }; }; @@ -23,17 +28,29 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }; }; - createCohortsView = function (test, initialCohortID, initialCohorts) { - var cohorts = new CohortCollection(initialCohorts || createMockCohorts(), {parse: true}); - cohorts.url = '/mock_service'; + createMockContentGroups = function () { + return [ + new ContentGroupModel({id: 0, name: 'Dog Content'}), + new ContentGroupModel({id: 1, name: 'Cat Content'}) + ]; + }; + + createCohortsView = function (test, options) { + var cohortsJson, cohorts, contentGroups; + options = options || {}; + cohortsJson = options.cohorts ? {cohorts: options.cohorts} : createMockCohorts(); + cohorts = new CohortCollection(cohortsJson, {parse: true}); + contentGroups = options.contentGroups || createMockContentGroups(); + cohorts.url = '/mock_service/cohorts'; requests = AjaxHelpers.requests(test); cohortsView = new CohortsView({ model: cohorts, + contentGroups: contentGroups, upload_cohorts_csv_url: "http://upload-csv-file-url/" }); cohortsView.render(); - if (initialCohortID) { - cohortsView.$('.cohort-select').val(initialCohortID.toString()).change(); + if (options && options.selectCohort) { + cohortsView.$('.cohort-select').val(options.selectCohort.toString()).change(); } }; @@ -41,6 +58,33 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe AjaxHelpers.respondWithJson(requests, createMockCohorts(catCount, dogCount)); }; + expectCohortAddRequest = function(name, group_id) { + AjaxHelpers.expectJsonRequest( + requests, 'POST', '/mock_service/cohorts', + { + name: name, + user_count: 0, + assignment_type: '', + group_id: group_id + } + ); + }; + + getAddModal = function() { + return cohortsView.$('.cohort-management-add-modal'); + }; + + selectContentGroup = function(values) { + cohortsView.$('.radio-yes').prop('checked', true).change(); + cohortsView.$('.input-cohort-group-association').val(values).change(); + }; + + clearContentGroup = function() { + cohortsView.$('.radio-no').prop('checked', true).change(); + expect(cohortsView.$('.radio-yes').prop('checked')).toBeFalsy(); + expect(cohortsView.$('.input-cohort-group-association').val()).toBe('None'); + }; + verifyMessage = function(expectedTitle, expectedMessageType, expectedAction, hasDetails) { expect(cohortsView.$('.message-title').text().trim()).toBe(expectedTitle); expect(cohortsView.$('div.message')).toHaveClass('message-' + expectedMessageType); @@ -94,7 +138,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe beforeEach(function () { setFixtures('
'); TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohorts'); - TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/add-cohort-form'); + 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/notification'); @@ -102,7 +146,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }); it("Show an error if no cohorts are defined", function() { - createCohortsView(this, null, { cohorts: [] }); + createCohortsView(this, {cohorts: []}); verifyMessage( 'You currently have no cohort groups configured', 'warning', @@ -114,14 +158,42 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }); it("Syncs data when membership tab is clicked", function() { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); verifyHeader(1, 'Cat Lovers', catLoversInitialCount); $(cohortsView.getSectionCss("membership")).click(); - AjaxHelpers.expectRequest(requests, 'GET', '/mock_service'); + AjaxHelpers.expectRequest(requests, 'GET', '/mock_service/cohorts'); respondToRefresh(1001, 2); verifyHeader(1, 'Cat Lovers', 1001); }); + it('can upload a CSV of cohort assignments if a cohort exists', function () { + var uploadCsvToggle, fileUploadForm, fileUploadFormCss='#file-upload-form'; + + createCohortsView(this); + + // Should see the control to toggle CSV file upload. + expect(cohortsView.$('.wrapper-cohort-supplemental')).not.toHaveClass('is-hidden'); + // But upload form should not be visible until toggle is clicked. + expect(cohortsView.$(fileUploadFormCss).length).toBe(0); + uploadCsvToggle = cohortsView.$('.toggle-cohort-management-secondary'); + expect(uploadCsvToggle.text()). + toContain('Assign students to cohort groups by uploading a CSV file'); + uploadCsvToggle.click(); + // After toggle is clicked, it should be hidden. + expect(uploadCsvToggle).toHaveClass('is-hidden'); + + fileUploadForm = cohortsView.$(fileUploadFormCss); + expect(fileUploadForm.length).toBe(1); + cohortsView.$(fileUploadForm).fileupload('add', {files: [{name: 'upload_file.txt'}]}); + cohortsView.$('.submit-file-button').click(); + + // No file will actually be uploaded because "uploaded_file.txt" doesn't actually exist. + AjaxHelpers.expectRequest(requests, 'POST', "http://upload-csv-file-url/", new FormData()); + AjaxHelpers.respondWithJson(requests, {}); + expect(cohortsView.$('.file-upload-form-result .message-confirmation .message-title').text().trim()) + .toBe("Your file 'upload_file.txt' has been uploaded. Please allow a few minutes for processing."); + }); + describe("Cohort Selector", function () { it('has no initial selection', function () { createCohortsView(this); @@ -129,63 +201,57 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe expect(cohortsView.$('.cohort-management-group-header .title-value').text()).toBe(''); }); - it('can upload a CSV of cohort assignments if a cohort exists', function () { - var uploadCsvToggle, fileUploadForm, fileUploadFormCss='#file-upload-form'; - - createCohortsView(this); - - // Should see the control to toggle CSV file upload. - expect(cohortsView.$('.wrapper-cohort-supplemental')).not.toHaveClass('is-hidden'); - // But upload form should not be visible until toggle is clicked. - expect(cohortsView.$(fileUploadFormCss).length).toBe(0); - uploadCsvToggle = cohortsView.$('.toggle-cohort-management-secondary'); - expect(uploadCsvToggle.text()). - toContain('Assign students to cohort groups by uploading a CSV file'); - uploadCsvToggle.click(); - // After toggle is clicked, it should be hidden. - expect(uploadCsvToggle).toHaveClass('is-hidden'); - - fileUploadForm = cohortsView.$(fileUploadFormCss); - expect(fileUploadForm.length).toBe(1); - cohortsView.$(fileUploadForm).fileupload('add', {files: [{name: 'upload_file.txt'}]}); - cohortsView.$('.submit-file-button').click(); - - // No file will actually be uploaded because "uploaded_file.txt" doesn't actually exist. - AjaxHelpers.expectRequest(requests, 'POST', "http://upload-csv-file-url/", new FormData()); - AjaxHelpers.respondWithJson(requests, {}); - expect(cohortsView.$('.file-upload-form-result .message-confirmation .message-title').text().trim()) - .toBe("Your file 'upload_file.txt' has been uploaded. Please allow a few minutes for processing."); - }); - it('can select a cohort', function () { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); verifyHeader(1, 'Cat Lovers', catLoversInitialCount); }); it('can switch cohort', function () { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); cohortsView.$('.cohort-select').val("2").change(); verifyHeader(2, 'Dog Lovers', dogLoversInitialCount); }); }); + describe("Cohort Editor Tab Panel", function () { + it("initially selects the Manage Students tab", function () { + createCohortsView(this, {selectCohort: 1}); + expect(cohortsView.$('.tab-manage_students')).toHaveClass('is-selected'); + expect(cohortsView.$('.tab-settings')).not.toHaveClass('is-selected'); + expect(cohortsView.$('.tab-content-manage_students')).not.toHaveClass('is-hidden'); + expect(cohortsView.$('.tab-content-settings')).toHaveClass('is-hidden'); + }); + + it("can select the Settings tab", function () { + createCohortsView(this, {selectCohort: 1}); + cohortsView.$('.tab-settings a').click(); + expect(cohortsView.$('.tab-manage_students')).not.toHaveClass('is-selected'); + expect(cohortsView.$('.tab-settings')).toHaveClass('is-selected'); + expect(cohortsView.$('.tab-content-manage_students')).toHaveClass('is-hidden'); + expect(cohortsView.$('.tab-content-settings')).not.toHaveClass('is-hidden'); + }); + }); + describe("Add Cohorts Form", function () { var defaultCohortName = 'New Cohort'; it("can add a cohort", function() { - createCohortsView(this, null, { cohorts: [] }); + var contentGroupId = 0; + createCohortsView(this, {cohorts: []}); cohortsView.$('.action-create').click(); - expect(cohortsView.$('.cohort-management-create-form').length).toBe(1); + expect(cohortsView.$('.cohort-management-settings-form').length).toBe(1); expect(cohortsView.$('.cohort-management-nav')).toHaveClass('is-disabled'); expect(cohortsView.$('.cohort-management-group')).toHaveClass('is-hidden'); - cohortsView.$('.cohort-create-name').val(defaultCohortName); + cohortsView.$('.cohort-name').val(defaultCohortName); + selectContentGroup(contentGroupId); cohortsView.$('.action-save').click(); - AjaxHelpers.expectRequest(requests, 'POST', '/mock_service/add', 'name=New+Cohort'); + expectCohortAddRequest(defaultCohortName, contentGroupId); AjaxHelpers.respondWithJson( requests, { - success: true, - cohort: { id: 1, name: defaultCohortName } + id: 1, + name: defaultCohortName, + group_id: contentGroupId } ); AjaxHelpers.respondWithJson( @@ -200,75 +266,60 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe verifyHeader(1, defaultCohortName, 0); expect(cohortsView.$('.cohort-management-nav')).not.toHaveClass('is-disabled'); expect(cohortsView.$('.cohort-management-group')).not.toHaveClass('is-hidden'); - expect(cohortsView.$('.cohort-management-create-form').length).toBe(0); + expect(getAddModal().find('.cohort-management-settings-form').length).toBe(0); }); it("trims off whitespace before adding a cohort", function() { createCohortsView(this); cohortsView.$('.action-create').click(); - cohortsView.$('.cohort-create-name').val(' New Cohort '); + cohortsView.$('.cohort-name').val(' New Cohort '); cohortsView.$('.action-save').click(); - AjaxHelpers.expectRequest(requests, 'POST', '/mock_service/add', 'name=New+Cohort'); + expectCohortAddRequest('New Cohort', null); }); it("does not allow a blank cohort name to be submitted", function() { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); cohortsView.$('.action-create').click(); - expect(cohortsView.$('.cohort-management-create-form').length).toBe(1); - cohortsView.$('.cohort-create-name').val(''); + expect(getAddModal().find('.cohort-management-settings-form').length).toBe(1); + cohortsView.$('.cohort-name').val(''); expect(cohortsView.$('.cohort-management-nav')).toHaveClass('is-disabled'); - cohortsView.$('.action-save').click(); + getAddModal().find('.action-save').click(); expect(requests.length).toBe(0); verifyMessage('Please enter a name for your new cohort group.', 'error'); }); - it("shows a message when adding a cohort throws a server error", function() { - createCohortsView(this, 1); + it("shows a message when adding a cohort returns a server error", function() { + var addModal; + createCohortsView(this, {selectCohort: 1}); cohortsView.$('.action-create').click(); - expect(cohortsView.$('.cohort-management-create-form').length).toBe(1); - cohortsView.$('.cohort-create-name').val(defaultCohortName); - cohortsView.$('.action-save').click(); - AjaxHelpers.expectRequest(requests, 'POST', '/mock_service/add', 'name=New+Cohort'); - AjaxHelpers.respondWithError(requests); + addModal = getAddModal(); + expect(addModal.find('.cohort-management-settings-form').length).toBe(1); + addModal.find('.cohort-name').val(defaultCohortName); + addModal.find('.action-save').click(); + AjaxHelpers.respondWithError(requests, 400, { + error: 'You cannot add two cohorts with the same name' + }); verifyHeader(1, 'Cat Lovers', catLoversInitialCount); verifyMessage( - "We've encountered an error. Please refresh your browser and then try again.", + 'You cannot add two cohorts with the same name', 'error' ); }); - it("shows a server message if adding a cohort fails", function() { - createCohortsView(this, 1); - cohortsView.$('.action-create').click(); - expect(cohortsView.$('.cohort-management-create-form').length).toBe(1); - cohortsView.$('.cohort-create-name').val('Cat Lovers'); - cohortsView.$('.action-save').click(); - AjaxHelpers.expectRequest(requests, 'POST', '/mock_service/add', 'name=Cat+Lovers'); - AjaxHelpers.respondWithJson( - requests, - { - success: false, - msg: 'You cannot create two cohorts with the same name' - } - ); - verifyHeader(1, 'Cat Lovers', catLoversInitialCount); - verifyMessage('You cannot create two cohorts with the same name', 'error'); - }); - it("is removed when 'Cancel' is clicked", function() { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); cohortsView.$('.action-create').click(); - expect(cohortsView.$('.cohort-management-create-form').length).toBe(1); + expect(getAddModal().find('.cohort-management-settings-form').length).toBe(1); expect(cohortsView.$('.cohort-management-nav')).toHaveClass('is-disabled'); cohortsView.$('.action-cancel').click(); - expect(cohortsView.$('.cohort-management-create-form').length).toBe(0); + expect(getAddModal().find('.cohort-management-settings-form').length).toBe(0); expect(cohortsView.$('.cohort-management-nav')).not.toHaveClass('is-disabled'); }); it("shows an error if canceled when no cohorts are defined", function() { - createCohortsView(this, null, { cohorts: [] }); + createCohortsView(this, {cohorts: []}); cohortsView.$('.action-create').click(); - expect(cohortsView.$('.cohort-management-create-form').length).toBe(1); + expect(getAddModal().find('.cohort-management-settings-form').length).toBe(1); expect(cohortsView.$('.cohort-management-nav')).toHaveClass('is-disabled'); cohortsView.$('.action-cancel').click(); verifyMessage( @@ -279,11 +330,11 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }); it("hides any error message when switching to show a cohort", function() { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); // First try to save a blank name to create a message cohortsView.$('.action-create').click(); - cohortsView.$('.cohort-create-name').val(''); + cohortsView.$('.cohort-name').val(''); cohortsView.$('.action-save').click(); verifyMessage('Please enter a name for your new cohort group.', 'error'); @@ -294,11 +345,11 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }); it("hides any error message when canceling the form", function() { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); // First try to save a blank name to create a message cohortsView.$('.action-create').click(); - cohortsView.$('.cohort-create-name').val(''); + cohortsView.$('.cohort-name').val(''); cohortsView.$('.action-save').click(); verifyMessage('Please enter a name for your new cohort group.', 'error'); @@ -328,7 +379,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }; it('shows an error when adding with no students specified', function() { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); addStudents(' '); expect(requests.length).toBe(0); verifyMessage('Please enter a username or email.', 'error'); @@ -337,9 +388,8 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe it('can add a single student', function() { var catLoversUpdatedCount = catLoversInitialCount + 1; - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); addStudents('student@sample.com'); - AjaxHelpers.expectRequest(requests, 'POST', '/mock_service/1/add', 'users=student%40sample.com'); respondToAdd({ added: ['student@sample.com'] }); respondToRefresh(catLoversUpdatedCount, dogLoversInitialCount); verifyHeader(1, 'Cat Lovers', catLoversUpdatedCount); @@ -348,9 +398,11 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }); it('shows an error when adding a student that does not exist', function() { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); addStudents('unknown@sample.com'); - AjaxHelpers.expectRequest(requests, 'POST', '/mock_service/1/add', 'users=unknown%40sample.com'); + AjaxHelpers.expectRequest( + requests, 'POST', '/mock_service/cohorts/1/add', 'users=unknown%40sample.com' + ); respondToAdd({ unknown: ['unknown@sample.com'] }); respondToRefresh(catLoversInitialCount, dogLoversInitialCount); verifyHeader(1, 'Cat Lovers', catLoversInitialCount); @@ -362,11 +414,12 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe it('shows a "view all" button when more than 5 students do not exist', function() { var sixUsers = 'unknown1@sample.com, unknown2@sample.com, unknown3@sample.com, unknown4@sample.com, unknown5@sample.com, unknown6@sample.com'; - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); addStudents(sixUsers); - AjaxHelpers.expectRequest(requests, 'POST', '/mock_service/1/add', - 'users=' + sixUsers.replace(/@/g, "%40").replace(/, /g, "%2C+") + AjaxHelpers.expectRequest( + requests, 'POST', '/mock_service/cohorts/1/add', + 'users=' + sixUsers.replace(/@/g, "%40").replace(/, /g, "%2C+") ); respondToAdd({ unknown: [ 'unknown1@sample.com', @@ -399,10 +452,10 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe it('shows students moved from one cohort to another', function() { var sixUsers = 'moved1@sample.com, moved2@sample.com, moved3@sample.com, alreadypresent@sample.com'; - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); addStudents(sixUsers); - AjaxHelpers.expectRequest(requests, 'POST', '/mock_service/1/add', + AjaxHelpers.expectRequest(requests, 'POST', '/mock_service/cohorts/1/add', 'users=' + sixUsers.replace(/@/g, "%40").replace(/, /g, "%2C+") ); respondToAdd({ @@ -426,7 +479,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }); it('shows a message when the add fails', function() { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); addStudents('student@sample.com'); AjaxHelpers.respondWithError(requests); verifyMessage('Error adding students.', 'error'); @@ -434,7 +487,7 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe }); it('clears an error message on subsequent add', function() { - createCohortsView(this, 1); + createCohortsView(this, {selectCohort: 1}); // First verify that an error is shown addStudents('student@sample.com'); @@ -448,5 +501,105 @@ define(['backbone', 'jquery', 'js/common_helpers/ajax_helpers', 'js/common_helpe verifyMessage('1 student has been added to this cohort group', 'confirmation'); }); }); + + describe("Cohort Settings", function() { + describe("Content Group Setting", function() { + it("shows a select element with an option for each content group", function () { + var options; + createCohortsView(this, {selectCohort: 1}); + cohortsView.$('.tab-settings a').click(); + options = cohortsView.$('.input-cohort-group-association option'); + expect(options.length).toBe(3); + expect($(options[0]).text().trim()).toBe('Choose a content group to associate'); + expect($(options[1]).text().trim()).toBe('Cat Content'); + expect($(options[2]).text().trim()).toBe('Dog Content'); + }); + + it("can select a single content group", function () { + createCohortsView(this, {selectCohort: 1}); + cohortsView.$('.tab-settings a').click(); + + // Select the content group with id 1 and verify the radio button was switched to 'Yes' + selectContentGroup(0); + expect(cohortsView.$('.radio-yes').prop('checked')).toBeTruthy(); + + // Click the save button and verify that the correct request is sent + cohortsView.$('.action-save').click(); + AjaxHelpers.expectJsonRequest( + requests, 'PATCH', '/mock_service/cohorts/1', + { + name: 'Cat Lovers', + group_id: 0 + } + ); + AjaxHelpers.respondWithJson( + requests, + createMockCohort('Cat Lovers', 1, catLoversInitialCount, 0, 0) + ); + verifyMessage('Saved cohort group.', 'confirmation'); + }); + + it("can clear selected content group", function () { + createCohortsView(this, { + cohorts: [ + {id: 1, name: 'Cat Lovers', group_id: 0} + ], + selectCohort: 1 + }); + cohortsView.$('.tab-settings a').click(); + expect(cohortsView.$('.radio-yes').prop('checked')).toBeTruthy(); + clearContentGroup(); + + // Click the save button and verify that the correct request is sent + cohortsView.$('.action-save').click(); + AjaxHelpers.expectJsonRequest( + requests, 'PATCH', '/mock_service/cohorts/1', + { + name: 'Cat Lovers', + group_id: null + } + ); + AjaxHelpers.respondWithJson( + requests, + createMockCohort('Cat Lovers', 1, catLoversInitialCount, 0, 0) + ); + verifyMessage('Saved cohort group.', 'confirmation'); + }); + + it("shows a message when the selected content group does not exist", function () { + createCohortsView(this, { + cohorts: [ + {id: 1, name: 'Cat Lovers', group_id: 999} + ], + selectCohort: 1 + }); + cohortsView.$('.tab-settings a').click(); + expect(cohortsView.$('.copy-error').text().trim()).toBe( + 'The selected content group has been deleted, you may wish to reassign this cohort group.' + ); + }); + + it("shows an error when the save fails", function () { + createCohortsView(this, {selectCohort: 1}); + cohortsView.$('.tab-settings a').click(); + cohortsView.$('.action-save').click(); + AjaxHelpers.respondWithError(requests); + verifyMessage( + 'We\'ve encountered an error. Please refresh your browser and then try again.', + 'error' + ); + }); + + it("shows an error message when no content groups are specified", function () { + createCohortsView(this, {selectCohort: 1, contentGroups: []}); + cohortsView.$('.tab-settings a').click(); + expect( + cohortsView.$('.msg-inline').text().trim(), + 'You haven\'t configured any content groups yet. You need to create a content group ' + + 'before you can create assignments.' + ); + }); + }); + }); }); }); diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index a0bebd885c..0bf09b003a 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -65,8 +65,10 @@ 'js/views/file_uploader': 'js/views/file_uploader', '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/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', 'js/groups/views/cohorts': 'js/groups/views/cohorts', 'js/student_account/account': 'js/student_account/account', 'js/student_account/views/FormView': 'js/student_account/views/FormView', @@ -284,17 +286,28 @@ exports: 'edx.groups.CohortModel', deps: ['backbone'] }, + 'js/groups/models/content_group': { + exports: 'edx.groups.ContentGroupModel', + deps: ['backbone'] + }, 'js/groups/collections/cohort': { exports: 'edx.groups.CohortCollection', deps: ['backbone', 'js/groups/models/cohort'] }, - 'js/groups/views/cohort_editor': { - exports: 'edx.groups.CohortsEditor', + 'js/groups/views/cohort_form': { + exports: 'edx.groups.CohortFormView', deps: [ 'backbone', 'jquery', 'underscore', 'js/views/notification', 'js/models/notification', 'string_utils' ] }, + 'js/groups/views/cohort_editor': { + exports: 'edx.groups.CohortEditorView', + deps: [ + 'backbone', 'jquery', 'underscore', 'js/views/notification', 'js/models/notification', + 'string_utils', 'js/groups/views/cohort_form' + ] + }, 'js/groups/views/cohorts': { exports: 'edx.groups.CohortsView', deps: [ diff --git a/lms/static/sass/course/instructor/_cohort-management.scss b/lms/static/sass/course/instructor/_cohort-management.scss index fbc61004e9..09415054d0 100644 --- a/lms/static/sass/course/instructor/_cohort-management.scss +++ b/lms/static/sass/course/instructor/_cohort-management.scss @@ -1,18 +1,3 @@ -.msg { - - &.inline { - @extend %t-copy-sub2; - display: inline-block; - margin: 0 0 0 $baseline; - padding: 0; - } - - &.error { - @extend %t-copy-sub2; - color: $error-red; - } -} - .cohort-management { .has-option-unavailable { // Given to that have some options that are unavailable @@ -59,34 +44,40 @@ @include clearfix(); background: $gray-l5; - .group-header-title { - padding: $baseline; - margin-bottom: 0; - border-bottom: 1px solid $gray-l3; - text-transform: none; - letter-spacing: normal; - } + .cohort-manage-group-header { + padding: 0; + border-bottom: 0; - .cohort-management-group-setup { - padding: $baseline; - - .cohort-management-group-text { - display: inline-block; - width: flex-grid(9); + .group-header-title { + padding: $baseline; + margin-bottom: 0; + border-bottom: 1px solid $gray-l3; + text-transform: none; + letter-spacing: normal; } - .cohort-management-group-actions { - display: inline-block; - width: flex-grid(3); - vertical-align: top; + .cohort-management-group-setup { + padding: $baseline; - .float-right { - float: right; + .cohort-management-group-text { + display: inline-block; + width: flex-grid(9); + } + + .cohort-management-group-actions { + display: inline-block; + width: flex-grid(3); + vertical-align: top; + + .float-right { + float: right; + } } } } - .cohort-management-details { + .cohort-management-details, + .cohort-management-group-add { border-top: ($baseline/5) solid $gray-l4; background: $white; @@ -149,4 +140,4 @@ } } } -} \ No newline at end of file +} diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index b7e29989a1..9986772314 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -17,7 +17,6 @@ .instructor-dashboard-wrapper-2 { position: relative; - // display: table; .olddash-button-wrapper { position: absolute; @@ -56,6 +55,14 @@ } } + // TYPE: inline + .msg-inline { + @extend %t-copy-sub2; + display: inline-block; + margin: 0 0 0 $baseline; + padding: 0; + } + // TYPE: warning .msg-warning { border-top: 2px solid $warning-color; @@ -125,16 +132,11 @@ // instructor dashboard 2 // ==================== -section.instructor-dashboard-content-2 { +.instructor-dashboard-content-2 { @extend .content; - // position: relative; padding: 40px; width: 100%; - // .has-event-handler-for-click { - // border: 1px solid blue; - // } - .wrap-instructor-info { display: inline; top: 0; @@ -222,6 +224,7 @@ section.instructor-dashboard-content-2 { // messages .message { + margin-top: $baseline; margin-bottom: $baseline; display: block; border-radius: 1px; @@ -426,6 +429,7 @@ section.instructor-dashboard-content-2 { } // view - membership +// -------------------- .instructor-dashboard-wrapper-2 section.idash-section#membership { .membership-section { @@ -538,11 +542,17 @@ section.instructor-dashboard-content-2 { } // create or edit cohort group - .cohort-management-create, .cohort-management-edit { + .cohort-management-settings, + .cohort-management-edit { @extend %cohort-management-form; border: 1px solid $gray-l5; margin-bottom: $baseline; + .message { + margin-left: $baseline; + margin-right: $baseline; + } + .form-title { @extend %t-title5; @extend %t-weight4; @@ -551,7 +561,7 @@ section.instructor-dashboard-content-2 { padding: $baseline; } - .form-fields { + .form-field { padding: $baseline; } @@ -640,6 +650,9 @@ section.instructor-dashboard-content-2 { @extend %t-title6; @extend %t-weight4; margin-bottom: ($baseline/4); + border: none; + background: transparent; + padding: 0; } .form-introduction { @@ -957,7 +970,148 @@ section.instructor-dashboard-content-2 { } } } + + /* + * Begin additional/override styles for cohort management. + * Placed for merge, but will need to be cleaned up and + * refactored in this stylesheet. + */ + + .has-other-input-text { // Given to groups which have an 'other' input that appears when needed + display: inline-block; + + label { + display: inline-block; + } + + .input-group-other { + display: inline; + position: relative; + overflow: auto; + width: 100%; + height: auto; + margin: 0 0 0 $baseline; + padding: inherit; + border: inherit; + clip: auto; + + &.is-hidden { + display: none; + } + + .input-cohort-group-association { + display: inline; + } + } + } + + .cohort-management-nav { + margin-bottom: $baseline; + } + + .cohort-management-settings { + @include clearfix(); + background: $gray-l5; + + .cohort-management-group-header { + padding: 0; + border-bottom: 0; + + .group-header-title { + padding: $baseline; + margin-bottom: 0; + border-bottom: 1px solid $gray-l3; + text-transform: none; + letter-spacing: normal; + } + + .cohort-management-group-setup { + padding: $baseline; + + .cohort-management-group-text { + display: inline-block; + width: flex-grid(9); + } + + .cohort-management-group-actions { + display: inline-block; + width: flex-grid(3); + vertical-align: top; + text-align: right; + } + } + } + + .cohort-management-details, + .cohort-management-group-add { + border-top: ($baseline/5) solid $gray-l4; + background: $white; + + .cohort-management-settings { + margin-bottom: 0; + border-top: 0; + background: $white; + } + + .cohort-details-name { + @extend %t-action1; + display: block; + width: 100%; + padding: ($baseline/2); + margin-bottom: ($baseline*2); + } + + .cohort-section-header { + margin-top: ($baseline*1.5); + padding: $baseline 0 ($baseline/2) 0; + } + + .cohort-section-header > .form-field { + padding-bottom: $baseline; + } + } + } + + .wrapper-tabs { // This applies to the tab-like interface that toggles between the student management and the group settings + @extend %ui-no-list; + @extend %ui-depth1; + position: relative; + top: (($baseline/5)); + padding: 0 $baseline; + + .tab { + position: relative; + display: inline-block; + + a { + display: inline-block; + padding: $baseline; + -webkit-transition: none; + -moz-transition: none; + -ms-transition: none; + -o-transition: none; + transition: none; + } + + &.is-selected { // Active or selected tabs (
  • ) get this class. Also useful for aria stuff if ever implemented in the future. + + a { + padding-bottom: ($baseline+($baseline/5)); + border-style: solid; + border-width: ($baseline/5) ($baseline/5) 0 ($baseline/5); + border-color: $gray-l4; + background: $white; + color: inherit; + cursor: default; + } + } + } + } } +/* + * End of additions/overrides + * Don't forget to refactor. + */ // view - student admin // -------------------- @@ -1771,5 +1925,3 @@ input[name="subject"] { left: 2em; right: auto; } - -@import '_cohort-management'; \ No newline at end of file diff --git a/lms/templates/instructor/instructor_dashboard_2/add-cohort-form.underscore b/lms/templates/instructor/instructor_dashboard_2/add-cohort-form.underscore deleted file mode 100644 index 3680de44b5..0000000000 --- a/lms/templates/instructor/instructor_dashboard_2/add-cohort-form.underscore +++ /dev/null @@ -1,26 +0,0 @@ -
    -
    - -

    <%- gettext('Add a New Cohort Group') %>

    - -
    -
    - - " required="required" /> -
    -
    - -
    - - <%- gettext('Cancel') %> -
    -
    -
    diff --git a/lms/templates/instructor/instructor_dashboard_2/cohort-editor.underscore b/lms/templates/instructor/instructor_dashboard_2/cohort-editor.underscore index 4bfc997c00..60a39690af 100644 --- a/lms/templates/instructor/instructor_dashboard_2/cohort-editor.underscore +++ b/lms/templates/instructor/instructor_dashboard_2/cohort-editor.underscore @@ -1,63 +1,72 @@ -
    -

    - <%- cohort.get('name') %> - <%- - interpolate( - ngettext('(contains %(student_count)s student)', '(contains %(student_count)s students)', cohort.get('user_count')), - { student_count: cohort.get('user_count') }, - true - ) - %> -

    -
    -
    - <% if (cohort.get('assignment_type') == "none") { %> - <%= gettext("Students are added to this group only when you provide their email addresses or usernames on this page.") %> - <%= gettext("What does this mean?") %> - <% } else { %> - <%= gettext("Students are added to this group automatically.") %> - <%= gettext("What does this mean?") %> - <% } %> -
    -
    - <% if (advanced_settings_url != "None") { %> - <%= gettext("Edit settings in Studio") %> - <% } %> -
    -
    -
    - - -
    -
    - -

    <%- gettext('Add students to this cohort group') %>

    - -
    -

    <%- gettext('Note: Students can only be in one cohort group. Adding students to this group overrides any previous group assignment.') %>

    -
    - -
    -
    - -
    -
    - - - - <%- gettext('You will not get notification for emails that bounce, so please double-check spelling.') %> +
    +
    +

    + <%- cohort.get('name') %> + <%- + interpolate( + ngettext('(contains %(student_count)s student)', '(contains %(student_count)s students)', cohort.get('user_count')), + { student_count: cohort.get('user_count') }, + true + ) + %> +

    +
    +
    + <% if (cohort.get('assignment_type') == "none") { %> + <%- gettext("Students are added to this group only when you provide their email addresses or usernames on this page.") %> + <%= gettext("What does this mean?") %> + <% } else { %> + <%- gettext("Students are added to this group automatically.") %> + <%- gettext("What does this mean?") %> + <% } %> +
    +
    + <% if (advanced_settings_url != "None") { %> + <%- gettext("Edit settings in Studio") %> + <% } %>
    +
    -
    - -
    - -
    + + +
    +
    + +

    <%- gettext('Add students to this cohort group') %>

    + +
    +

    <%- gettext('Note: Students can only be in one cohort group. Adding students to this group overrides any previous group assignment.') %>

    +
    + +
    +
    + +
    +
    + + + + <%- gettext('You will not get notification for emails that bounce, so please double-check spelling.') %> +
    +
    + +
    + +
    +
    +
    + + + diff --git a/lms/templates/instructor/instructor_dashboard_2/cohort-form.underscore b/lms/templates/instructor/instructor_dashboard_2/cohort-form.underscore new file mode 100644 index 0000000000..0311df107f --- /dev/null +++ b/lms/templates/instructor/instructor_dashboard_2/cohort-form.underscore @@ -0,0 +1,97 @@ +<% var isNewCohort = cohort.id == null; %> +
    +
    + <% if (isNewCohort) { %> +

    <%- gettext('Add a New Cohort Group') %>

    + <% } %> + +
    + <% + // Don't allow renaming of existing cohorts yet as it doesn't interact well with + // the course's advanced setting for auto cohorting. + if (isNewCohort) { + %> +
    +
    + + " required="required" /> +
    +
    + <% } %> + + <% + var foundSelected = false; + var selectedContentGroupId = cohort.get('group_id'); + var hasSelectedContentGroup = selectedContentGroupId != null; + %> +
    +
    + + +
    + + <% if (contentGroups.length > 0) { %> +
    + + + + <% if (hasSelectedContentGroup && !foundSelected) { %> +
    +

    <%- gettext("The selected content group has been deleted, you may wish to reassign this cohort group.") %>

    +
    + <% } %> +
    + <% } else { // no content groups available %> +
    +
    +

    You haven't configured any content groups yet. You need to create a content group before you can create assignments. Create a content group

    +
    +
    + <% } %> +
    +
    +
    +
    + +
    + + <% if (isNewCohort) { %> + <%- gettext('Cancel') %> + <% } %> +
    +
    +
    diff --git a/lms/templates/instructor/instructor_dashboard_2/cohorts.underscore b/lms/templates/instructor/instructor_dashboard_2/cohorts.underscore index 8113089642..faf4efe4c6 100644 --- a/lms/templates/instructor/instructor_dashboard_2/cohorts.underscore +++ b/lms/templates/instructor/instructor_dashboard_2/cohorts.underscore @@ -24,6 +24,9 @@
    + +
    +
    diff --git a/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html b/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html index 733b5a90ab..04d618216a 100644 --- a/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html +++ b/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html @@ -58,14 +58,16 @@ + + ## Include Underscore templates <%block name="header_extras"> -% for template_name in ["cohorts", "cohort-editor", "cohort-selector", "add-cohort-form", "notification"]: +% for template_name in ["cohorts", "cohort-editor", "cohort-selector", "cohort-form", "notification"]: diff --git a/lms/templates/instructor/instructor_dashboard_2/membership.html b/lms/templates/instructor/instructor_dashboard_2/membership.html index 3d61a5ada6..1e198a0ebc 100644 --- a/lms/templates/instructor/instructor_dashboard_2/membership.html +++ b/lms/templates/instructor/instructor_dashboard_2/membership.html @@ -1,6 +1,7 @@ <%! from django.utils.translation import ugettext as _ %> <%page args="section_data"/> <%! from microsite_configuration import microsite %> +<%! from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition %>