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 ...@@ -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 Notes page. TNL-797
LMS: Student Notes: Add possibility to add/edit/remove notes. TNL-655 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 Platform: Add group_access field to all xblocks. TNL-670
......
...@@ -28,7 +28,6 @@ class CourseMetadata(object): ...@@ -28,7 +28,6 @@ class CourseMetadata(object):
'graded', 'graded',
'hide_from_toc', 'hide_from_toc',
'pdf_textbooks', 'pdf_textbooks',
'user_partitions',
'name', # from xblock 'name', # from xblock
'tags', # from xblock 'tags', # from xblock
'visible_to_staff_only', 'visible_to_staff_only',
......
...@@ -153,7 +153,7 @@ class MembershipPageCohortManagementSection(PageObject): ...@@ -153,7 +153,7 @@ class MembershipPageCohortManagementSection(PageObject):
Adds a new manual cohort with the specified name. Adds a new manual cohort with the specified name.
""" """
self.q(css=self._bounded_selector("div.cohort-management-nav .action-create")).first.click() 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) textinput.send_keys(cohort_name)
self.q(css=self._bounded_selector("div.form-actions .action-save")).first.click() self.q(css=self._bounded_selector("div.form-actions .action-save")).first.click()
......
...@@ -3,6 +3,7 @@ Helper functions and classes for discussion tests. ...@@ -3,6 +3,7 @@ Helper functions and classes for discussion tests.
""" """
from uuid import uuid4 from uuid import uuid4
import json
from ...fixtures.discussion import ( from ...fixtures.discussion import (
SingleThreadViewFixture, SingleThreadViewFixture,
...@@ -68,11 +69,11 @@ class CohortTestMixin(object): ...@@ -68,11 +69,11 @@ class CohortTestMixin(object):
""" """
Adds a cohort group by name, returning the ID for the group. Adds a cohort group by name, returning the ID for the group.
""" """
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/add' url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/'
data = {"name": cohort_name} data = json.dumps({"name": cohort_name})
response = course_fixture.session.post(url, data=data, headers=course_fixture.headers) response = course_fixture.session.post(url, data=data, headers=course_fixture.headers)
self.assertTrue(response.ok, "Failed to create cohort") 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): 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 || {}; var edx = edx || {};
(function(Backbone, _, $, gettext, ngettext, interpolate_text, NotificationModel, NotificationView) { (function(Backbone, _, $, gettext, ngettext, interpolate_text, CohortFormView, NotificationModel, NotificationView) {
'use strict'; 'use strict';
edx.groups = edx.groups || {}; edx.groups = edx.groups || {};
edx.groups.CohortEditorView = Backbone.View.extend({ edx.groups.CohortEditorView = Backbone.View.extend({
events : { 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) { initialize: function(options) {
this.template = _.template($('#cohort-editor-tpl').text()); this.template = _.template($('#cohort-editor-tpl').text());
this.cohorts = options.cohorts; this.cohorts = options.cohorts;
this.cohortUserPartitionId = options.cohortUserPartitionId;
this.contentGroups = options.contentGroups;
this.advanced_settings_url = options.advanced_settings_url; this.advanced_settings_url = options.advanced_settings_url;
}, },
...@@ -24,11 +28,35 @@ var edx = edx || {}; ...@@ -24,11 +28,35 @@ var edx = edx || {};
render: function() { render: function() {
this.$el.html(this.template({ this.$el.html(this.template({
cohort: this.model, cohort: this.model,
cohortUserPartitionId: this.cohortUserPartitionId,
contentGroups: this.contentGroups,
advanced_settings_url: this.advanced_settings_url 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; 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) { setCohort: function(cohort) {
this.model = cohort; this.model = cohort;
this.render(); this.render();
...@@ -208,4 +236,5 @@ var edx = edx || {}; ...@@ -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 || {}; var edx = edx || {};
(function($, _, Backbone, gettext, interpolate_text, CohortEditorView, (function($, _, Backbone, gettext, interpolate_text, CohortModel, CohortEditorView, CohortFormView,
NotificationModel, NotificationView, FileUploaderView) { NotificationModel, NotificationView, FileUploaderView) {
'use strict'; 'use strict';
...@@ -13,8 +13,8 @@ var edx = edx || {}; ...@@ -13,8 +13,8 @@ var edx = edx || {};
events : { events : {
'change .cohort-select': 'onCohortSelected', 'change .cohort-select': 'onCohortSelected',
'click .action-create': 'showAddCohortForm', 'click .action-create': 'showAddCohortForm',
'click .action-cancel': 'cancelAddCohortForm', 'click .cohort-management-add-modal .action-save': 'saveAddCohortForm',
'click .action-save': 'saveAddCohortForm', 'click .cohort-management-add-modal .action-cancel': 'cancelAddCohortForm',
'click .link-cross-reference': 'showSection', 'click .link-cross-reference': 'showSection',
'click .toggle-cohort-management-secondary': 'showCsvUpload' 'click .toggle-cohort-management-secondary': 'showCsvUpload'
}, },
...@@ -24,9 +24,10 @@ var edx = edx || {}; ...@@ -24,9 +24,10 @@ var edx = edx || {};
this.template = _.template($('#cohorts-tpl').text()); this.template = _.template($('#cohorts-tpl').text());
this.selectorTemplate = _.template($('#cohort-selector-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.advanced_settings_url = options.advanced_settings_url;
this.upload_cohorts_csv_url = options.upload_cohorts_csv_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); model.on('sync', this.onSync, this);
// Update cohort counts when the user clicks back on the membership tab // Update cohort counts when the user clicks back on the membership tab
...@@ -52,13 +53,17 @@ var edx = edx || {}; ...@@ -52,13 +53,17 @@ var edx = edx || {};
})); }));
}, },
onSync: function() { onSync: function(model, response, options) {
var selectedCohort = this.lastSelectedCohortId && this.model.get(this.lastSelectedCohortId), var selectedCohort = this.lastSelectedCohortId && this.model.get(this.lastSelectedCohortId),
hasCohorts = this.model.length > 0, hasCohorts = this.model.length > 0,
cohortNavElement = this.$('.cohort-management-nav'), 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(); 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); cohortNavElement.removeClass(hiddenClass);
additionalCohortControlElement.removeClass(hiddenClass); additionalCohortControlElement.removeClass(hiddenClass);
this.renderSelector(selectedCohort); this.renderSelector(selectedCohort);
...@@ -99,6 +104,8 @@ var edx = edx || {}; ...@@ -99,6 +104,8 @@ var edx = edx || {};
el: this.$('.cohort-management-group'), el: this.$('.cohort-management-group'),
model: cohort, model: cohort,
cohorts: this.model, cohorts: this.model,
cohortUserPartitionId: this.cohortUserPartitionId,
contentGroups: this.contentGroups,
advanced_settings_url: this.advanced_settings_url advanced_settings_url: this.advanced_settings_url
}); });
this.editor.render(); this.editor.render();
...@@ -122,21 +129,32 @@ var edx = edx || {}; ...@@ -122,21 +129,32 @@ var edx = edx || {};
if (this.notification) { if (this.notification) {
this.notification.remove(); this.notification.remove();
} }
if (this.cohortFormView) {
this.cohortFormView.removeNotification();
}
}, },
showAddCohortForm: function(event) { showAddCohortForm: function(event) {
var newCohort;
event.preventDefault(); event.preventDefault();
this.removeNotification(); this.removeNotification();
this.addCohortForm = $(this.addCohortFormTemplate({})); newCohort = new CohortModel();
this.addCohortForm.insertAfter(this.$('.cohort-management-nav')); 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); this.setCohortEditorVisibility(false);
}, },
hideAddCohortForm: function() { hideAddCohortForm: function() {
this.setCohortEditorVisibility(true); this.setCohortEditorVisibility(true);
if (this.addCohortForm) { if (this.cohortFormView) {
this.addCohortForm.remove(); this.cohortFormView.remove();
this.addCohortForm = null; this.cohortFormView = null;
} }
}, },
...@@ -151,42 +169,23 @@ var edx = edx || {}; ...@@ -151,42 +169,23 @@ var edx = edx || {};
}, },
saveAddCohortForm: function(event) { saveAddCohortForm: function(event) {
event.preventDefault();
var self = this, var self = this,
showAddError, newCohort = this.cohortFormView.model;
cohortName = this.$('.cohort-create-name').val().trim(); event.preventDefault();
showAddError = function(message) {
self.showNotification(
{type: 'error', title: message},
self.$('.cohort-management-create-form-name label')
);
};
this.removeNotification(); this.removeNotification();
if (cohortName.length > 0) { this.cohortFormView.saveForm()
$.post( .done(function() {
this.model.url + '/add', self.lastSelectedCohortId = newCohort.id;
{name: cohortName} self.model.fetch().done(function() {
).done(function(result) { self.showNotification({
if (result.success) { type: 'confirmation',
self.lastSelectedCohortId = result.cohort.id; title: interpolate_text(
self.model.fetch().done(function() { gettext('The {cohortGroupName} cohort group has been created. You can manually add students to this group below.'),
self.showNotification({ {cohortGroupName: newCohort.get('name')}
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."));
}); });
} else { });
showAddError(gettext('Please enter a name for your new cohort group.'));
}
}, },
cancelAddCohortForm: function(event) { cancelAddCohortForm: function(event) {
...@@ -234,5 +233,5 @@ var edx = edx || {}; ...@@ -234,5 +233,5 @@ var edx = edx || {};
return ".instructor-nav .nav-item a[data-section='" + section + "']"; return ".instructor-nav .nav-item a[data-section='" + section + "']";
} }
}); });
}).call(this, $, _, Backbone, gettext, interpolate_text, edx.groups.CohortEditorView, }).call(this, $, _, Backbone, gettext, interpolate_text, edx.groups.CohortModel, edx.groups.CohortEditorView,
NotificationModel, NotificationView, FileUploaderView); edx.groups.CohortFormView, NotificationModel, NotificationView, FileUploaderView);
...@@ -65,8 +65,10 @@ ...@@ -65,8 +65,10 @@
'js/views/file_uploader': 'js/views/file_uploader', 'js/views/file_uploader': 'js/views/file_uploader',
'js/views/notification': 'js/views/notification', 'js/views/notification': 'js/views/notification',
'js/groups/models/cohort': 'js/groups/models/cohort', '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/collections/cohort': 'js/groups/collections/cohort',
'js/groups/views/cohort_editor': 'js/groups/views/cohort_editor', '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/groups/views/cohorts': 'js/groups/views/cohorts',
'js/student_account/account': 'js/student_account/account', 'js/student_account/account': 'js/student_account/account',
'js/student_account/views/FormView': 'js/student_account/views/FormView', 'js/student_account/views/FormView': 'js/student_account/views/FormView',
...@@ -284,17 +286,28 @@ ...@@ -284,17 +286,28 @@
exports: 'edx.groups.CohortModel', exports: 'edx.groups.CohortModel',
deps: ['backbone'] deps: ['backbone']
}, },
'js/groups/models/content_group': {
exports: 'edx.groups.ContentGroupModel',
deps: ['backbone']
},
'js/groups/collections/cohort': { 'js/groups/collections/cohort': {
exports: 'edx.groups.CohortCollection', exports: 'edx.groups.CohortCollection',
deps: ['backbone', 'js/groups/models/cohort'] deps: ['backbone', 'js/groups/models/cohort']
}, },
'js/groups/views/cohort_editor': { 'js/groups/views/cohort_form': {
exports: 'edx.groups.CohortsEditor', exports: 'edx.groups.CohortFormView',
deps: [ deps: [
'backbone', 'jquery', 'underscore', 'js/views/notification', 'js/models/notification', 'backbone', 'jquery', 'underscore', 'js/views/notification', 'js/models/notification',
'string_utils' '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': { 'js/groups/views/cohorts': {
exports: 'edx.groups.CohortsView', exports: 'edx.groups.CohortsView',
deps: [ 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 { .cohort-management {
.has-option-unavailable { // Given to <selects> that have some options that are unavailable .has-option-unavailable { // Given to <selects> that have some options that are unavailable
...@@ -59,34 +44,40 @@ ...@@ -59,34 +44,40 @@
@include clearfix(); @include clearfix();
background: $gray-l5; background: $gray-l5;
.group-header-title { .cohort-manage-group-header {
padding: $baseline; padding: 0;
margin-bottom: 0; border-bottom: 0;
border-bottom: 1px solid $gray-l3;
text-transform: none;
letter-spacing: normal;
}
.cohort-management-group-setup {
padding: $baseline;
.cohort-management-group-text { .group-header-title {
display: inline-block; padding: $baseline;
width: flex-grid(9); margin-bottom: 0;
border-bottom: 1px solid $gray-l3;
text-transform: none;
letter-spacing: normal;
} }
.cohort-management-group-actions { .cohort-management-group-setup {
display: inline-block; padding: $baseline;
width: flex-grid(3);
vertical-align: top;
.float-right { .cohort-management-group-text {
float: right; 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; border-top: ($baseline/5) solid $gray-l4;
background: $white; background: $white;
...@@ -149,4 +140,4 @@ ...@@ -149,4 +140,4 @@
} }
} }
} }
} }
\ No newline at end of file
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
.instructor-dashboard-wrapper-2 { .instructor-dashboard-wrapper-2 {
position: relative; position: relative;
// display: table;
.olddash-button-wrapper { .olddash-button-wrapper {
position: absolute; position: absolute;
...@@ -56,6 +55,14 @@ ...@@ -56,6 +55,14 @@
} }
} }
// TYPE: inline
.msg-inline {
@extend %t-copy-sub2;
display: inline-block;
margin: 0 0 0 $baseline;
padding: 0;
}
// TYPE: warning // TYPE: warning
.msg-warning { .msg-warning {
border-top: 2px solid $warning-color; border-top: 2px solid $warning-color;
...@@ -125,16 +132,11 @@ ...@@ -125,16 +132,11 @@
// instructor dashboard 2 // instructor dashboard 2
// ==================== // ====================
section.instructor-dashboard-content-2 { .instructor-dashboard-content-2 {
@extend .content; @extend .content;
// position: relative;
padding: 40px; padding: 40px;
width: 100%; width: 100%;
// .has-event-handler-for-click {
// border: 1px solid blue;
// }
.wrap-instructor-info { .wrap-instructor-info {
display: inline; display: inline;
top: 0; top: 0;
...@@ -222,6 +224,7 @@ section.instructor-dashboard-content-2 { ...@@ -222,6 +224,7 @@ section.instructor-dashboard-content-2 {
// messages // messages
.message { .message {
margin-top: $baseline;
margin-bottom: $baseline; margin-bottom: $baseline;
display: block; display: block;
border-radius: 1px; border-radius: 1px;
...@@ -426,6 +429,7 @@ section.instructor-dashboard-content-2 { ...@@ -426,6 +429,7 @@ section.instructor-dashboard-content-2 {
} }
// view - membership // view - membership
// --------------------
.instructor-dashboard-wrapper-2 section.idash-section#membership { .instructor-dashboard-wrapper-2 section.idash-section#membership {
.membership-section { .membership-section {
...@@ -538,11 +542,17 @@ section.instructor-dashboard-content-2 { ...@@ -538,11 +542,17 @@ section.instructor-dashboard-content-2 {
} }
// create or edit cohort group // create or edit cohort group
.cohort-management-create, .cohort-management-edit { .cohort-management-settings,
.cohort-management-edit {
@extend %cohort-management-form; @extend %cohort-management-form;
border: 1px solid $gray-l5; border: 1px solid $gray-l5;
margin-bottom: $baseline; margin-bottom: $baseline;
.message {
margin-left: $baseline;
margin-right: $baseline;
}
.form-title { .form-title {
@extend %t-title5; @extend %t-title5;
@extend %t-weight4; @extend %t-weight4;
...@@ -551,7 +561,7 @@ section.instructor-dashboard-content-2 { ...@@ -551,7 +561,7 @@ section.instructor-dashboard-content-2 {
padding: $baseline; padding: $baseline;
} }
.form-fields { .form-field {
padding: $baseline; padding: $baseline;
} }
...@@ -640,6 +650,9 @@ section.instructor-dashboard-content-2 { ...@@ -640,6 +650,9 @@ section.instructor-dashboard-content-2 {
@extend %t-title6; @extend %t-title6;
@extend %t-weight4; @extend %t-weight4;
margin-bottom: ($baseline/4); margin-bottom: ($baseline/4);
border: none;
background: transparent;
padding: 0;
} }
.form-introduction { .form-introduction {
...@@ -957,7 +970,148 @@ section.instructor-dashboard-content-2 { ...@@ -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 // view - student admin
// -------------------- // --------------------
...@@ -1771,5 +1925,3 @@ input[name="subject"] { ...@@ -1771,5 +1925,3 @@ input[name="subject"] {
left: 2em; left: 2em;
right: auto; 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"> <section class="cohort-management-settings has-tabs">
<h3 class="group-header-title"> <header class="cohort-management-group-header">
<span class="title-value"><%- cohort.get('name') %></span> <h3 class="group-header-title">
<span class="group-count"><%- <span class="title-value"><%- cohort.get('name') %></span>
interpolate( <span class="group-count"><%-
ngettext('(contains %(student_count)s student)', '(contains %(student_count)s students)', cohort.get('user_count')), interpolate(
{ student_count: cohort.get('user_count') }, ngettext('(contains %(student_count)s student)', '(contains %(student_count)s students)', cohort.get('user_count')),
true { student_count: cohort.get('user_count') },
) true
%></span> )
</h3> %></span>
<div class="cohort-management-group-setup"> </h3>
<div class="setup-value"> <div class="cohort-management-group-setup">
<% if (cohort.get('assignment_type') == "none") { %> <div class="setup-value">
<%= gettext("Students are added to this group only when you provide their email addresses or usernames on this page.") %> <% if (cohort.get('assignment_type') == "none") { %>
<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> <%- gettext("Students are added to this group only when you provide their email addresses or usernames on this page.") %>
<% } else { %> <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>
<%= gettext("Students are added to this group automatically.") %> <% } else { %>
<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> <%- 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"> </div>
<% if (advanced_settings_url != "None") { %> <div class="setup-actions">
<a href="<%= advanced_settings_url %>" class="action-secondary action-edit"><%= gettext("Edit settings in Studio") %></a> <% if (advanced_settings_url != "None") { %>
<% } %> <a href="<%= advanced_settings_url %>" class="action-secondary action-edit"><%- gettext("Edit settings in Studio") %></a>
<% } %>
</div>
</div> </div>
</div> </header>
</header>
<!-- individual group - form --> <ul class="wrapper-tabs">
<div class="cohort-management-group-add"> <li class="tab tab-manage_students is-selected" data-tab="manage_students"><a href="#"><%- gettext("Manage Students") %></a></li>
<form action="" method="post" id="cohort-management-group-add-form" class="cohort-management-group-add-form"> <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"> <h4 class="form-title"><%- gettext('Add students to this cohort group') %></h4>
<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="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-confirmations"></div>
<div class="cohort-errors"></div> <div class="cohort-errors"></div>
<div class="form-fields"> <div class="form-fields">
<div class="field field-textarea is-required"> <div class="field field-textarea is-required">
<label for="cohort-management-group-add-students" class="label"> <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. *') %> <%- gettext('Enter email addresses and/or usernames separated by new lines or commas for students to add. *') %>
<span class="sr"><%- gettext('(Required Field)') %></span> <span class="sr"><%- gettext('(Required Field)') %></span>
</label> </label>
<textarea name="cohort-management-group-add-students" id="cohort-management-group-add-students" <textarea name="cohort-management-group-add-students" id="cohort-management-group-add-students"
class="input cohort-management-group-add-students" class="input cohort-management-group-add-students"
placeholder="<%- gettext('e.g. johndoe@example.com, JaneDoe, joeydoe@example.com') %>"></textarea> 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>
<div class="form-actions"> <div class="form-actions">
<button class="form-submit button action-primary action-view"> <button class="form-submit button action-primary action-view">
<i class="button-icon icon fa fa-plus"></i> <%- gettext('Add Students') %> <i class="button-icon icon fa fa-plus"></i> <%- gettext('Add Students') %>
</button> </button>
</div> </div>
</form> </form>
</div> </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 @@ ...@@ -24,6 +24,9 @@
</a> </a>
</div> </div>
<!-- Add modal -->
<div class="cohort-management-add-modal"></div>
<!-- individual group --> <!-- individual group -->
<div class="cohort-management-group"></div> <div class="cohort-management-group"></div>
......
...@@ -58,14 +58,16 @@ ...@@ -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/notification.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/file_uploader.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/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/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/cohort_editor.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/cohorts.js')}"></script> <script type="text/javascript" src="${static.url('js/groups/views/cohorts.js')}"></script>
</%block> </%block>
## Include Underscore templates ## Include Underscore templates
<%block name="header_extras"> <%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"> <script type="text/template" id="${template_name}-tpl">
<%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" /> <%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" />
</script> </script>
......
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<%page args="section_data"/> <%page args="section_data"/>
<%! from microsite_configuration import microsite %> <%! 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"> <script type="text/template" id="member-list-widget-template">
<div class="member-list-widget"> <div class="member-list-widget">
...@@ -244,7 +245,7 @@ ...@@ -244,7 +245,7 @@
</div> </div>
%if course.is_cohorted: % if course.is_cohorted:
<hr class="divider" /> <hr class="divider" />
<div class="cohort-management membership-section" <div class="cohort-management membership-section"
data-ajax_url="${section_data['cohorts_ajax_url']}" data-ajax_url="${section_data['cohorts_ajax_url']}"
...@@ -254,15 +255,29 @@ ...@@ -254,15 +255,29 @@
</div> </div>
<%block name="headextra"> <%block name="headextra">
<%
cohorted_user_partition = get_cohorted_user_partition(course.id)
content_groups = cohorted_user_partition.groups if cohorted_user_partition else []
%>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
var cohortManagementElement = $('.cohort-management'); var cohortManagementElement = $('.cohort-management');
if (cohortManagementElement.length > 0) { 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'); cohorts.url = cohortManagementElement.data('ajax_url');
var cohortsView = new edx.groups.CohortsView({ var cohortsView = new edx.groups.CohortsView({
el: cohortManagementElement, el: cohortManagementElement,
model: cohorts, model: cohorts,
cohortUserPartitionId: ${cohorted_user_partition.id if cohorted_user_partition else 'null'},
contentGroups: contentGroups,
advanced_settings_url: cohortManagementElement.data('advanced-settings-url'), advanced_settings_url: cohortManagementElement.data('advanced-settings-url'),
upload_cohorts_csv_url: cohortManagementElement.data('upload_cohorts_csv_url') upload_cohorts_csv_url: cohortManagementElement.data('upload_cohorts_csv_url')
}); });
......
...@@ -43,7 +43,6 @@ ...@@ -43,7 +43,6 @@
<section class="instructor-dashboard-content-2" id="instructor-dashboard-content"> <section class="instructor-dashboard-content-2" id="instructor-dashboard-content">
<h1>Instructor Dashboard</h1> <h1>Instructor Dashboard</h1>
<section class="idash-section active-section" id="membership"> <section class="idash-section active-section" id="membership">
<div class="cohort-management membership-section"> <div class="cohort-management membership-section">
<h2 class="section-title"> <h2 class="section-title">
<span class="value">Cohort Group Management</span> <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