mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Allow columns on group members page to be sortable.
This commit is contained in:
parent
7e47fdf8da
commit
a2da2971af
@ -0,0 +1,24 @@
|
|||||||
|
import { iconHTML } from 'discourse-common/helpers/fa-icon';
|
||||||
|
import { bufferedRender } from 'discourse-common/lib/buffered-render';
|
||||||
|
|
||||||
|
export default Ember.Component.extend(bufferedRender({
|
||||||
|
tagName: 'th',
|
||||||
|
classNames: ['sortable'],
|
||||||
|
rerenderTriggers: ['order', 'asc'],
|
||||||
|
|
||||||
|
buildBuffer(buffer) {
|
||||||
|
buffer.push(I18n.t(this.get('i18nKey')));
|
||||||
|
|
||||||
|
if (this.get('field') === this.get('order')) {
|
||||||
|
buffer.push(iconHTML(this.get('asc') ? 'chevron-up' : 'chevron-down'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
click() {
|
||||||
|
if (this.get('order') === this.field) {
|
||||||
|
this.set('asc', this.get('asc') ? null : true);
|
||||||
|
} else {
|
||||||
|
this.setProperties({ order: this.field, asc: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
@ -1,12 +1,22 @@
|
|||||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
import Group from 'discourse/models/group';
|
import Group from 'discourse/models/group';
|
||||||
|
import { observes } from 'ember-addons/ember-computed-decorators';
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
export default Ember.Controller.extend({
|
||||||
|
queryParams: ['order', 'asc'],
|
||||||
|
order: 'last_posted_at',
|
||||||
|
asc: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
limit: null,
|
limit: null,
|
||||||
offset: null,
|
offset: null,
|
||||||
isOwner: Ember.computed.alias('model.is_group_owner'),
|
isOwner: Ember.computed.alias('model.is_group_owner'),
|
||||||
|
|
||||||
|
@observes('order', 'asc')
|
||||||
|
refreshMembers() {
|
||||||
|
this.get('model') &&
|
||||||
|
this.get('model').findMembers({ order: this.get('order'), asc: this.get('asc') });
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
removeMember(user) {
|
removeMember(user) {
|
||||||
this.get('model').removeMember(user);
|
this.get('model').removeMember(user);
|
||||||
@ -25,7 +35,12 @@ export default Ember.Controller.extend({
|
|||||||
|
|
||||||
this.set("loading", true);
|
this.set("loading", true);
|
||||||
|
|
||||||
Group.loadMembers(this.get("model.name"), this.get("model.members.length"), this.get("limit")).then(result => {
|
Group.loadMembers(
|
||||||
|
this.get("model.name"),
|
||||||
|
this.get("model.members.length"),
|
||||||
|
this.get("limit"),
|
||||||
|
{ order: this.get('order'), asc: this.get('asc') }
|
||||||
|
).then(result => {
|
||||||
this.get("model.members").addObjects(result.members.map(member => Discourse.User.create(member)));
|
this.get("model.members").addObjects(result.members.map(member => Discourse.User.create(member)));
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -24,12 +24,12 @@ const Group = Discourse.Model.extend({
|
|||||||
if (userCount > 0) { return userCount; }
|
if (userCount > 0) { return userCount; }
|
||||||
},
|
},
|
||||||
|
|
||||||
findMembers() {
|
findMembers(params) {
|
||||||
if (Em.isEmpty(this.get('name'))) { return ; }
|
if (Em.isEmpty(this.get('name'))) { return ; }
|
||||||
|
|
||||||
const self = this, offset = Math.min(this.get("user_count"), Math.max(this.get("offset"), 0));
|
const self = this, offset = Math.min(this.get("user_count"), Math.max(this.get("offset"), 0));
|
||||||
|
|
||||||
return Group.loadMembers(this.get("name"), offset, this.get("limit")).then(function (result) {
|
return Group.loadMembers(this.get("name"), offset, this.get("limit"), params).then(function (result) {
|
||||||
var ownerIds = {};
|
var ownerIds = {};
|
||||||
result.owners.forEach(owner => ownerIds[owner.id] = true);
|
result.owners.forEach(owner => ownerIds[owner.id] = true);
|
||||||
|
|
||||||
@ -177,12 +177,12 @@ Group.reopenClass({
|
|||||||
return ajax("/groups/" + name + ".json").then(result => Group.create(result.basic_group));
|
return ajax("/groups/" + name + ".json").then(result => Group.create(result.basic_group));
|
||||||
},
|
},
|
||||||
|
|
||||||
loadMembers(name, offset, limit) {
|
loadMembers(name, offset, limit, params) {
|
||||||
return ajax('/groups/' + name + '/members.json', {
|
return ajax('/groups/' + name + '/members.json', {
|
||||||
data: {
|
data: Object.assign({
|
||||||
limit: limit || 50,
|
limit: limit || 50,
|
||||||
offset: offset || 0
|
offset: offset || 0
|
||||||
}
|
}, params)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
export default Discourse.Route.extend({
|
export default Discourse.Route.extend({
|
||||||
|
|
||||||
titleToken() {
|
titleToken() {
|
||||||
return I18n.t('groups.members');
|
return I18n.t('groups.members');
|
||||||
},
|
},
|
||||||
@ -11,6 +10,6 @@ export default Discourse.Route.extend({
|
|||||||
setupController(controller, model) {
|
setupController(controller, model) {
|
||||||
this.controllerFor("group").set("showing", "members");
|
this.controllerFor("group").set("showing", "members");
|
||||||
controller.set("model", model);
|
controller.set("model", model);
|
||||||
model.findMembers();
|
controller.refreshMembers();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
<table class='group-members'>
|
<table class='group-members'>
|
||||||
<thead>
|
<thead>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>{{i18n 'last_post'}}</th>
|
{{group-index-toggle order=order asc=asc field='last_posted_at' i18nKey='last_post'}}
|
||||||
<th>{{i18n 'last_seen'}}</th>
|
{{group-index-toggle order=order asc=asc field='last_seen_at' i18nKey='last_seen'}}
|
||||||
<th></th>
|
<th></th>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
|
@ -17,13 +17,33 @@
|
|||||||
|
|
||||||
table.group-members {
|
table.group-members {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
|
||||||
th, tr {
|
th, tr {
|
||||||
border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
th:first-child {
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:last-child {
|
||||||
|
width: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
padding: 5px 0px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $tertiary;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
|
@ -72,12 +72,27 @@ class GroupsController < ApplicationController
|
|||||||
def members
|
def members
|
||||||
group = find_group(:group_id)
|
group = find_group(:group_id)
|
||||||
|
|
||||||
limit = (params[:limit] || 50).to_i
|
limit = (params[:limit] || 20).to_i
|
||||||
offset = params[:offset].to_i
|
offset = params[:offset].to_i
|
||||||
|
|
||||||
|
order = {}
|
||||||
|
|
||||||
|
if params[:order] && %w{last_posted_at last_seen_at}.include?(params[:order])
|
||||||
|
order.merge!({ params[:order] => params[:asc].blank? ? 'ASC' : 'DESC' })
|
||||||
|
end
|
||||||
|
|
||||||
total = group.users.count
|
total = group.users.count
|
||||||
members = group.users.order('NOT group_users.owner').order(:username_lower).limit(limit).offset(offset)
|
members = group.users
|
||||||
owners = group.users.order(:username_lower).where('group_users.owner')
|
.order('NOT group_users.owner')
|
||||||
|
.order(order)
|
||||||
|
.order(:username_lower)
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
|
||||||
|
owners = group.users
|
||||||
|
.order(order)
|
||||||
|
.order(:username_lower)
|
||||||
|
.where('group_users.owner')
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
members: serialize_data(members, GroupUserSerializer),
|
members: serialize_data(members, GroupUserSerializer),
|
||||||
|
@ -53,20 +53,19 @@ describe GroupsController do
|
|||||||
expect(response).to be_success
|
expect(response).to be_success
|
||||||
end
|
end
|
||||||
|
|
||||||
# Pending until we fix group truncation
|
it "ensures that membership can be paginated" do
|
||||||
skip "ensures that membership can be paginated" do
|
|
||||||
5.times { group.add(Fabricate(:user)) }
|
5.times { group.add(Fabricate(:user)) }
|
||||||
usernames = group.users.map{ |m| m['username'] }.sort
|
usernames = group.users.map{ |m| m.username }.sort
|
||||||
|
|
||||||
xhr :get, :members, group_id: group.name, limit: 3
|
xhr :get, :members, group_id: group.name, limit: 3
|
||||||
expect(response).to be_success
|
expect(response).to be_success
|
||||||
members = JSON.parse(response.body)
|
members = JSON.parse(response.body)["members"]
|
||||||
expect(members.map{ |m| m['username'] }).to eq(usernames[0..2])
|
expect(members.map { |m| m['username'] }).to eq(usernames[0..2])
|
||||||
|
|
||||||
xhr :get, :members, group_id: group.name, limit: 3, offset: 3
|
xhr :get, :members, group_id: group.name, limit: 3, offset: 3
|
||||||
expect(response).to be_success
|
expect(response).to be_success
|
||||||
members = JSON.parse(response.body)
|
members = JSON.parse(response.body)["members"]
|
||||||
expect(members.map{ |m| m['username'] }).to eq(usernames[3..4])
|
expect(members.map { |m| m['username'] }).to eq(usernames[3..4])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe "Groups" do
|
describe "Groups" do
|
||||||
let(:password) { 'somecomplicatedpassword' }
|
let(:user) { Fabricate(:user) }
|
||||||
let(:email_token) { Fabricate(:email_token, confirmed: true) }
|
|
||||||
let(:user) { email_token.user }
|
|
||||||
|
|
||||||
before do
|
def sign_in(user)
|
||||||
user.update_attributes!(password: password)
|
password = 'somecomplicatedpassword'
|
||||||
|
user.update!(password: password)
|
||||||
|
Fabricate(:email_token, confirmed: true, user: user)
|
||||||
post "/session.json", { login: user.username, password: password }
|
post "/session.json", { login: user.username, password: password }
|
||||||
expect(response).to be_success
|
expect(response).to be_success
|
||||||
end
|
end
|
||||||
@ -15,6 +15,7 @@ describe "Groups" do
|
|||||||
let(:group) { Fabricate(:group, name: 'test', users: [user]) }
|
let(:group) { Fabricate(:group, name: 'test', users: [user]) }
|
||||||
|
|
||||||
it "should return the right response" do
|
it "should return the right response" do
|
||||||
|
sign_in(user)
|
||||||
group
|
group
|
||||||
|
|
||||||
get "/groups/test/mentionable.json", { name: group.name }
|
get "/groups/test/mentionable.json", { name: group.name }
|
||||||
@ -40,6 +41,7 @@ describe "Groups" do
|
|||||||
context "when user is group owner" do
|
context "when user is group owner" do
|
||||||
before do
|
before do
|
||||||
group.add_owner(user)
|
group.add_owner(user)
|
||||||
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should be able update the group" do
|
it "should be able update the group" do
|
||||||
@ -66,6 +68,7 @@ describe "Groups" do
|
|||||||
context "when user is group admin" do
|
context "when user is group admin" do
|
||||||
before do
|
before do
|
||||||
user.update_attributes!(admin: true)
|
user.update_attributes!(admin: true)
|
||||||
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should be able to update the group' do
|
it 'should be able to update the group' do
|
||||||
@ -78,10 +81,68 @@ describe "Groups" do
|
|||||||
|
|
||||||
context "when user is not a group owner or admin" do
|
context "when user is not a group owner or admin" do
|
||||||
it 'should not be able to update the group' do
|
it 'should not be able to update the group' do
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
xhr :put, "/groups/#{group.id}", { group: { name: 'testing' } }
|
xhr :put, "/groups/#{group.id}", { group: { name: 'testing' } }
|
||||||
|
|
||||||
expect(response.status).to eq(403)
|
expect(response.status).to eq(403)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'members' do
|
||||||
|
let(:user1) do
|
||||||
|
Fabricate(:user,
|
||||||
|
last_seen_at: Time.zone.now,
|
||||||
|
last_posted_at: Time.zone.now - 1.day,
|
||||||
|
email: 'b@test.org'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:user2) do
|
||||||
|
Fabricate(:user,
|
||||||
|
last_seen_at: Time.zone.now - 1 .day,
|
||||||
|
last_posted_at: Time.zone.now,
|
||||||
|
email: 'a@test.org'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:group) { Fabricate(:group, users: [user1, user2]) }
|
||||||
|
|
||||||
|
it "should allow members to be sorted by" do
|
||||||
|
xhr :get, "/groups/#{group.name}/members", order: 'last_seen_at', asc: true
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
|
||||||
|
members = JSON.parse(response.body)["members"]
|
||||||
|
|
||||||
|
expect(members.map { |m| m["id"] }).to eq([user1.id, user2.id])
|
||||||
|
|
||||||
|
xhr :get, "/groups/#{group.name}/members", order: 'last_seen_at'
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
|
||||||
|
members = JSON.parse(response.body)["members"]
|
||||||
|
|
||||||
|
expect(members.map { |m| m["id"] }).to eq([user2.id, user1.id])
|
||||||
|
|
||||||
|
xhr :get, "/groups/#{group.name}/members", order: 'last_posted_at', asc: true
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
|
||||||
|
members = JSON.parse(response.body)["members"]
|
||||||
|
|
||||||
|
expect(members.map { |m| m["id"] }).to eq([user2.id, user1.id])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not allow members to be sorted by columns that are not allowed" do
|
||||||
|
xhr :get, "/groups/#{group.name}/members", order: 'email'
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
|
||||||
|
members = JSON.parse(response.body)["members"]
|
||||||
|
|
||||||
|
expect(members.map { |m| m["id"] }).to eq([user1.id, user2.id])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user