From b8b0110d1546527eba10dd3ef3a1bf5fb9efe53d Mon Sep 17 00:00:00 2001 From: "Albert St. Aubin" Date: Fri, 7 Jul 2017 19:58:16 +0000 Subject: [PATCH] Ui updates, Error messaging shown when discussion scheme is not selected --- common/static/common/js/discussion/content.js | 2 +- common/static/common/js/discussion/utils.js | 12 +- .../views/discussion_content_view.js | 3 +- lms/djangoapps/instructor/views/api.py | 15 ++- .../js/instructor_dashboard/membership.js | 117 ++++++++++++++---- .../instructor_dashboard_2/membership.html | 23 ++-- 6 files changed, 131 insertions(+), 41 deletions(-) diff --git a/common/static/common/js/discussion/content.js b/common/static/common/js/discussion/content.js index e39c721849..028ceb1d14 100644 --- a/common/static/common/js/discussion/content.js +++ b/common/static/common/js/discussion/content.js @@ -122,7 +122,7 @@ userId = this.get('user_id'); if (userId) { this.set('staff_authored', DiscussionUtil.isStaff(userId)); - this.set('community_ta_authored', DiscussionUtil.isTA(userId)); + this.set('community_ta_authored', DiscussionUtil.isTA(userId) || DiscussionUtil.isGroupTA(userId)); } else { this.set('staff_authored', false); this.set('community_ta_authored', false); diff --git a/common/static/common/js/discussion/utils.js b/common/static/common/js/discussion/utils.js index d4f8fcc21c..ea0088dac4 100644 --- a/common/static/common/js/discussion/utils.js +++ b/common/static/common/js/discussion/utils.js @@ -1,4 +1,4 @@ -/* globals $$course_id, Content, Markdown, MathJax, URI */ +/* globals $$course_id, Content, Markdown, MathJax, URI, _ */ (function() { 'use strict'; this.DiscussionUtil = (function() { @@ -41,6 +41,16 @@ return _.include(ta, parseInt(userId)); }; + DiscussionUtil.isGroupTA = function(userId) { + var groupTa, + localUserId = userId; + if (_.isUndefined(userId)) { + localUserId = this.user ? this.user.id : void 0; + } + groupTa = _.union(this.roleIds['Group Moderator']); + return _.include(groupTa, parseInt(localUserId, 10)); + }; + DiscussionUtil.isPrivilegedUser = function(userId) { return this.isStaff(userId) || this.isTA(userId); }; diff --git a/common/static/common/js/discussion/views/discussion_content_view.js b/common/static/common/js/discussion/views/discussion_content_view.js index 3b00c3e257..8b318fbe75 100644 --- a/common/static/common/js/discussion/views/discussion_content_view.js +++ b/common/static/common/js/discussion/views/discussion_content_view.js @@ -508,7 +508,8 @@ return _.template($('#post-user-display-template').html())({ username: endorsement.username, user_url: DiscussionUtil.urlFor('user_profile', endorsement.user_id), - is_community_ta: DiscussionUtil.isTA(endorsement.user_id), + is_community_ta: DiscussionUtil.isTA(endorsement.user_id) || + DiscussionUtil.isGroupTA(endorsement.user_id), is_staff: DiscussionUtil.isStaff(endorsement.user_id) }); } else { diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 09e4129112..301b3d6927 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -45,7 +45,12 @@ from certificates.models import CertificateInvalidation, CertificateStatuses, Ce from courseware.access import has_access from courseware.courses import get_course_by_id, get_course_with_access from courseware.models import StudentModule -from django_comment_client.utils import has_forum_access +from django_comment_client.utils import ( + has_forum_access, + get_course_discussion_settings, + get_group_name, + get_group_id_for_user +) from django_comment_common.models import ( Role, FORUM_ROLE_ADMINISTRATOR, @@ -933,6 +938,7 @@ def list_course_role_members(request, course_id): def extract_user_info(user): """ convert user into dicts for json view """ + return { 'username': user.username, 'email': user.email, @@ -2505,18 +2511,25 @@ def list_forum_members(request, course_id): except Role.DoesNotExist: users = [] + course_discussion_settings = get_course_discussion_settings(course_id) + def extract_user_info(user): """ Convert user to dict for json rendering. """ + group_id = get_group_id_for_user(user, course_discussion_settings) + group_name = get_group_name(group_id, course_discussion_settings) + return { 'username': user.username, 'email': user.email, 'first_name': user.first_name, 'last_name': user.last_name, + 'group_name': group_name, } response_payload = { 'course_id': course_id.to_deprecated_string(), rolename: map(extract_user_info, users), + 'division_scheme': course_discussion_settings.division_scheme, } return JsonResponse(response_payload) diff --git a/lms/static/js/instructor_dashboard/membership.js b/lms/static/js/instructor_dashboard/membership.js index 9cf969a5b0..553d20d0e0 100644 --- a/lms/static/js/instructor_dashboard/membership.js +++ b/lms/static/js/instructor_dashboard/membership.js @@ -12,7 +12,7 @@ such that the value can be defined later than this assignment (file load order). (function() { 'use strict'; var AuthListWidget, BatchEnrollment, BetaTesterBulkAddition, - MemberListWidget, Membership, emailStudents, plantTimeout, statusAjaxError, + MemberListWidget, Membership, emailStudents, plantTimeout, statusAjaxError, enableAddButton, /* eslint-disable */ __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -26,6 +26,18 @@ such that the value can be defined later than this assignment (file load order). return window.InstructorDashboard.util.statusAjaxError.apply(this, arguments); }; + enableAddButton = function(enable, parent) { + var $addButton = parent.$('input[type="button"].add'); + var $addField = parent.$('input[type="text"].add-field'); + if (enable) { + $addButton.removeAttr('disabled'); + $addField.removeAttr('disabled'); + } else { + $addButton.attr('disabled', true); + $addField.attr('disabled', true); + } + }; + emailStudents = false; MemberListWidget = (function() { @@ -92,17 +104,22 @@ such that the value can be defined later than this assignment (file load order). __extends(AuthListWidget, _super); // eslint-disable-line no-use-before-define function AuthListWidget($container, rolename, $errorSection) { // eslint-disable-line no-shadow var msg, - authlistwidget = this; + authListWidget = this, + labelsList = [gettext('Username'), gettext('Email'), gettext('Revoke access')]; this.rolename = rolename; this.$errorSection = $errorSection; + this.list_enabled = true; + if (this.rolename === 'Group Moderator') { + labelsList = [gettext('Username'), gettext('Email'), gettext('Group'), gettext('Revoke access')]; + } AuthListWidget.__super__.constructor.call(this, $container, { // eslint-disable-line no-underscore-dangle title: $container.data('display-name'), info: $container.data('info-text'), - labels: [gettext('Username'), gettext('Email'), gettext('Revoke access')], + labels: labelsList, add_placeholder: gettext('Enter username or email'), add_btn_label: $container.data('add-button-label'), add_handler: function(input) { - return authlistwidget.add_handler(input); + return authListWidget.add_handler(input); } }); this.debug = true; @@ -122,15 +139,15 @@ such that the value can be defined later than this assignment (file load order). }; AuthListWidget.prototype.add_handler = function(input) { - var authlistwidgetaddhandler = this; + var authListWidgetAddHandler = this; if ((input != null) && input !== '') { return this.modify_member_access(input, 'allow', function(error) { if (error !== null) { - return authlistwidgetaddhandler.show_errors(error); + return authListWidgetAddHandler.show_errors(error); } - authlistwidgetaddhandler.clear_errors(); - authlistwidgetaddhandler.clear_input(); - return authlistwidgetaddhandler.reload_list(); + authListWidgetAddHandler.clear_errors(); + authListWidgetAddHandler.clear_input(); + return authListWidgetAddHandler.reload_list(); }); } else { return this.show_errors(gettext('Please enter a username or email.')); @@ -138,43 +155,69 @@ such that the value can be defined later than this assignment (file load order). }; AuthListWidget.prototype.reload_list = function() { - var authlistwidgetreloadlist = this; - return this.get_member_list(function(error, memberList) { + var authListWidgetReloadList = this, + $selectedOption; + return this.get_member_list(function(error, memberList, divisionScheme) { if (error !== null) { - return authlistwidgetreloadlist.show_errors(error); + authListWidgetReloadList.show_errors(error); + return; } - authlistwidgetreloadlist.clear_rows(); - return _.each(memberList, function(member) { + authListWidgetReloadList.clear_rows(); + + _.each(memberList, function(member) { var $revokeBtn, labelTrans; labelTrans = gettext('Revoke access'); + $revokeBtn = $(_.template('
<%- label %>
')({ // eslint-disable-line max-len label: labelTrans }), { class: 'revoke' }); $revokeBtn.click(function() { - return authlistwidgetreloadlist.modify_member_access(member.email, 'revoke', function(err) { + authListWidgetReloadList.modify_member_access(member.email, 'revoke', function(err) { if (err !== null) { - return authlistwidgetreloadlist.show_errors(err); + authListWidgetReloadList.show_errors(err); + return; } - authlistwidgetreloadlist.clear_errors(); - return authlistwidgetreloadlist.reload_list(); + authListWidgetReloadList.clear_errors(); + authListWidgetReloadList.reload_list(); }); }); - return authlistwidgetreloadlist.add_row([member.username, member.email, $revokeBtn]); + if (authListWidgetReloadList.rolename === 'Group Moderator') { + if (divisionScheme !== undefined && divisionScheme === 'none') { + // There is No discussion division scheme selected so the Group Moderator role + // should be disabled + authListWidgetReloadList.list_enabled = false; + $selectedOption = $('select#member-lists-selector').children('option:selected'); + if ($selectedOption[0].value === authListWidgetReloadList.rolename) { + authListWidgetReloadList.show_errors( + gettext('This role requires a divided discussions scheme.') + ); + enableAddButton(false, authListWidgetReloadList); + } + } else { + authListWidgetReloadList.list_enabled = true; + enableAddButton(true, authListWidgetReloadList); + authListWidgetReloadList.add_row([member.username, member.email, + member.group_name, $revokeBtn] + ); + } + } else { + authListWidgetReloadList.add_row([member.username, member.email, $revokeBtn]); + } }); }); }; AuthListWidget.prototype.clear_errors = function() { - var ref, result; - result = (this.$error_section) != null ? ref.text('') : undefined; + var result; + result = this.$errorSection !== undefined ? this.$errorSection.text('') : undefined; return result; }; AuthListWidget.prototype.show_errors = function(msg) { - var ref, result; - result = (this.$error_section) != null ? ref.text(msg) : undefined; + var result; + result = this.$errorSection !== undefined ? this.$errorSection.text(msg) : undefined; return result; }; @@ -188,7 +231,11 @@ such that the value can be defined later than this assignment (file load order). rolename: this.rolename }, success: function(data) { - return typeof cb === 'function' ? cb(null, data[authlistwidgetgetmemberlist.rolename]) : undefined; + return typeof cb === 'function' ? cb( + null, + data[authlistwidgetgetmemberlist.rolename], + data.division_scheme + ) : undefined; } }); }; @@ -933,6 +980,7 @@ such that the value can be defined later than this assignment (file load order). this.$list_selector = this.$section.find('select#member-lists-selector'); this.$auth_list_containers = this.$section.find('.auth-list-container'); this.$auth_list_errors = this.$section.find('.member-lists-management .request-response-error'); + this.auth_lists = _.map(this.$auth_list_containers, function(authListContainer) { var rolename; rolename = $(authListContainer).data('rolename'); @@ -944,6 +992,7 @@ such that the value can be defined later than this assignment (file load order). authList = ref[i]; this.$list_selector.append($('