diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 40f8e2e518a5b..651a2dba372ec 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -278,6 +278,18 @@ def web_index(conferences) @render_alternatives = WebConference.conference_types(@context).all? { |ct| ct[:replace_with_alternatives] } case @context when Course + @sections = @context.course_sections + @groups = @context.active_groups + + @group_user_ids_map = @groups.to_a.reduce({}) do |acc, group| + acc[group.id] = group.participating_users_in_context.map{|u| u.id.to_s} + acc + end + + @section_user_ids_map = @sections.to_a.reduce({}) do |acc, section| + acc[section.id] = section.participants.map{|u| u.id.to_s} + acc + end @users = User.where(:id => @context.current_enrollments.not_fake.active_by_date.where.not(:user_id => @current_user).select(:user_id)). order(User.sortable_name_order_by_clause).to_a @render_alternatives ||= @context.settings[:show_conference_alternatives].present? @@ -288,6 +300,8 @@ def web_index(conferences) @users = @context.users.where("users.id<>?", @current_user).order(User.sortable_name_order_by_clause).to_a.uniq end end + @groups ||= [] + @sections ||= [] # exposing the initial data as json embedded on page. js_env( @@ -296,6 +310,10 @@ def web_index(conferences) default_conference: default_conference_json(@context, @current_user, session), conference_type_details: conference_types_json(WebConference.conference_types(@context)), users: @users.map { |u| {:id => u.id, :name => u.last_name_first} }, + groups: @groups.map { |g| {:id => g.id, :name => g.full_name} }, + sections: @sections.map { |s| {:id => s.id, :name => s.display_name} }, + group_user_ids_map: @group_user_ids_map, + section_user_ids_map: @section_user_ids_map, can_create_conferences: @context.grants_right?(@current_user, session, :create_conferences), render_alternatives: @render_alternatives ) diff --git a/spec/javascripts/jsx/conferences/EditConferenceViewSpec.js b/spec/javascripts/jsx/conferences/EditConferenceViewSpec.js index 172b2efc6d1ef..4ab953fc1495b 100644 --- a/spec/javascripts/jsx/conferences/EditConferenceViewSpec.js +++ b/spec/javascripts/jsx/conferences/EditConferenceViewSpec.js @@ -30,7 +30,21 @@ QUnit.module('EditConferenceView', { this.datepickerSetting = {field: 'datepickerSetting', type: 'date_picker'} fakeENV.setup({ conference_type_details: [{settings: [this.datepickerSetting]}], - users: [{id: 1, name: 'Owlswick Clamp'}] + users: [ + {id: 1, name: 'Owlswick Clamp'}, + {id: 2, name: 'Abby Zollinger'}, + {id: 3, name: 'Bruce Young'} + ], + sections: [ + {id: 1, name: 'Section 1'}, + {id: 2, name: 'Section 2'} + ], + groups: [ + {id: 1, name: 'Study Group 1'}, + {id: 2, name: 'Study Group 2'} + ], + section_user_ids_map: {1: [1, 2], 2: [3]}, + group_user_ids_map: {1: [1], 2: [1, 2]} }) }, teardown() { @@ -129,3 +143,259 @@ test('"remove observers" modifies "invite all course members"', function() { ok(this.view.$('#members_list').is(':visible')) ok(this.view.$('#observers_remove').is(':disabled')) }) + +test('sections should appear in member list if course has more than one section', function() { + const attributes = { + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + } + } + + const conference = new Conference(attributes) + this.view.show(conference) + this.view.$('#user_all').click() + ok(this.view.$('#section_1').is(':visible')) + ok(this.view.$('#section_2').is(':visible')) +}) + +test('sections should not appear in member list if course has only section', function() { + const attributes = { + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + } + } + + window.ENV.sections = [{name: 'Section 1', id: 1}] + + const conference = new Conference(attributes) + this.view.show(conference) + this.view.$('#user_all').click() + ok(!this.view.$('#section_1').is(':visible')) +}) + +test('groups should appear in member list if course has one or more groups', function() { + const attributes = { + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + } + } + + const conference = new Conference(attributes) + this.view.show(conference) + this.view.$('#user_all').click() + ok(this.view.$('#group_1').is(':visible')) +}) + +test('checking/unchecking a section also checks/unchecks the members that are in that section', function() { + const attributes = { + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + } + } + + const conference = new Conference(attributes) + this.view.show(conference) + this.view.$('#user_all').click() + this.view.$('#section_2').click() + ok(!this.view.$('#user_1').is(':checked')) + this.view.$('#section_1').click() + ok(this.view.$('#user_1').is(':checked')) + this.view.$('#section_1').click() + ok(!this.view.$('#user_1').is(':checked')) +}) + +test('checking/unchecking a groups also checks/unchecks the members that are in that group', function() { + const attributes = { + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + } + } + + const conference = new Conference(attributes) + this.view.show(conference) + this.view.$('#user_all').click() + ok(!this.view.$('#user_1').is(':checked')) + this.view.$('#group_1').click() + ok(this.view.$('#user_1').is(':checked')) + this.view.$('#group_1').click() + ok(!this.view.$('#user_1').is(':checked')) +}) + +test('unchecking a group only unchecks members that have not been selected by section also', function() { + const attributes = { + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + } + } + + const conference = new Conference(attributes) + this.view.show(conference) + this.view.$('#user_all').click() + ok(!this.view.$('#user_1').is(':checked')) + this.view.$('#group_1').click() + this.view.$('#section_1').click() + + ok(this.view.$('#user_1').is(':checked')) + ok(this.view.$('#user_2').is(':checked')) + this.view.$('#group_1').click() + ok(this.view.$('#user_1').is(':checked')) +}) + +test('unchecking a section only unchecks members that have not been selected by group also', function() { + const attributes = { + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + } + } + + const conference = new Conference(attributes) + this.view.show(conference) + this.view.$('#user_all').click() + ok(!this.view.$('#user_1').is(':checked')) + this.view.$('#group_1').click() + this.view.$('#section_1').click() + + ok(this.view.$('#user_1').is(':checked')) + ok(this.view.$('#user_2').is(':checked')) + this.view.$('#section_1').click() + ok(this.view.$('#user_1').is(':checked')) + ok(!this.view.$('#user_2').is(':checked')) +}) + +test('While editing a conference the box for a group should be checked and disabled if everyone in the group is a participant', function() { + const attributes = { + title: 'Making Money', + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + }, + user_ids: [1] + } + const conference = new Conference(attributes) + this.view.show(conference, {isEditing: true}) + + ok(this.view.$('#group_1').is(':checked')) + ok(this.view.$('#group_1').is(':disabled')) +}) + +test('While editing a conference the box for a section should be checked and disabled if everyone in the section is a participant', function() { + const attributes = { + title: 'Making Money', + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + }, + user_ids: [3] + } + const conference = new Conference(attributes) + this.view.show(conference, {isEditing: true}) + + ok(this.view.$('#section_2').is(':checked')) + ok(this.view.$('#section_2').is(':disabled')) +}) + +test('While editing a conference unchecking a group should only uncheck members who are not a part of the existing conference', function() { + const attributes = { + title: 'Making Money', + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + }, + user_ids: [1] + } + const conference = new Conference(attributes) + this.view.show(conference, {isEditing: true}) + ok(!this.view.$('#group_2').is(':checked')) + ok(!this.view.$('#user_2').is(':checked')) + + this.view.$('#group_2').click() + ok(this.view.$('#user_1').is(':checked')) + ok(this.view.$('#user_2').is(':checked')) + + this.view.$('#group_2').click() + ok(this.view.$('#user_1').is(':checked')) + ok(!this.view.$('#user_2').is(':checked')) +}) + +test('While editing a conference unchecking a section should only uncheck member who are not a part of the existing conference', function() { + const attributes = { + title: 'Making Money', + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + }, + user_ids: [1] + } + const conference = new Conference(attributes) + this.view.show(conference, {isEditing: true}) + ok(!this.view.$('#section_1').is(':checked')) + ok(!this.view.$('#user_2').is(':checked')) + + this.view.$('#section_1').click() + ok(this.view.$('#user_1').is(':checked')) + ok(this.view.$('#user_2').is(':checked')) + + this.view.$('#section_1').click() + ok(this.view.$('#user_1').is(':checked')) + ok(!this.view.$('#user_2').is(':checked')) +}) + +test('while context_is_group = true no sections or groups should appear in the member list', function() { + const attributes = { + title: 'Making Money', + recordings: [], + user_settings: { + scheduled_date: new Date() + }, + permissions: { + update: true + } + } + window.ENV.context_is_group = true + const conference = new Conference(attributes) + this.view.show(conference) + ok(!this.view.$('#section_1').is(':visible')) + ok(!this.view.$('#section_2').is(':visible')) + ok(!this.view.$('#group_1').is(':visible')) + ok(!this.view.$('#group_2').is(':visible')) +}) diff --git a/ui/features/conferences/backbone/views/EditConferenceView.coffee b/ui/features/conferences/backbone/views/EditConferenceView.coffee index 049cddc295baf..2607f85bb8d32 100644 --- a/ui/features/conferences/backbone/views/EditConferenceView.coffee +++ b/ui/features/conferences/backbone/views/EditConferenceView.coffee @@ -45,7 +45,9 @@ export default class EditConferenceView extends DialogBaseView @delegateEvents() @toggleAllUsers() @markInvitedUsers() + @markInvitedSectionsAndGroups() @renderConferenceFormUserSettings() + @setupGroupAndSectionEventListeners() @$('form').formSubmit( object_name: 'web_conference' beforeSubmit: (data) => @@ -105,14 +107,23 @@ export default class EditConferenceView extends DialogBaseView if numberHelper.validate(conferenceData.duration) conferenceData.duration = I18n.n(conferenceData.duration) + hide_groups = !ENV.groups || ENV.groups.length == 0 + hide_sections = !ENV.sections || ENV.sections.length <= 1 + hide_user_header = hide_groups && hide_sections + json = settings: is_editing: is_editing is_adding: is_adding disable_duration_changes: ((conferenceData['long_running'] || is_editing) && conferenceData['started_at']) auth_token: authenticity_token() + hide_sections: hide_sections + hide_groups: hide_groups + hide_user_header: hide_user_header conferenceData: conferenceData users: ENV.users + sections: ENV.sections + groups: ENV.groups context_is_group: ENV.context_asset_string.split("_")[0] == "group" conferenceTypes: ENV.conference_type_details.map((type) -> {name: type.name, type: type.type, selected: (conferenceData.conference_type == type.type)} @@ -203,9 +214,71 @@ export default class EditConferenceView extends DialogBaseView el.attr('disabled', true) ) + markInvitedSectionsAndGroups: -> + _.each(ENV.sections, (section) => + section_user_ids = ENV.section_user_ids_map[section.id] + intersection = _.intersection(section_user_ids, @model.get("user_ids")) + if (intersection.length == section_user_ids.length) + el = $("#members_list .member.section_" + section.id).find(":checkbox") + el.attr('checked', true) + el.attr('disabled', true) + ) + + _.each(ENV.groups, (group) => + group_user_ids = ENV.group_user_ids_map[group.id] + intersection = _.intersection(group_user_ids, @model.get("user_ids")) + if (intersection.length == group_user_ids.length) + el = $("#members_list .member.group_" + group.id).find(":checkbox") + el.attr('checked', true) + el.attr('disabled', true) + ) + + changeLongRunning: (e) -> if ($(e.currentTarget).is(':checked')) $('#web_conference_duration').prop('disabled', true).val('') else # use restore time from data attribute $('#web_conference_duration').prop('disabled', false).val($('#web_conference_duration').data('restore-value')) + + setupGroupAndSectionEventListeners: () -> + selectedBySection = [] + selectedByGroup = [] + toggleMember = (id, checked) -> + memberEl = $("#members_list .member.user_" + id).find(":checkbox") + memberEl.attr('checked', checked) + memberEl.attr('disabled', checked) + + _.each(ENV.groups, (group) => + el = $("#members_list .member.group_" + group.id) + el.on("change", (e) => + _.each( + ENV.group_user_ids_map[group.id], + (id) => + if (e.target.checked) + selectedByGroup.push(id) + toggleMember(id, e.target.checked) + else + selectedByGroup = _.without(selectedByGroup, id) + if (!_.contains(selectedBySection, id) && !_.contains(@model.get("user_ids"), id)) + toggleMember(id, e.target.checked) + ) + ) + ) + + _.each(ENV.sections, (section) => + el = $("#members_list .member.section_" + section.id) + el.on("change", (e) => + _.each( + ENV.section_user_ids_map[section.id], + (id) => + if (e.target.checked) + selectedBySection.push(id) + toggleMember(id, e.target.checked) + else + selectedBySection = _.without(selectedBySection, id) + if (!_.contains(selectedByGroup, id) && !_.contains(@model.get("user_ids"), id)) + toggleMember(id, e.target.checked) + ) + ) + ) diff --git a/ui/features/conferences/jst/editConferenceForm.handlebars b/ui/features/conferences/jst/editConferenceForm.handlebars index cad1506c48346..1364392c520d1 100644 --- a/ui/features/conferences/jst/editConferenceForm.handlebars +++ b/ui/features/conferences/jst/editConferenceForm.handlebars @@ -74,6 +74,41 @@ {{/unless}}