From 47e25648dfa200ac266a6311ba585827638d2bcc Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 26 Oct 2015 15:56:59 -0400 Subject: [PATCH] FEATURE: Change user groups in bulk via admin --- .../controllers/admin-groups-bulk.js.es6 | 34 ++++++++++++++++++ .../admin/routes/admin-groups-bulk.js.es6 | 13 +++++++ .../admin/routes/admin-route-map.js.es6 | 2 ++ .../admin/templates/groups-bulk-complete.hbs | 1 + .../admin/templates/groups-bulk.hbs | 19 ++++++++++ .../javascripts/admin/templates/groups.hbs | 1 + .../stylesheets/common/admin/admin_base.scss | 10 ++++++ app/controllers/admin/groups_controller.rb | 36 +++++++++++++++++++ config/locales/client.en.yml | 4 +++ config/routes.rb | 3 ++ .../admin/groups_controller_spec.rb | 20 +++++++++++ 11 files changed, 143 insertions(+) create mode 100644 app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 create mode 100644 app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 create mode 100644 app/assets/javascripts/admin/templates/groups-bulk-complete.hbs create mode 100644 app/assets/javascripts/admin/templates/groups-bulk.hbs diff --git a/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 b/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 new file mode 100644 index 000000000..6628a6fa7 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 @@ -0,0 +1,34 @@ +import computed from 'ember-addons/ember-computed-decorators'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; + +export default Ember.Controller.extend({ + users: null, + groupId: null, + saving: false, + + @computed('saving', 'users', 'groupId') + buttonDisabled(saving, users, groupId) { + return saving || !groupId || !users || !users.length; + }, + + actions: { + addToGroup() { + if (this.get('saving')) { return; } + + const users = this.get('users').split("\n") + .uniq() + .reject(x => x.length === 0); + + this.set('saving', true); + Discourse.ajax('/admin/groups/bulk', { + data: { users, group_id: this.get('groupId') }, + method: 'PUT' + }).then(() => { + this.transitionToRoute('adminGroups.bulkComplete'); + }).catch(popupAjaxError).finally(() => { + this.set('saving', false); + }); + + } + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 b/app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 new file mode 100644 index 000000000..8d9554556 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 @@ -0,0 +1,13 @@ +import Group from 'discourse/models/group'; + +export default Ember.Route.extend({ + model() { + return Group.findAll().then(groups => { + return groups.filter(g => !g.get('automatic')); + }); + }, + + setupController(controller, groups) { + controller.setProperties({ groups, groupId: null, users: null }); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 index e01d0f8f0..dd758556c 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -49,6 +49,8 @@ export default { }); this.resource('adminGroups', { path: '/groups' }, function() { + this.route('bulk'); + this.route('bulkComplete', { path: 'bulk-complete' }); this.resource('adminGroupsType', { path: '/:type' }, function() { this.resource('adminGroup', { path: '/:name' }); }); diff --git a/app/assets/javascripts/admin/templates/groups-bulk-complete.hbs b/app/assets/javascripts/admin/templates/groups-bulk-complete.hbs new file mode 100644 index 000000000..51eb3e439 --- /dev/null +++ b/app/assets/javascripts/admin/templates/groups-bulk-complete.hbs @@ -0,0 +1 @@ +

{{i18n "admin.groups.bulk_complete"}}

diff --git a/app/assets/javascripts/admin/templates/groups-bulk.hbs b/app/assets/javascripts/admin/templates/groups-bulk.hbs new file mode 100644 index 000000000..baf3a63cd --- /dev/null +++ b/app/assets/javascripts/admin/templates/groups-bulk.hbs @@ -0,0 +1,19 @@ +
+

{{i18n "admin.groups.bulk_paste"}}

+ +
+ {{textarea value=users class="paste-users"}} +
+ +
+ {{combo-box content=groups valueAttribute="id" value=groupId none="admin.groups.bulk_select"}} +
+ +
+ {{d-button disabled=buttonDisabled + class="btn-primary" + action="addToGroup" + icon="plus" + label="admin.groups.bulk"}} +
+
diff --git a/app/assets/javascripts/admin/templates/groups.hbs b/app/assets/javascripts/admin/templates/groups.hbs index 2d767c384..aa7d9213c 100644 --- a/app/assets/javascripts/admin/templates/groups.hbs +++ b/app/assets/javascripts/admin/templates/groups.hbs @@ -1,6 +1,7 @@ {{#admin-nav}} {{nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom'}} {{nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic'}} + {{nav-item route='adminGroups.bulk' label='admin.groups.bulk'}} {{/admin-nav}}
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 60396122e..63c01ffed 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -215,6 +215,11 @@ td.flaggers td { } } +.paste-users { + width: 400px; + height: 150px; +} + .groups, .badges { .form-horizontal { label { @@ -1015,6 +1020,11 @@ table.api-keys { } } +.groups-bulk { + .control { + margin-bottom: 1em; + } +} .commits-widget { border: solid 1px dark-light-diff($primary, $secondary, 90%, -60%); diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 767657f1e..67e8d8696 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -19,6 +19,42 @@ class Admin::GroupsController < Admin::AdminController render nothing: true end + def bulk + render nothing: true + end + + def bulk_perform + group = Group.find(params[:group_id].to_i) + if group.present? + users = (params[:users] || []).map {|u| u.downcase} + user_ids = User.where("username_lower in (:users) OR email IN (:users)", users: users).pluck(:id) + + if user_ids.present? + Group.exec_sql("INSERT INTO group_users + (group_id, user_id, created_at, updated_at) + SELECT #{group.id}, + u.id, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + FROM users AS u + WHERE u.id IN (#{user_ids.join(', ')}) + AND NOT EXISTS(SELECT 1 FROM group_users AS gu + WHERE gu.user_id = u.id AND + gu.group_id = #{group.id})") + + if group.primary_group? + User.where(id: user_ids).update_all(primary_group_id: group.id) + end + + if group.title.present? + User.where(id: user_ids).update_all(title: group.title) + end + end + end + + render json: success_json + end + def create group = Group.new diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index af5d2e6e0..6eff38ce5 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1949,6 +1949,10 @@ en: add: "Add" add_members: "Add members" custom: "Custom" + bulk_complete: "The users have been added to the group." + bulk: "Bulk Add to Group" + bulk_paste: "Paste a list of usernames or emails, one per line:" + bulk_select: "(select a group)" automatic: "Automatic" automatic_membership_email_domains: "Users who register with an email domain that exactly matches one in this list will be automatically added to this group:" automatic_membership_retroactive: "Apply the same email domain rule to add existing registered users" diff --git a/config/routes.rb b/config/routes.rb index cc55476b0..0af5799a2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -60,6 +60,9 @@ Discourse::Application.routes.draw do resources :groups, constraints: AdminConstraint.new do collection do post "refresh_automatic_groups" => "groups#refresh_automatic_groups" + get 'bulk' + get 'bulk-complete' => 'groups#bulk' + put 'bulk' => 'groups#bulk_perform' end member do put "members" => "groups#add_members" diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 75bc9ddac..e7c2233ec 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -36,6 +36,26 @@ describe Admin::GroupsController do end + context ".bulk" do + it "can assign users to a group by email or username" do + group = Fabricate(:group, name: "test", primary_group: true, title: 'WAT') + user = Fabricate(:user) + user2 = Fabricate(:user) + + xhr :put, :bulk_perform, group_id: group.id, users: [user.username.upcase, user2.email, 'doesnt_exist'] + + expect(response).to be_success + + user.reload + expect(user.primary_group).to eq(group) + expect(user.title).to eq("WAT") + + user2.reload + expect(user2.primary_group).to eq(group) + + end + end + context ".create" do it "strip spaces on the group name" do