Commit b77e65cd by Andy Armstrong

Assign cohorts to groups in the instructor dashboard

TNL-653
parent 674bfc4f
......@@ -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
......
......@@ -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',
......
......@@ -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()
......
......@@ -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):
"""
......
var edx = edx || {};
(function(Backbone) {
'use strict';
edx.groups = edx.groups || {};
edx.groups.ContentGroupModel = Backbone.Model.extend({
idAttribute: 'id',
defaults: {
name: ''
}
});
}).call(this, Backbone);
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);
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);
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);
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('<ul class="instructor-nav"><li class="nav-item"><<a href data-section="membership" class="active-section">Membership</a></li></ul><div></div>');
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.'
);
});
});
});
});
});
......@@ -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: [
......
.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 <selects> 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-management-group-setup {
padding: $baseline;
.cohort-manage-group-header {
padding: 0;
border-bottom: 0;
.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
}
......@@ -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 (<li>) 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
<div class="cohort-management-create">
<form action="" method="post" name="" id="cohort-management-create-form" class="cohort-management-create-form">
<h3 class="form-title"><%- gettext('Add a New Cohort Group') %></h3>
<div class="form-fields">
<div class="cohort-management-create-form-name field field-text">
<label for="cohort-create-name" class="label">
<%- gettext('New Cohort Name') %> *
<span class="sr"><%- gettext('(Required Field)')%></span>
</label>
<input type="text" name="cohort-create-name" value="" class="input cohort-create-name"
id="cohort-create-name"
placeholder="<%- gettext("Enter Your New Cohort Group's Name") %>" required="required" />
</div>
</div>
<div class="form-actions">
<button class="form-submit button action-primary action-save">
<i class="icon fa fa-plus"></i>
<%- gettext('Save') %>
</button>
<a href="" class="form-cancel action-secondary action-cancel"><%- gettext('Cancel') %></a>
</div>
</form>
</div>
<header class="cohort-management-group-header">
<h3 class="group-header-title">
<span class="title-value"><%- cohort.get('name') %></span>
<span class="group-count"><%-
interpolate(
ngettext('(contains %(student_count)s student)', '(contains %(student_count)s students)', cohort.get('user_count')),
{ student_count: cohort.get('user_count') },
true
)
%></span>
</h3>
<div class="cohort-management-group-setup">
<div class="setup-value">
<% 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.") %>
<a href="http://edx.readthedocs.org/projects/edx-partner-course-staff/en/latest/cohorts/cohort_config.html#assign-students-to-cohort-groups-manually" class="incontext-help action-secondary action-help"><%= gettext("What does this mean?") %></a>
<% } else { %>
<%= gettext("Students are added to this group automatically.") %>
<a href="http://edx.readthedocs.org/projects/edx-partner-course-staff/en/latest/cohorts/cohorts_overview.html#all-automated-assignment" class="incontext-help action-secondary action-help"><%= gettext("What does this mean?") %></a>
<% } %>
</div>
<div class="setup-actions">
<% if (advanced_settings_url != "None") { %>
<a href="<%= advanced_settings_url %>" class="action-secondary action-edit"><%= gettext("Edit settings in Studio") %></a>
<% } %>
<section class="cohort-management-settings has-tabs">
<header class="cohort-management-group-header">
<h3 class="group-header-title">
<span class="title-value"><%- cohort.get('name') %></span>
<span class="group-count"><%-
interpolate(
ngettext('(contains %(student_count)s student)', '(contains %(student_count)s students)', cohort.get('user_count')),
{ student_count: cohort.get('user_count') },
true
)
%></span>
</h3>
<div class="cohort-management-group-setup">
<div class="setup-value">
<% 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.") %>
<a href="http://edx.readthedocs.org/projects/edx-partner-course-staff/en/latest/cohorts/cohort_config.html#assign-students-to-cohort-groups-manually" class="incontext-help action-secondary action-help"><%= gettext("What does this mean?") %></a>
<% } else { %>
<%- gettext("Students are added to this group automatically.") %>
<a href="http://edx.readthedocs.org/projects/edx-partner-course-staff/en/latest/cohorts/cohorts_overview.html#all-automated-assignment" class="incontext-help action-secondary action-help"><%- gettext("What does this mean?") %></a>
<% } %>
</div>
<div class="setup-actions">
<% if (advanced_settings_url != "None") { %>
<a href="<%= advanced_settings_url %>" class="action-secondary action-edit"><%- gettext("Edit settings in Studio") %></a>
<% } %>
</div>
</div>
</div>
</header>
</header>
<!-- individual group - form -->
<div class="cohort-management-group-add">
<form action="" method="post" id="cohort-management-group-add-form" class="cohort-management-group-add-form">
<ul class="wrapper-tabs">
<li class="tab tab-manage_students is-selected" data-tab="manage_students"><a href="#"><%- gettext("Manage Students") %></a></li>
<li class="tab tab-settings" data-tab="settings"><a href="#"><%- gettext("Settings") %></a></li>
</ul>
<h4 class="form-title"><%- gettext('Add students to this cohort group') %></h4>
<div class="cohort-management-group-add tab-content tab-content-manage_students">
<form action="" method="post" id="cohort-management-group-add-form" class="cohort-management-group-add-form">
<div class="form-introduction">
<p><%- gettext('Note: Students can only be in one cohort group. Adding students to this group overrides any previous group assignment.') %></p>
</div>
<h4 class="form-title"><%- gettext('Add students to this cohort group') %></h4>
<div class="form-introduction">
<p><%- gettext('Note: Students can only be in one cohort group. Adding students to this group overrides any previous group assignment.') %></p>
</div>
<div class="cohort-confirmations"></div>
<div class="cohort-errors"></div>
<div class="cohort-confirmations"></div>
<div class="cohort-errors"></div>
<div class="form-fields">
<div class="field field-textarea is-required">
<label for="cohort-management-group-add-students" class="label">
<%- gettext('Enter email addresses and/or usernames separated by new lines or commas for students to add. *') %>
<span class="sr"><%- gettext('(Required Field)') %></span>
</label>
<textarea name="cohort-management-group-add-students" id="cohort-management-group-add-students"
class="input cohort-management-group-add-students"
placeholder="<%- gettext('e.g. johndoe@example.com, JaneDoe, joeydoe@example.com') %>"></textarea>
<div class="form-fields">
<div class="field field-textarea is-required">
<label for="cohort-management-group-add-students" class="label">
<%- gettext('Enter email addresses and/or usernames separated by new lines or commas for students to add. *') %>
<span class="sr"><%- gettext('(Required Field)') %></span>
</label>
<textarea name="cohort-management-group-add-students" id="cohort-management-group-add-students"
class="input cohort-management-group-add-students"
placeholder="<%- gettext('e.g. johndoe@example.com, JaneDoe, joeydoe@example.com') %>"></textarea>
<span class="tip"><%- gettext('You will not get notification for emails that bounce, so please double-check spelling.') %></span>
<span class="tip"><%- gettext('You will not get notification for emails that bounce, so please double-check spelling.') %></span>
</div>
</div>
</div>
<div class="form-actions">
<button class="form-submit button action-primary action-view">
<i class="button-icon icon fa fa-plus"></i> <%- gettext('Add Students') %>
</button>
</div>
</form>
</div>
<div class="form-actions">
<button class="form-submit button action-primary action-view">
<i class="button-icon icon fa fa-plus"></i> <%- gettext('Add Students') %>
</button>
</div>
</form>
</div>
<div class="cohort-management-details tab-content tab-content-settings is-hidden">
</div>
</section>
<% var isNewCohort = cohort.id == null; %>
<div class="cohort-management-settings">
<form action="" method="post" name="" id="cohort-management-settings-form" class="cohort-management-settings-form">
<% if (isNewCohort) { %>
<h3 class="form-title"><%- gettext('Add a New Cohort Group') %></h3>
<% } %>
<div class="form-fields">
<%
// 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) {
%>
<div class="form-field">
<div class="cohort-management-settings-form-name field field-text">
<label for="cohort-name" class="label">
<%- gettext('Cohort Name') %> *
<span class="sr"><%- gettext('(Required Field)')%></span>
</label>
<input type="text" name="cohort-name" value="<%- cohort ? cohort.get('name') : '' %>" class="input cohort-name"
id="cohort-name"
placeholder="<%- gettext("Enter Your Cohort Group's Name") %>" required="required" />
</div>
</div>
<% } %>
<%
var foundSelected = false;
var selectedContentGroupId = cohort.get('group_id');
var hasSelectedContentGroup = selectedContentGroupId != null;
%>
<div class="form-field">
<div class="cohort-management-details-association-course field field-radio">
<label class="label">
<%- gettext('Is this cohort group associated with course-based content groups?') %>
</label>
<label><input type="radio" class="radio-no" name="cohort-association-course" value="no" <%- !hasSelectedContentGroup ? 'checked="checked"' : '' %>/> <%- gettext("No") %></label>
<div class="input-group has-other-input-text">
<label><input type="radio" class="radio-yes" name="cohort-association-course" value="yes" <%- hasSelectedContentGroup ? 'checked="checked"' : '' %> /> <%- gettext("Yes") %></label>
<% if (contentGroups.length > 0) { %>
<div class="input-group-other">
<label class="sr" for="cohort-group-association"><%- gettext("Choose a content group to associate") %></label>
<select name="cohort-group-association" class="input input-lg has-option-unavailable input-cohort-group-association">
<option value="None"><%- gettext("Choose a content group to associate") %></option>
<%
var orderedContentGroups = _.sortBy(
contentGroups,
function(group) { return group.get('name'); }
);
for (var i=0; i < orderedContentGroups.length; i++) {
var contentGroup = orderedContentGroups[i];
var contentGroupId = contentGroup.get('id');
var isSelected = contentGroupId == selectedContentGroupId;
if (isSelected) {
foundSelected = true;
}
%>
<option value="<%- contentGroupId %>" <%- isSelected ? 'selected="selected"' : '' %>><%- contentGroup.get('name') %></option>
<%
}
%>
<% if (hasSelectedContentGroup && !foundSelected) { %>
<option value="<%- contentGroupId %>" class="option-unavailable" selected="selected"><%- gettext("Some content group that's been deleted") %></option>
<% } %>
</select>
<% if (hasSelectedContentGroup && !foundSelected) { %>
<div class="msg-inline">
<p class="copy-error"><i class="icon icon-warning-sign"></i><%- gettext("The selected content group has been deleted, you may wish to reassign this cohort group.") %></p>
</div>
<% } %>
</div>
<% } else { // no content groups available %>
<div class="input-group-other is-visible">
<div class="msg-inline">
<p class="copy-error"><i class="icon icon-warning-sign"></i> You haven't configured any content groups yet. You need to create a content group before you can create assignments. <a href="#">Create a content group</a></p>
</div>
</div>
<% } %>
</div>
</div>
</div>
</div>
<div class="form-actions">
<button class="form-submit button action-primary action-save">
<i class="icon fa fa-plus"></i>
<%- gettext('Save') %>
</button>
<% if (isNewCohort) { %>
<a href="" class="form-cancel action-secondary action-cancel"><%- gettext('Cancel') %></a>
<% } %>
</div>
</form>
</div>
......@@ -24,6 +24,9 @@
</a>
</div>
<!-- Add modal -->
<div class="cohort-management-add-modal"></div>
<!-- individual group -->
<div class="cohort-management-group"></div>
......
......@@ -58,14 +58,16 @@
<script type="text/javascript" src="${static.url('js/views/notification.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/file_uploader.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/models/cohort.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/models/content_group.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/collections/cohort.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/cohort_form.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/cohort_editor.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/cohorts.js')}"></script>
</%block>
## 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"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" />
</script>
......
<%! 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 %>
<script type="text/template" id="member-list-widget-template">
<div class="member-list-widget">
......@@ -244,7 +245,7 @@
</div>
%if course.is_cohorted:
% if course.is_cohorted:
<hr class="divider" />
<div class="cohort-management membership-section"
data-ajax_url="${section_data['cohorts_ajax_url']}"
......@@ -254,15 +255,29 @@
</div>
<%block name="headextra">
<%
cohorted_user_partition = get_cohorted_user_partition(course.id)
content_groups = cohorted_user_partition.groups if cohorted_user_partition else []
%>
<script>
$(document).ready(function() {
var cohortManagementElement = $('.cohort-management');
if (cohortManagementElement.length > 0) {
var cohorts = new edx.groups.CohortCollection();
var cohorts = new edx.groups.CohortCollection(),
contentGroups = [
% for content_group in content_groups:
new edx.groups.ContentGroupModel({
id: ${content_group.id},
name: "${content_group.name | h}"
}),
% endfor
];
cohorts.url = cohortManagementElement.data('ajax_url');
var cohortsView = new edx.groups.CohortsView({
el: cohortManagementElement,
model: cohorts,
cohortUserPartitionId: ${cohorted_user_partition.id if cohorted_user_partition else 'null'},
contentGroups: contentGroups,
advanced_settings_url: cohortManagementElement.data('advanced-settings-url'),
upload_cohorts_csv_url: cohortManagementElement.data('upload_cohorts_csv_url')
});
......
......@@ -43,7 +43,6 @@
<section class="instructor-dashboard-content-2" id="instructor-dashboard-content">
<h1>Instructor Dashboard</h1>
<section class="idash-section active-section" id="membership">
<div class="cohort-management membership-section">
<h2 class="section-title">
<span class="value">Cohort Group Management</span>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment