FEATURE: Admin selector to choose a primary group for a user, display it
and apply a CSS class to their posts.
This commit is contained in:
parent
c97481623d
commit
b61df08d1b
|
@ -24,6 +24,8 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({
|
|||
return Discourse.SiteSettings.must_approve_users;
|
||||
}.property(),
|
||||
|
||||
primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'primary_group_id'),
|
||||
|
||||
actions: {
|
||||
toggleTitleEdit: function() {
|
||||
this.toggleProperty('editingTitle');
|
||||
|
@ -44,6 +46,22 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({
|
|||
this.get('model').generateApiKey();
|
||||
},
|
||||
|
||||
savePrimaryGroup: function() {
|
||||
var self = this;
|
||||
Discourse.ajax("/admin/users/" + this.get('id') + "/primary_group", {
|
||||
type: 'PUT',
|
||||
data: {primary_group_id: this.get('primary_group_id')}
|
||||
}).then(function () {
|
||||
self.set('originalPrimaryGroupId', self.get('primary_group_id'));
|
||||
}).catch(function() {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
},
|
||||
|
||||
resetPrimaryGroup: function() {
|
||||
this.set('primary_group_id', this.get('originalPrimaryGroupId'));
|
||||
},
|
||||
|
||||
regenerateApiKey: function() {
|
||||
var self = this;
|
||||
bootbox.confirm(I18n.t("admin.api.confirm_regen"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
|
|
|
@ -23,10 +23,16 @@ Discourse.AdminUserRoute = Discourse.Route.extend({
|
|||
afterModel: function(adminUser) {
|
||||
var controller = this.controllerFor('adminUser');
|
||||
|
||||
adminUser.loadDetails().then(function () {
|
||||
return adminUser.loadDetails().then(function () {
|
||||
adminUser.setOriginalTrustLevel();
|
||||
controller.set('model', adminUser);
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties({
|
||||
originalPrimaryGroupId: model.get('primary_group_id'),
|
||||
model: model
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -46,6 +46,25 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n admin.groups.primary}}</div>
|
||||
<div class='value'>
|
||||
{{#if custom_groups}}
|
||||
{{combobox content=custom_groups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if primaryGroupDirty}}
|
||||
<div>
|
||||
<button class='btn ok' {{action savePrimaryGroup}}><i class='fa fa-check'></i></button>
|
||||
<button class='btn cancel' {{action resetPrimaryGroup}}><i class='fa fa-times'></i></button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n user.ip_address.title}}</div>
|
||||
<div class='value'>{{ip_address}}</div>
|
||||
|
@ -317,7 +336,7 @@
|
|||
|
||||
<section>
|
||||
<hr/>
|
||||
<button {{bind-attr class=":btn :btn-danger :pull-right deleteForbidden:hidden"}} {{action destroy target="content"}} {{bind-attr disabled="deleteForbidden"}} {{bind-attr}}>
|
||||
<button {{bind-attr class=":btn :btn-danger :pull-right deleteForbidden:hidden"}} {{action destroy target="content"}} {{bind-attr disabled="deleteForbidden"}}>
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
{{i18n admin.user.delete}}
|
||||
</button>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
The view class for an Admin User
|
||||
|
||||
@class AdminUserView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AdminUserView = Discourse.View.extend(Discourse.ScrollTop);
|
||||
|
|
@ -25,7 +25,10 @@ Discourse.PostGapComponent = Ember.Component.extend({
|
|||
if (this.get('loading')) {
|
||||
buffer.push(I18n.t('loading'));
|
||||
} else {
|
||||
buffer.push(I18n.t('post.gap', {count: this.get('gap.length')}));
|
||||
var gapLength = this.get('gap.length');
|
||||
if (gapLength) {
|
||||
buffer.push(I18n.t('post.gap', {count: gapLength}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
{{/if}}
|
||||
|
||||
{{#if user_title}}<div class="user-title" {{action showPosterExpansion this}}>{{user_title}}</div>{{/if}}
|
||||
{{#if primary_group_name}}<div><a href='/groups/{{unbound primary_group_name}}' class='user-group'>{{unbound primary_group_name}}</a></div>{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="contents">
|
||||
|
|
|
@ -12,13 +12,21 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
|||
classNameBindings: ['postTypeClass',
|
||||
'selected',
|
||||
'post.hidden:post-hidden',
|
||||
'post.deleted'],
|
||||
'post.deleted',
|
||||
'groupNameClass'],
|
||||
postBinding: 'content',
|
||||
|
||||
postTypeClass: function() {
|
||||
return this.get('post.post_type') === Discourse.Site.currentProp('post_types.moderator_action') ? 'moderator' : 'regular';
|
||||
}.property('post.post_type'),
|
||||
|
||||
groupNameClass: function() {
|
||||
var primaryGroupName = this.get('post.primary_group_name');
|
||||
if (primaryGroupName) {
|
||||
return "group-" + primaryGroupName;
|
||||
}
|
||||
}.property('post.primary_group_name'),
|
||||
|
||||
// If the cooked content changed, add the quote controls
|
||||
cookedChanged: function() {
|
||||
var postView = this;
|
||||
|
|
|
@ -472,6 +472,15 @@ iframe {
|
|||
width: 45px;
|
||||
}
|
||||
|
||||
a.user-group {
|
||||
margin: 4px 0 0 0;
|
||||
padding: 0px;
|
||||
color: $primary_light;
|
||||
font-size: 80%;
|
||||
width: 100%;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
h3 a {
|
||||
display: inline;
|
||||
width: auto;
|
||||
|
@ -614,6 +623,7 @@ position: relative;
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.user-title {
|
||||
margin-top: 8px;
|
||||
color: $primary_light;
|
||||
|
|
|
@ -442,6 +442,7 @@ iframe {
|
|||
float: left;
|
||||
}
|
||||
|
||||
|
||||
.user-title {
|
||||
color: #aaa;
|
||||
padding-top: 2px;
|
||||
|
|
|
@ -17,6 +17,7 @@ class Admin::UsersController < Admin::AdminController
|
|||
:block,
|
||||
:unblock,
|
||||
:trust_level,
|
||||
:primary_group,
|
||||
:generate_api_key,
|
||||
:revoke_api_key]
|
||||
|
||||
|
@ -94,6 +95,13 @@ class Admin::UsersController < Admin::AdminController
|
|||
render_serialized(@user, AdminUserSerializer)
|
||||
end
|
||||
|
||||
def primary_group
|
||||
guardian.ensure_can_change_primary_group!(@user)
|
||||
@user.primary_group_id = params[:primary_group_id]
|
||||
@user.save!
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
def trust_level
|
||||
guardian.ensure_can_change_trust_level!(@user)
|
||||
logger = StaffActionLogger.new(current_user)
|
||||
|
|
|
@ -220,6 +220,7 @@ class Group < ActiveRecord::Base
|
|||
if @deletions
|
||||
@deletions.each do |gu|
|
||||
gu.destroy
|
||||
User.update_all 'primary_group_id = NULL', ['id = ? AND primary_group_id = ?', gu.user_id, gu.group_id]
|
||||
end
|
||||
end
|
||||
@deletions = nil
|
||||
|
|
|
@ -14,12 +14,14 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
|||
:private_topics_count,
|
||||
:can_delete_all_posts,
|
||||
:can_be_deleted,
|
||||
:suspend_reason
|
||||
:suspend_reason,
|
||||
:primary_group_id
|
||||
|
||||
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
|
||||
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
|
||||
|
||||
def can_revoke_admin
|
||||
scope.can_revoke_admin?(object)
|
||||
|
|
|
@ -23,6 +23,7 @@ class PostSerializer < BasicPostSerializer
|
|||
:topic_slug,
|
||||
:topic_id,
|
||||
:display_username,
|
||||
:primary_group_name,
|
||||
:version,
|
||||
:can_edit,
|
||||
:can_delete,
|
||||
|
@ -75,6 +76,11 @@ class PostSerializer < BasicPostSerializer
|
|||
object.user.try(:name)
|
||||
end
|
||||
|
||||
def primary_group_name
|
||||
return nil unless object.user
|
||||
return @topic_view.primary_group_names[object.user.primary_group_id] if object.user.primary_group_id
|
||||
end
|
||||
|
||||
def link_counts
|
||||
return @single_post_link_counts if @single_post_link_counts.present?
|
||||
|
||||
|
|
|
@ -1267,6 +1267,8 @@ en:
|
|||
other: "spam x{{count}}"
|
||||
|
||||
groups:
|
||||
primary: "Primary Group"
|
||||
no_primary: "(no primary group)"
|
||||
title: "Groups"
|
||||
edit: "Edit Groups"
|
||||
selector_placeholder: "add users"
|
||||
|
|
|
@ -58,6 +58,7 @@ Discourse::Application.routes.draw do
|
|||
put "block"
|
||||
put "unblock"
|
||||
put "trust_level"
|
||||
put "primary_group"
|
||||
get "leader_requirements"
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddPrimaryGroupIdToUsers < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :users, :primary_group_id, :integer, null: true
|
||||
end
|
||||
end
|
|
@ -133,6 +133,10 @@ class Guardian
|
|||
user && is_staff?
|
||||
end
|
||||
|
||||
def can_change_primary_group?(user)
|
||||
user && is_staff?
|
||||
end
|
||||
|
||||
def can_change_trust_level?(user)
|
||||
user && is_staff?
|
||||
end
|
||||
|
|
|
@ -111,6 +111,22 @@ class TopicView
|
|||
filter_posts_paged(opts[:page].to_i)
|
||||
end
|
||||
|
||||
def primary_group_names
|
||||
return @group_names if @group_names
|
||||
|
||||
primary_group_ids = Set.new
|
||||
@posts.each do |p|
|
||||
primary_group_ids << p.user.primary_group_id if p.user.try(:primary_group_id)
|
||||
end
|
||||
|
||||
result = {}
|
||||
unless primary_group_ids.empty?
|
||||
Group.where(id: primary_group_ids.to_a).pluck(:id, :name).each do |g|
|
||||
result[g[0]] = g[1]
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# Find the sort order for a post in the topic
|
||||
def sort_order_for_post_number(post_number)
|
||||
|
|
|
@ -140,6 +140,29 @@ describe Admin::UsersController do
|
|||
end
|
||||
end
|
||||
|
||||
context '.primary_group' do
|
||||
before do
|
||||
@another_user = Fabricate(:coding_horror)
|
||||
end
|
||||
|
||||
it "raises an error when the user doesn't have permission" do
|
||||
Guardian.any_instance.expects(:can_change_primary_group?).with(@another_user).returns(false)
|
||||
xhr :put, :primary_group, user_id: @another_user.id
|
||||
response.should be_forbidden
|
||||
end
|
||||
|
||||
it "returns a 404 if the user doesn't exist" do
|
||||
xhr :put, :primary_group, user_id: 123123
|
||||
response.should be_forbidden
|
||||
end
|
||||
|
||||
it "chagnes the user's trust level" do
|
||||
xhr :put, :primary_group, user_id: @another_user.id, primary_group_id: 2
|
||||
@another_user.reload
|
||||
@another_user.primary_group_id.should == 2
|
||||
end
|
||||
end
|
||||
|
||||
context '.trust_level' do
|
||||
before do
|
||||
@another_user = Fabricate(:coding_horror)
|
||||
|
|
|
@ -1050,4 +1050,35 @@ describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe "primary_group_id" do
|
||||
let!(:user) { Fabricate(:user) }
|
||||
|
||||
it "has no primary_group_id by default" do
|
||||
user.primary_group_id.should be_nil
|
||||
end
|
||||
|
||||
context "when the user has a group" do
|
||||
let!(:group) { Fabricate(:group) }
|
||||
|
||||
before do
|
||||
group.usernames = user.username
|
||||
group.save
|
||||
user.primary_group_id = group.id
|
||||
user.save
|
||||
user.reload
|
||||
end
|
||||
|
||||
it "should allow us to use it as a primary group" do
|
||||
user.primary_group_id.should == group.id
|
||||
|
||||
# If we remove the user from the group
|
||||
group.usernames = ""
|
||||
group.save
|
||||
|
||||
# It should unset it from the primary_group_id
|
||||
user.reload
|
||||
user.primary_group_id.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue