From ac3f1ba3d6001876468d1b3427b2abb066c0cfe2 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Sun, 13 Jul 2014 20:11:38 +0200 Subject: [PATCH 1/6] Improved Group Member Management on User Administration Allows for a quick and easy group membership management on the user-administration page. Uses the select2 UI component to autosuggest other groups, remove existing ones and lock in automatic groups. --- .../components/admin-group-selector.js.es6 | 39 +++++++++++++++++++ .../controllers/admin_user_controller.js | 28 +++++++++++++ .../admin/templates/user_index.js.handlebars | 14 +++---- .../admin-group-selector.js.handlebars | 3 ++ app/controllers/admin/users_controller.rb | 17 ++++++++ .../admin_detailed_user_serializer.rb | 2 +- config/routes.rb | 2 + 7 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/admin/components/admin-group-selector.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/admin-group-selector.js.handlebars diff --git a/app/assets/javascripts/admin/components/admin-group-selector.js.es6 b/app/assets/javascripts/admin/components/admin-group-selector.js.es6 new file mode 100644 index 0000000000..c78f1ff899 --- /dev/null +++ b/app/assets/javascripts/admin/components/admin-group-selector.js.es6 @@ -0,0 +1,39 @@ +export default Ember.Component.extend({ + tagName: 'div', + + didInsertElement: function(){ + this.$("input").select2({ + multiple: true, + width: '100%', + query: function(opts){ + opts.callback({ + results: this.get("available").map(this._format) + }); + }.bind(this) + }).on("change", function(evt) { + if (evt.added){ + this.triggerAction({action: "groupAdded", + actionContext: this.get("available" + ).findBy("id", evt.added.id)}); + } else if (evt.removed) { + this.triggerAction({action:"groupRemoved", + actionContext: this.get("selected" + ).findBy("id", evt.removed.id)}); + } + }.bind(this)); + + Discourse.Group.findAll().then(function(groups){ + this.set("available", groups.filterBy("automatic", false)); + }.bind(this)); + + this.refreshOnReset(); + }, + + _format: function(item){ + return {"text": item.name, "id": item.id, "locked": item.automatic}; + }, + + refreshOnReset: function() { + this.$("input").select2("data", this.get("selected").map(this._format)); + }.observes("selected") +}); \ No newline at end of file diff --git a/app/assets/javascripts/admin/controllers/admin_user_controller.js b/app/assets/javascripts/admin/controllers/admin_user_controller.js index 50dff9a2fe..5fc95f5050 100644 --- a/app/assets/javascripts/admin/controllers/admin_user_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_user_controller.js @@ -25,6 +25,12 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({ primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'primary_group_id'), + custom_groups: function(){ + return this.get("model.groups").filter(function(g){ + return (!g.automatic && g.visible); + }); + }.property("model.groups.[]"), + actions: { toggleTitleEdit: function() { this.toggleProperty('editingTitle'); @@ -45,6 +51,28 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({ this.get('model').generateApiKey(); }, + groupAdded: function(added){ + var self = this; + Discourse.ajax("/admin/users/" + this.get('id') + "/groups", { + type: 'POST', + data: {group_id: added.id} + }).then(function () { + self.get('model.groups').pushObject(added); + }).catch(function() { + bootbox.alert(I18n.t('generic_error')); + }); + }, + groupRemoved: function(removed){ + var self = this; + Discourse.ajax("/admin/users/" + this.get('id') + "/groups/" + removed.id, { + type: 'DELETE' + }).then(function () { + self.set('model.groups.[]', self.get('model.groups').rejectBy("id", removed.id)); + }).catch(function() { + bootbox.alert(I18n.t('generic_error')); + }); + }, + savePrimaryGroup: function() { var self = this; Discourse.ajax("/admin/users/" + this.get('id') + "/primary_group", { diff --git a/app/assets/javascripts/admin/templates/user_index.js.handlebars b/app/assets/javascripts/admin/templates/user_index.js.handlebars index dddcee6038..fccb6d13a2 100644 --- a/app/assets/javascripts/admin/templates/user_index.js.handlebars +++ b/app/assets/javascripts/admin/templates/user_index.js.handlebars @@ -53,20 +53,18 @@
-
{{i18n admin.groups.primary}}
+
{{i18n admin.groups.title}}
- {{#if custom_groups}} - {{combo-box content=custom_groups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}} - {{else}} - — - {{/if}} + {{admin-group-selector selected=model.groups }}
+ {{#if custom_groups}} + {{i18n admin.groups.primary}} + {{combo-box content=custom_groups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}} + {{/if}} {{#if primaryGroupDirty}} -
-
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/admin-group-selector.js.handlebars b/app/assets/javascripts/discourse/templates/components/admin-group-selector.js.handlebars new file mode 100644 index 0000000000..f897810ab9 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/admin-group-selector.js.handlebars @@ -0,0 +1,3 @@ + + + diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index e5fd6d1ae8..b7a4123a3c 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -17,6 +17,8 @@ class Admin::UsersController < Admin::AdminController :block, :unblock, :trust_level, + :add_group, + :remove_group, :primary_group, :generate_api_key, :revoke_api_key] @@ -101,6 +103,21 @@ class Admin::UsersController < Admin::AdminController render_serialized(@user, AdminUserSerializer) end + def add_group + group = Group.find(params[:group_id].to_i) + return render_json_error group unless group && !group.automatic + group.users << @user + render nothing: true + end + + def remove_group + group = Group.find(params[:group_id].to_i) + return render_json_error group unless group && !group.automatic + group.users.delete(@user) + render nothing: true + end + + def primary_group guardian.ensure_can_change_primary_group!(@user) @user.primary_group_id = params[:primary_group_id] diff --git a/app/serializers/admin_detailed_user_serializer.rb b/app/serializers/admin_detailed_user_serializer.rb index 32ba08cff3..030e6aded0 100644 --- a/app/serializers/admin_detailed_user_serializer.rb +++ b/app/serializers/admin_detailed_user_serializer.rb @@ -22,7 +22,7 @@ class AdminDetailedUserSerializer < AdminUserSerializer has_one :api_key, serializer: ApiKeySerializer, embed: :objects has_one :suspended_by, serializer: BasicUserSerializer, embed: :objects has_one :leader_requirements, serializer: LeaderRequirementsSerializer, embed: :objects - has_many :custom_groups, embed: :object, serializer: BasicGroupSerializer + has_many :groups, embed: :object, serializer: BasicGroupSerializer def can_revoke_admin scope.can_revoke_admin?(object) diff --git a/config/routes.rb b/config/routes.rb index c7a5acddf7..779f5f9847 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -70,6 +70,8 @@ Discourse::Application.routes.draw do put "unblock" put "trust_level" put "primary_group" + post "groups" => "users#add_group", constraints: AdminConstraint.new + delete "groups/:group_id" => "users#remove_group", constraints: AdminConstraint.new get "badges" get "leader_requirements" end From 6710637904922a806b638b0324b0f55734a6e742 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 15 Jul 2014 15:35:16 +0200 Subject: [PATCH 2/6] renamed --- .../javascripts/admin/components/admin-group-selector.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/admin/components/admin-group-selector.js.es6 b/app/assets/javascripts/admin/components/admin-group-selector.js.es6 index c78f1ff899..08bcc619f4 100644 --- a/app/assets/javascripts/admin/components/admin-group-selector.js.es6 +++ b/app/assets/javascripts/admin/components/admin-group-selector.js.es6 @@ -26,14 +26,14 @@ export default Ember.Component.extend({ this.set("available", groups.filterBy("automatic", false)); }.bind(this)); - this.refreshOnReset(); + this._refreshOnReset(); }, _format: function(item){ return {"text": item.name, "id": item.id, "locked": item.automatic}; }, - refreshOnReset: function() { + _refreshOnReset: function() { this.$("input").select2("data", this.get("selected").map(this._format)); }.observes("selected") }); \ No newline at end of file From 2435961b58a24851d3f364c35f0af424f888894f Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 15 Jul 2014 15:43:53 +0200 Subject: [PATCH 3/6] use computed --- .../admin/controllers/admin_user_controller.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/admin/controllers/admin_user_controller.js b/app/assets/javascripts/admin/controllers/admin_user_controller.js index 5fc95f5050..277be26bd8 100644 --- a/app/assets/javascripts/admin/controllers/admin_user_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_user_controller.js @@ -25,11 +25,9 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({ primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'primary_group_id'), - custom_groups: function(){ - return this.get("model.groups").filter(function(g){ - return (!g.automatic && g.visible); - }); - }.property("model.groups.[]"), + custom_groups: Ember.computed.filter("model.groups", function(g){ + return (!g.automatic && g.visible); + }), actions: { toggleTitleEdit: function() { From 5025e9771284c54acd4ec4551e7c903ff4e89639 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 15 Jul 2014 15:48:02 +0200 Subject: [PATCH 4/6] move available groups loader into router --- .../javascripts/admin/components/admin-group-selector.js.es6 | 5 ----- app/assets/javascripts/admin/routes/admin_user_route.js | 5 +++++ .../javascripts/admin/templates/user_index.js.handlebars | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/admin/components/admin-group-selector.js.es6 b/app/assets/javascripts/admin/components/admin-group-selector.js.es6 index 08bcc619f4..4189beed3f 100644 --- a/app/assets/javascripts/admin/components/admin-group-selector.js.es6 +++ b/app/assets/javascripts/admin/components/admin-group-selector.js.es6 @@ -21,11 +21,6 @@ export default Ember.Component.extend({ ).findBy("id", evt.removed.id)}); } }.bind(this)); - - Discourse.Group.findAll().then(function(groups){ - this.set("available", groups.filterBy("automatic", false)); - }.bind(this)); - this._refreshOnReset(); }, diff --git a/app/assets/javascripts/admin/routes/admin_user_route.js b/app/assets/javascripts/admin/routes/admin_user_route.js index fd78a5ed6b..96af282ebc 100644 --- a/app/assets/javascripts/admin/routes/admin_user_route.js +++ b/app/assets/javascripts/admin/routes/admin_user_route.js @@ -23,6 +23,11 @@ Discourse.AdminUserRoute = Discourse.Route.extend({ afterModel: function(adminUser) { var controller = this.controllerFor('adminUser'); + Discourse.Group.findAll().then(function(groups){ + controller.set("availableGroups", groups.filterBy("automatic", false)); + }.bind(this)); + + return adminUser.loadDetails().then(function () { adminUser.setOriginalTrustLevel(); controller.set('model', adminUser); diff --git a/app/assets/javascripts/admin/templates/user_index.js.handlebars b/app/assets/javascripts/admin/templates/user_index.js.handlebars index fccb6d13a2..22d77122a8 100644 --- a/app/assets/javascripts/admin/templates/user_index.js.handlebars +++ b/app/assets/javascripts/admin/templates/user_index.js.handlebars @@ -55,7 +55,7 @@
{{i18n admin.groups.title}}
- {{admin-group-selector selected=model.groups }} + {{admin-group-selector selected=model.groups available=availableGroups}}
{{#if custom_groups}} From 518207aa90eee3bde10c05b7ff143034fbc99e49 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 15 Jul 2014 16:11:39 +0200 Subject: [PATCH 5/6] move ajax into models --- .../controllers/admin_user_controller.js | 16 +++------------- .../javascripts/admin/models/admin_user.js | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/admin/controllers/admin_user_controller.js b/app/assets/javascripts/admin/controllers/admin_user_controller.js index 277be26bd8..f909dec1d4 100644 --- a/app/assets/javascripts/admin/controllers/admin_user_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_user_controller.js @@ -50,23 +50,13 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({ }, groupAdded: function(added){ - var self = this; - Discourse.ajax("/admin/users/" + this.get('id') + "/groups", { - type: 'POST', - data: {group_id: added.id} - }).then(function () { - self.get('model.groups').pushObject(added); - }).catch(function() { + this.get('model').groupAdded(added).catch(function() { bootbox.alert(I18n.t('generic_error')); }); }, + groupRemoved: function(removed){ - var self = this; - Discourse.ajax("/admin/users/" + this.get('id') + "/groups/" + removed.id, { - type: 'DELETE' - }).then(function () { - self.set('model.groups.[]', self.get('model.groups').rejectBy("id", removed.id)); - }).catch(function() { + this.get('model').groupRemoved(removed).catch(function() { bootbox.alert(I18n.t('generic_error')); }); }, diff --git a/app/assets/javascripts/admin/models/admin_user.js b/app/assets/javascripts/admin/models/admin_user.js index 981e1be367..9cde5b2e8d 100644 --- a/app/assets/javascripts/admin/models/admin_user.js +++ b/app/assets/javascripts/admin/models/admin_user.js @@ -23,6 +23,25 @@ Discourse.AdminUser = Discourse.User.extend({ }); }, + groupAdded: function(added){ + var self = this; + return Discourse.ajax("/admin/users/" + this.get('id') + "/groups", { + type: 'POST', + data: {group_id: added.id} + }).then(function () { + self.get('groups').pushObject(added); + }); + }, + + groupRemoved: function(removed){ + var self = this; + return Discourse.ajax("/admin/users/" + this.get('id') + "/groups/" + removed.id, { + type: 'DELETE' + }).then(function () { + self.set('groups.[]', self.get('groups').rejectBy("id", removed.id)); + }) + }, + /** Revokes a user's current API key From 67d127ec5c2ea7bcfb1462b2027dd28c71bfb3f9 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 15 Jul 2014 17:12:03 +0200 Subject: [PATCH 6/6] make jshint happy: missing semicolon --- app/assets/javascripts/admin/models/admin_user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/models/admin_user.js b/app/assets/javascripts/admin/models/admin_user.js index 9cde5b2e8d..62a8fd73c5 100644 --- a/app/assets/javascripts/admin/models/admin_user.js +++ b/app/assets/javascripts/admin/models/admin_user.js @@ -39,7 +39,7 @@ Discourse.AdminUser = Discourse.User.extend({ type: 'DELETE' }).then(function () { self.set('groups.[]', self.get('groups').rejectBy("id", removed.id)); - }) + }); }, /**