FEATURE: Change user groups in bulk via admin

This commit is contained in:
Robin Ward 2015-10-26 15:56:59 -04:00
parent e1d5503053
commit 47e25648df
11 changed files with 143 additions and 0 deletions

View file

@ -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);
});
}
}
});

View file

@ -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 });
}
});

View file

@ -49,6 +49,8 @@ export default {
}); });
this.resource('adminGroups', { path: '/groups' }, function() { this.resource('adminGroups', { path: '/groups' }, function() {
this.route('bulk');
this.route('bulkComplete', { path: 'bulk-complete' });
this.resource('adminGroupsType', { path: '/:type' }, function() { this.resource('adminGroupsType', { path: '/:type' }, function() {
this.resource('adminGroup', { path: '/:name' }); this.resource('adminGroup', { path: '/:name' });
}); });

View file

@ -0,0 +1 @@
<p>{{i18n "admin.groups.bulk_complete"}}</p>

View file

@ -0,0 +1,19 @@
<div class='groups-bulk'>
<p>{{i18n "admin.groups.bulk_paste"}}</p>
<div class='control'>
{{textarea value=users class="paste-users"}}
</div>
<div class='control'>
{{combo-box content=groups valueAttribute="id" value=groupId none="admin.groups.bulk_select"}}
</div>
<div class='control'>
{{d-button disabled=buttonDisabled
class="btn-primary"
action="addToGroup"
icon="plus"
label="admin.groups.bulk"}}
</div>
</div>

View file

@ -1,6 +1,7 @@
{{#admin-nav}} {{#admin-nav}}
{{nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom'}} {{nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom'}}
{{nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic'}} {{nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic'}}
{{nav-item route='adminGroups.bulk' label='admin.groups.bulk'}}
{{/admin-nav}} {{/admin-nav}}
<div class="admin-container"> <div class="admin-container">

View file

@ -215,6 +215,11 @@ td.flaggers td {
} }
} }
.paste-users {
width: 400px;
height: 150px;
}
.groups, .badges { .groups, .badges {
.form-horizontal { .form-horizontal {
label { label {
@ -1015,6 +1020,11 @@ table.api-keys {
} }
} }
.groups-bulk {
.control {
margin-bottom: 1em;
}
}
.commits-widget { .commits-widget {
border: solid 1px dark-light-diff($primary, $secondary, 90%, -60%); border: solid 1px dark-light-diff($primary, $secondary, 90%, -60%);

View file

@ -19,6 +19,42 @@ class Admin::GroupsController < Admin::AdminController
render nothing: true render nothing: true
end 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 def create
group = Group.new group = Group.new

View file

@ -1949,6 +1949,10 @@ en:
add: "Add" add: "Add"
add_members: "Add members" add_members: "Add members"
custom: "Custom" 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: "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_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" automatic_membership_retroactive: "Apply the same email domain rule to add existing registered users"

View file

@ -60,6 +60,9 @@ Discourse::Application.routes.draw do
resources :groups, constraints: AdminConstraint.new do resources :groups, constraints: AdminConstraint.new do
collection do collection do
post "refresh_automatic_groups" => "groups#refresh_automatic_groups" post "refresh_automatic_groups" => "groups#refresh_automatic_groups"
get 'bulk'
get 'bulk-complete' => 'groups#bulk'
put 'bulk' => 'groups#bulk_perform'
end end
member do member do
put "members" => "groups#add_members" put "members" => "groups#add_members"

View file

@ -36,6 +36,26 @@ describe Admin::GroupsController do
end 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 context ".create" do
it "strip spaces on the group name" do it "strip spaces on the group name" do