UX: Tweaks to group pages.

This commit is contained in:
Guo Xiang Tan 2018-03-29 14:57:10 +08:00
parent 27f06505b1
commit 52e75eaee9
29 changed files with 468 additions and 253 deletions

View File

@ -3,6 +3,8 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
import showModal from 'discourse/lib/show-modal'; import showModal from 'discourse/lib/show-modal';
export default Ember.Component.extend({ export default Ember.Component.extend({
classNames: ["group-membership-button"],
@computed("model.public_admission", "userIsGroupUser") @computed("model.public_admission", "userIsGroupUser")
canJoinGroup(publicAdmission, userIsGroupUser) { canJoinGroup(publicAdmission, userIsGroupUser) {
return publicAdmission && !userIsGroupUser; return publicAdmission && !userIsGroupUser;

View File

@ -124,16 +124,21 @@ const Group = RestModel.extend({
return mentionableLevel === '99'; return mentionableLevel === '99';
}, },
@computed("visibility_level")
isPrivate(visibilityLevel) {
return visibilityLevel !== 0;
},
@observes("visibility_level", "canEveryoneMention") @observes("visibility_level", "canEveryoneMention")
_updateAllowMembershipRequests() { _updateAllowMembershipRequests() {
if (this.get('visibility_level') !== 0 || !this.get('canEveryoneMention')) { if (this.get('isPrivate') || !this.get('canEveryoneMention')) {
this.set ('allow_membership_requests', false); this.set ('allow_membership_requests', false);
} }
}, },
@observes("visibility_level") @observes("visibility_level")
_updatePublic() { _updatePublic() {
if (this.get('visibility_level') !== 0) { if (this.get('isPrivate')) {
this.set('public', false); this.set('public', false);
this.set('allow_membership_requests', false); this.set('allow_membership_requests', false);
} }
@ -250,10 +255,6 @@ Group.reopenClass({
}); });
}, },
find(name) {
return ajax("/groups/" + name + ".json").then(result => Group.create(result.basic_group));
},
loadMembers(name, offset, limit, params) { loadMembers(name, offset, limit, params) {
return ajax('/groups/' + name + '/members.json', { return ajax('/groups/' + name + '/members.json', {
data: _.extend({ data: _.extend({

View File

@ -107,6 +107,11 @@ export default Ember.Object.extend({
var adapter = this.adapterFor(type); var adapter = this.adapterFor(type);
return adapter.find(this, type, findArgs, opts).then(result => { return adapter.find(this, type, findArgs, opts).then(result => {
var hydrated = this._hydrateFindResults(result, type, findArgs, opts); var hydrated = this._hydrateFindResults(result, type, findArgs, opts);
if (result.extras) {
hydrated.set('extras', result.extras);
}
if (adapter.cache) { if (adapter.cache) {
const stale = adapter.findStale(this, type, findArgs, opts); const stale = adapter.findStale(this, type, findArgs, opts);
hydrated = this._updateStale(stale, hydrated); hydrated = this._updateStale(stale, hydrated);

View File

@ -1,13 +1,10 @@
import Group from 'discourse/models/group';
export default Discourse.Route.extend({ export default Discourse.Route.extend({
titleToken() { titleToken() {
return [ this.modelFor('group').get('name') ]; return [ this.modelFor('group').get('name') ];
}, },
model(params) { model(params) {
return Group.find(params.name); return this.store.find("group", params.name);
}, },
serialize(model) { serialize(model) {
@ -15,6 +12,6 @@ export default Discourse.Route.extend({
}, },
setupController(controller, model) { setupController(controller, model) {
controller.setProperties({ model, counts: this.get('counts') }); controller.setProperties({ model });
} }
}); });

View File

@ -11,21 +11,9 @@
label="groups.leave" label="groups.leave"
disabled=updatingMembership}} disabled=updatingMembership}}
{{else if model.allow_membership_requests}} {{else if model.allow_membership_requests}}
{{#if userIsGroupUser}}
{{#if showMembershipStatus}}
{{d-button
class="btn-primary"
icon="user"
label="groups.is_group_user"
disabled=true}}
{{/if}}
{{else}}
{{d-button action="showRequestMembershipForm" {{d-button action="showRequestMembershipForm"
class="group-index-request" class="group-index-request"
disabled=loading disabled=loading
icon="user-plus" icon="user-plus"
label="groups.request"}} label="groups.request"}}
{{/if}} {{/if}}
{{else}}
{{yield}}
{{/if}}

View File

@ -1,4 +1,14 @@
{{#mobile-nav class='group-nav' desktopClass="nav nav-pills" currentPath=currentPath}} {{#mobile-nav class='group-nav' desktopClass="nav nav-pills" currentPath=currentPath}}
{{#if site.mobileView}}
<li>
{{#link-to "groups.index"}}
{{i18n "groups.index.all"}}
{{/link-to}}
</li>
{{else}}
{{group-dropdown content=group.extras.visible_group_names value=group.name}}
{{/if}}
{{#each tabs as |tab|}} {{#each tabs as |tab|}}
<li> <li>
{{#link-to tab.route group title=tab.message class=tab.name}} {{#link-to tab.route group title=tab.message class=tab.name}}

View File

@ -7,6 +7,7 @@
<table class='group-members'> <table class='group-members'>
<thead> <thead>
{{group-index-toggle order=order desc=desc field='username_lower' i18nKey='username'}} {{group-index-toggle order=order desc=desc field='username_lower' i18nKey='username'}}
<th>{{i18n "groups.members.owner"}}</th>
{{group-index-toggle order=order desc=desc field='last_posted_at' i18nKey='last_post'}} {{group-index-toggle order=order desc=desc field='last_posted_at' i18nKey='last_post'}}
{{group-index-toggle order=order desc=desc field='last_seen_at' i18nKey='last_seen'}} {{group-index-toggle order=order desc=desc field='last_seen_at' i18nKey='last_seen'}}
<th></th> <th></th>
@ -16,10 +17,17 @@
{{#each model.members as |m|}} {{#each model.members as |m|}}
<tr> <tr>
<td class='avatar'> <td class='avatar'>
{{#user-info user=m skipName=skipName}} {{user-info user=m skipName=skipName}}
{{#if m.owner}}<strong class="group-owner-label">{{i18n "groups.owner"}}</strong>{{/if}}
{{/user-info}}
</td> </td>
<td>
{{#if m.owner}}
<strong class="group-owner-label">
{{d-icon "shield"}}
</strong>
{{/if}}
</td>
<td> <td>
<span class="text">{{bound-date m.last_posted_at}}</span> <span class="text">{{bound-date m.last_posted_at}}</span>
</td> </td>

View File

@ -1,12 +1,5 @@
{{plugin-outlet name="before-group-container" args=(hash group=model)}} {{plugin-outlet name="before-group-container" args=(hash group=model)}}
{{#link-to "groups"}}
{{d-icon 'arrow-left'}}
{{i18n "groups.index.title"}}
{{/link-to}}
<hr>
<div class="container group {{model.name}}"> <div class="container group {{model.name}}">
<div class='group-details-container'> <div class='group-details-container'>
<div class='group-info'> <div class='group-info'>
@ -36,21 +29,26 @@
<p>{{{model.bio_cooked}}}</p> <p>{{{model.bio_cooked}}}</p>
</div> </div>
{{/if}} {{/if}}
<div class="group-details-button">
{{group-membership-button
class="inline"
model=model
showLogin='showLogin'}}
{{#if displayGroupMessageButton}}
{{d-button
action="messageGroup"
class="btn-primary group-message-button inline"
icon="envelope"
label="groups.message"}}
{{/if}}
</div>
</div> </div>
<div class="list-controls"> <div class="list-controls">
<div class="container"> <div class="container">
{{group-navigation group=model currentPath=application.currentPath tabs=tabs}} {{group-navigation group=model currentPath=application.currentPath tabs=tabs}}
{{#if displayGroupMessageButton}}
{{d-button
action="messageGroup"
class="btn-primary group-message-button"
icon="envelope"
label="groups.message"}}
{{/if}}
{{group-membership-button model=model showLogin='showLogin'}}
</div> </div>
</div> </div>

View File

@ -1,22 +1,24 @@
{{#d-section pageClass="groups"}} {{#d-section pageClass="groups"}}
<h1>{{i18n "groups.index.title"}}</h1> <div class="groups-header">
{{#if currentUser.admin}} {{#if currentUser.admin}}
<div class="list-controls"> {{d-button action="new"
{{group-admin-dropdown new="new"}} class="groups-header-new pull-right"
</div> icon="plus"
label="groups.new.title"}}
{{/if}} {{/if}}
<div class="groups-filter"> <div class="groups-header-filters">
{{text-field value=filterInput
placeholderKey="groups.index.all_groups"
class="groups-header-filters-name no-blur"}}
{{combo-box value=type {{combo-box value=type
content=types content=types
clearable=true clearable=true
none="groups.index.all_groups" allowAutoSelectFirst=false
class="groups-type-filter"}} placeholder="groups.index.filter"
class="groups-header-filters-type"}}
{{text-field value=filterInput </div>
placeholderKey="groups.filter_name"
class="groups-name-filter no-blur"}}
</div> </div>
{{#if model}} {{#if model}}
@ -25,10 +27,10 @@
<div class='container'> <div class='container'>
<table class="groups-table"> <table class="groups-table">
<thead> <thead>
<th></th> {{directory-toggle field="name" labelKey="groups.group_name" order=order asc=asc}}
{{directory-toggle field="user_count" labelKey="groups.user_count" order=order asc=asc}} {{directory-toggle field="user_count" labelKey="groups.user_count" order=order asc=asc}}
<th>{{i18n "groups.index.group_type"}}</th>
<th>{{i18n "groups.membership"}}</th> <th>{{i18n "groups.membership"}}</th>
<th></th>
</thead> </thead>
<tbody> <tbody>
@ -64,25 +66,31 @@
<td class="groups-user-count">{{group.user_count}}</td> <td class="groups-user-count">{{group.user_count}}</td>
<td> <td class="groups-table-type">
{{#group-membership-button model=group {{#if group.public_admission}}
showMembershipStatus=true {{i18n 'groups.index.public'}}
showLogin='showLogin'}} {{else if group.isPrivate}}
{{d-icon "eye-slash"}}
{{d-button icon="ban" {{i18n 'groups.index.private'}}
label=(if group.automatic 'groups.automatic_group' 'groups.close_group') {{else}}
disabled=true}} {{#if group.automatic}}
{{/group-membership-button}} {{i18n 'groups.index.automatic'}}
{{else}}
{{i18n 'groups.index.closed'}}
{{/if}}
{{/if}}
</td> </td>
<td class="group-user-status"> <td class="groups-table-membership">
{{#if group.is_group_user}} <span>
{{d-icon "user" title="groups.is_group_user"}}
{{/if}}
{{#if group.is_group_owner}} {{#if group.is_group_owner}}
{{d-icon "shield" title="groups.is_group_owner"}} {{i18n "groups.index.is_group_owner"}}
{{else if group.is_group_user}}
{{i18n "groups.index.is_group_user"}}
{{/if}} {{/if}}
</span>
{{group-membership-button model=group showLogin='showLogin'}}
</td> </td>
</tr> </tr>
{{/each}} {{/each}}

View File

@ -1,29 +0,0 @@
import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box";
export default DropdownSelectBoxComponent.extend({
classNames: "groups-admin-dropdown pull-right",
headerIcon: ["bars", "caret-down"],
showFullTitle: false,
computeContent() {
const items = [
{
id: "new",
name: I18n.t("groups.new.title"),
description: I18n.t("groups.new.description"),
icon: "plus"
}
];
return items;
},
mutateValue(value) {
switch (value) {
case 'new': {
this.sendAction("new");
break;
}
}
},
});

View File

@ -0,0 +1,44 @@
import ComboBoxComponent from "select-kit/components/combo-box";
import DiscourseURL from "discourse/lib/url";
import { default as computed } from "ember-addons/ember-computed-decorators";
export default ComboBoxComponent.extend({
pluginApiIdentifiers: ["group-dropdown"],
classNames: "group-dropdown",
content: Ember.computed.alias("groups"),
tagName: "li",
caretDownIcon: "caret-right fa-fw",
caretUpIcon: "caret-down fa-fw",
allowAutoSelectFirst: false,
valueAttribute: 'name',
@computed("content")
filterable(content) {
return content && content.length >= 10;
},
computeHeaderContent() {
let content = this._super();
if (!this.get("hasSelection")) {
content.label = `<span>${I18n.t("groups.index.all")}</span>`;
}
return content;
},
@computed
collectionHeader() {
return `
<a href="${Discourse.getURL("/groups")}" class="group-dropdown-filter">
${I18n.t("groups.index.all").toLowerCase()}
</a>
`.htmlSafe();
},
actions: {
onSelect(groupName) {
DiscourseURL.routeTo(Discourse.getURL(`/groups/${groupName}`));
}
}
});

View File

@ -2,6 +2,23 @@
background: rgba(230, 230, 230, 0.3); background: rgba(230, 230, 230, 0.3);
padding: 20px; padding: 20px;
margin-bottom: 15px; margin-bottom: 15px;
position: relative;
.group-details-button {
position: absolute;
top: 20px;
right: 20px;
}
}
.group-outlet {
position: relative;
}
.group-username-filter {
position: absolute;
right: 0;
top: -49px;
} }
.group-post { .group-post {
@ -120,7 +137,7 @@ table.group-members {
} }
th:first-child { th:first-child {
width: 60%; width: 30%;
text-align: left; text-align: left;
} }
@ -201,13 +218,8 @@ table.group-members {
} }
} }
.group-manage, .group-form-save {
.groups-new-page { margin-right: 20px;
.form-horizontal {
label {
font-weight: bold;
}
}
} }
.group-manage-members { .group-manage-members {

View File

@ -5,15 +5,18 @@
} }
} }
.groups-filter { .groups-header {
display: inline-block; margin-bottom: 30px;
float: right; }
.groups-type-filter { .groups-header-filters {
display: inline-block;
.groups-header-filters-type {
vertical-align: middle; vertical-align: middle;
} }
.groups-name-filter { .groups-header-filters-name {
vertical-align: middle; vertical-align: middle;
margin: 0; margin: 0;
} }
@ -39,8 +42,8 @@
border-bottom: 1px solid $primary-low; border-bottom: 1px solid $primary-low;
td { td {
color: blend-primary-secondary(50%);
padding: 0.8em; padding: 0.8em;
color: $primary-medium;
} }
td.groups-info { td.groups-info {
@ -54,7 +57,24 @@
} }
td.groups-user-count { td.groups-user-count {
font-size: $font-up-2 width: 17%;
font-size: $font-up-2;
}
td.groups-table-type {
width: 17%;
font-size: $font-up-1;
}
td.groups-table-membership {
.group-membership-button {
display: inline-block;
margin-left: 5px;
}
> span {
font-size: $font-up-1;
}
} }
} }

View File

@ -0,0 +1,76 @@
.select-kit {
&.combo-box {
&.group-dropdown {
min-width: auto;
.combo-box-header {
background: $primary-low;
color: $primary;
border: 1px solid transparent;
padding: 4.5px 5px 4.5px 10px;
font-size: $font-0;
transition: none;
.d-icon {
opacity: 1;
font-size: $font-0;
margin: 0;
}
}
&.is-expanded .tag-drop-header {
border: 1px solid $tertiary;
box-shadow: shadow("focus");
}
.select-kit-collection {
display: flex;
flex-direction: column;
padding: 0;
max-height: 300px;
.collection-header {
.group-dropdown-filter {
white-space: nowrap;
color: $primary;
font-size: $font-down-1;
line-height: $line-height-medium;
font-weight: bold;
display: block;
padding: 10px 5px;
&:hover {
text-decoration: underline;
}
}
}
}
.select-kit-filter .filter-input {
width: auto;
}
.select-kit-body {
width: auto;
min-width: 150px;
border-radius: 0;
box-shadow: shadow("dropdown");
}
.select-kit-row {
margin: 0;
font-size: $font-down-1;
font-weight: bold;
color: $tertiary;
&.no-content {
font-weight: normal;
}
}
&.is-expanded .select-kit-wrapper, .select-kit-wrapper {
display: none;
}
}
}
}

View File

@ -1,4 +1,12 @@
.group-nav { .group-nav {
.group-dropdown {
margin-right: 10px;
i {
color: $primary;
}
}
li { li {
float: left; float: left;

View File

@ -6,8 +6,8 @@
$filter-line-height: 1.5; $filter-line-height: 1.5;
.groups-filter { .groups-header-filters {
.groups-type-filter { .groups-header-filters-type {
.select-kit-header { .select-kit-header {
line-height: $filter-line-height; line-height: $filter-line-height;
} }

View File

@ -80,3 +80,8 @@ table.group-manage-logs {
} }
} }
} }
.group-username-filter {
top: -57px;
height: 27px;
}

View File

@ -3,12 +3,14 @@
margin-top: 20px; margin-top: 20px;
} }
.groups-filter { .groups-header-filters {
display: block; display: block;
float: none; float: none;
} }
.groups-name-filter { .groups-header-filters-name,
.groups-header-filters-type,
.groups-header-new {
margin-top: 10px; margin-top: 10px;
} }
} }

View File

@ -109,7 +109,18 @@ class GroupsController < ApplicationController
end end
format.json do format.json do
render_serialized(group, GroupShowSerializer, root: 'basic_group') groups = Group.visible_groups(current_user)
if !guardian.is_staff?
groups = groups.where(automatic: false)
end
render_json_dump(
group: serialize_data(group, GroupShowSerializer, root: nil),
extras: {
visible_group_names: groups.pluck(:name)
}
)
end end
end end
end end
@ -436,7 +447,8 @@ class GroupsController < ApplicationController
def find_group(param_name) def find_group(param_name)
name = params.require(param_name) name = params.require(param_name)
group = Group.find_by("lower(name) = ?", name.downcase) group = Group
group = group.find_by("lower(name) = ?", name.downcase)
guardian.ensure_can_see!(group) guardian.ensure_can_see!(group)
group group
end end

View File

@ -448,15 +448,10 @@ en:
topics: "There are no topics by members of this group." topics: "There are no topics by members of this group."
logs: "There are no logs for this group." logs: "There are no logs for this group."
add: "Add" add: "Add"
join: "Join Group" join: "Join"
leave: "Leave Group" leave: "Leave"
request: "Request to Join Group" request: "Request"
filter_name: "filter by group name"
message: "Message" message: "Message"
automatic_group: Automatic Group
close_group: Close Group
is_group_user: "You are a member of this group"
is_group_owner: "You are an owner of this group"
allow_membership_requests: "Allow users to send membership requests to group owners" allow_membership_requests: "Allow users to send membership requests to group owners"
membership_request_template: "Custom template to display to users when sending a membership request" membership_request_template: "Custom template to display to users when sending a membership request"
membership_request: membership_request:
@ -465,19 +460,31 @@ en:
reason: "Let the group owners know why you belong in this group" reason: "Let the group owners know why you belong in this group"
membership: "Membership" membership: "Membership"
name: "Name" name: "Name"
user_count: "Members Count" group_name: "Group name"
user_count: "Users"
bio: "About Group" bio: "About Group"
selector_placeholder: "enter username" selector_placeholder: "enter username"
owner: "owner" owner: "owner"
index: index:
title: "Groups" title: "Groups"
all: "All Groups"
empty: "There are no visible groups." empty: "There are no visible groups."
filter: "Filter by group type"
all_groups: "All Groups" all_groups: "All Groups"
owner_groups: "Groups I am an owner of" owner_groups: "Groups I am an owner of"
close_groups: "Close Groups" close_groups: "Close Groups"
automatic_groups: "Automatic Groups" automatic_groups: "Automatic Groups"
automatic: "Automatic"
closed: "Closed"
public: "Public"
private: "Private"
public_groups: "Public Groups" public_groups: "Public Groups"
automatic_group: Automatic Group
close_group: Close Group
my_groups: "My Groups" my_groups: "My Groups"
group_type: "Group type"
is_group_user: "Member"
is_group_owner: "Owner"
title: title:
one: "Group" one: "Group"
other: "Groups" other: "Groups"
@ -492,6 +499,7 @@ en:
make_owner_description: "Make <b>%{username}</b> an owner of this group" make_owner_description: "Make <b>%{username}</b> an owner of this group"
remove_owner: "Remove as Owner" remove_owner: "Remove as Owner"
remove_owner_description: "Remove <b>%{username}</b> as an owner of this group" remove_owner_description: "Remove <b>%{username}</b> as an owner of this group"
owner: "Owner"
topics: "Topics" topics: "Topics"
posts: "Posts" posts: "Posts"
mentions: "Mentions" mentions: "Mentions"

View File

@ -1,94 +0,0 @@
require 'rails_helper'
describe GroupsController do
let(:group) { Fabricate(:group) }
describe 'show' do
it "ensures the group can be seen" do
Guardian.any_instance.expects(:can_see?).with(group).returns(false)
get :show, params: { id: group.name }, format: :json
expect(response).not_to be_success
end
it "responds with JSON" do
Guardian.any_instance.expects(:can_see?).with(group).returns(true)
get :show, params: { id: group.name }, format: :json
expect(response).to be_success
expect(::JSON.parse(response.body)['basic_group']['id']).to eq(group.id)
end
it "works even with an upper case group name" do
Guardian.any_instance.expects(:can_see?).with(group).returns(true)
get :show, params: { id: group.name.upcase }, format: :json
expect(response).to be_success
expect(::JSON.parse(response.body)['basic_group']['id']).to eq(group.id)
end
end
describe "posts" do
it "ensures the group can be seen" do
Guardian.any_instance.expects(:can_see?).with(group).returns(false)
get :posts, params: { group_id: group.name }, format: :json
expect(response).not_to be_success
end
it "calls `posts_for` and responds with JSON" do
Guardian.any_instance.expects(:can_see?).with(group).returns(true)
Group.any_instance.expects(:posts_for).returns(Group.none)
get :posts, params: { group_id: group.name }, format: :json
expect(response).to be_success
end
end
describe "members" do
it "ensures the group can be seen" do
Guardian.any_instance.expects(:can_see?).with(group).returns(false)
get :members, params: { group_id: group.name }, format: :json
expect(response).not_to be_success
end
it "calls `posts_for` and responds with JSON" do
Guardian.any_instance.expects(:can_see?).with(group).returns(true)
get :posts, params: { group_id: group.name }, format: :json
expect(response).to be_success
end
it "ensures that membership can be paginated" do
5.times { group.add(Fabricate(:user)) }
usernames = group.users.map { |m| m.username }.sort
get :members, params: { group_id: group.name, limit: 3 }, format: :json
expect(response).to be_success
members = JSON.parse(response.body)["members"]
expect(members.map { |m| m['username'] }).to eq(usernames[0..2])
get :members, params: { group_id: group.name, limit: 3, offset: 3 }, format: :json
expect(response).to be_success
members = JSON.parse(response.body)["members"]
expect(members.map { |m| m['username'] }).to eq(usernames[3..4])
end
end
describe '.posts_feed' do
it 'renders RSS' do
get :posts_feed, params: { group_id: group.name }, format: :rss
expect(response).to be_success
expect(response.content_type).to eq('application/rss+xml')
end
end
describe '.mentions_feed' do
it 'renders RSS' do
get :mentions_feed, params: { group_id: group.name }, format: :rss
expect(response).to be_success
expect(response.content_type).to eq('application/rss+xml')
end
it 'fails when disabled' do
SiteSetting.enable_mentions = false
get :mentions_feed, params: { group_id: group.name }, format: :rss
expect(response).not_to be_success
end
end
end

View File

@ -206,6 +206,47 @@ describe GroupsController do
end end
describe '#show' do describe '#show' do
it "ensures the group can be seen" do
sign_in(Fabricate(:user))
group.update!(visibility_level: Group.visibility_levels[:owners])
get "/groups/#{group.name}.json"
expect(response.status).to eq(403)
end
it "returns the right response" do
sign_in(user)
get "/groups/#{group.name}.json"
expect(response.status).to eq(200)
response_body = JSON.parse(response.body)
expect(response_body['group']['id']).to eq(group.id)
expect(response_body['extras']["visible_group_names"]).to eq([group.name])
end
context 'as an admin' do
it "returns the right response" do
sign_in(Fabricate(:admin))
get "/groups/#{group.name}.json"
expect(response.status).to eq(200)
response_body = JSON.parse(response.body)
expect(response_body['group']['id']).to eq(group.id)
groups = Group::AUTO_GROUPS.keys
groups.delete(:everyone)
groups.push(group.name)
expect(response_body['extras']["visible_group_names"])
.to contain_exactly(*groups.map(&:to_s))
end
end
it 'should respond to HTML' do it 'should respond to HTML' do
group.update_attribute(:bio_cooked, 'testing group bio') group.update_attribute(:bio_cooked, 'testing group bio')
@ -228,13 +269,91 @@ describe GroupsController do
expect(response.status).to eq(200) expect(response.status).to eq(200)
response_body = JSON.parse(response.body)['basic_group'] response_body = JSON.parse(response.body)['group']
expect(response_body["id"]).to eq(group.id) expect(response_body["id"]).to eq(group.id)
end end
end end
end end
describe "#posts" do
it "ensures the group can be seen" do
sign_in(Fabricate(:user))
group.update!(visibility_level: Group.visibility_levels[:owners])
get "/groups/#{group.name}/posts.json"
expect(response.status).to eq(403)
end
it "calls `posts_for` and responds with JSON" do
sign_in(user)
post = Fabricate(:post, user: user)
get "/groups/#{group.name}/posts.json"
expect(response.status).to eq(200)
expect(JSON.parse(response.body).first["id"]).to eq(post.id)
end
end
describe "#members" do
it "ensures the group can be seen" do
sign_in(Fabricate(:user))
group.update!(visibility_level: Group.visibility_levels[:owners])
get "/groups/#{group.name}/members.json"
expect(response.status).to eq(403)
end
it "ensures that membership can be paginated" do
5.times { group.add(Fabricate(:user)) }
usernames = group.users.map { |m| m.username }.sort
get "/groups/#{group.name}/members.json", params: { limit: 3 }
expect(response.status).to eq(200)
members = JSON.parse(response.body)["members"]
expect(members.map { |m| m['username'] }).to eq(usernames[0..2])
get "/groups/#{group.name}/members.json", params: { limit: 3, offset: 3 }
expect(response.status).to eq(200)
members = JSON.parse(response.body)["members"]
expect(members.map { |m| m['username'] }).to eq(usernames[3..5])
end
end
describe '#posts_feed' do
it 'renders RSS' do
get "/groups/#{group.name}/posts.rss"
expect(response.status).to eq(200)
expect(response.content_type).to eq('application/rss+xml')
end
end
describe '#mentions_feed' do
it 'renders RSS' do
get "/groups/#{group.name}/mentions.rss"
expect(response.status).to eq(200)
expect(response.content_type).to eq('application/rss+xml')
end
it 'fails when disabled' do
SiteSetting.enable_mentions = false
get "/groups/#{group.name}/mentions.rss"
expect(response.status).to eq(404)
end
end
describe '#mentionable' do describe '#mentionable' do
it "should return the right response" do it "should return the right response" do
sign_in(user) sign_in(user)

View File

@ -11,8 +11,8 @@ acceptance("Group logs", {
]; ];
}; };
server.get('/groups/snorlax.json', () => { // eslint-disable-line no-undef server.get('/groups/snorlax', () => { // eslint-disable-line no-undef
return response({"basic_group":{"id":41,"automatic":false,"name":"snorlax","user_count":1,"alias_level":0,"visible":true,"automatic_membership_email_domains":"","automatic_membership_retroactive":false,"primary_group":true,"title":"Team Snorlax","grant_trust_level":null,"incoming_email":null,"has_messages":false,"flair_url":"","flair_bg_color":"","flair_color":"","bio_raw":"","bio_cooked":null,"public":true,"is_group_user":true,"is_group_owner":true}}); return response({"group":{"id":41,"automatic":false,"name":"snorlax","user_count":1,"alias_level":0,"visible":true,"automatic_membership_email_domains":"","automatic_membership_retroactive":false,"primary_group":true,"title":"Team Snorlax","grant_trust_level":null,"incoming_email":null,"has_messages":false,"flair_url":"","flair_bg_color":"","flair_color":"","bio_raw":"","bio_cooked":null,"public":true,"is_group_user":true,"is_group_owner":true}});
}); });
// Workaround while awaiting https://github.com/tildeio/route-recognizer/issues/53 // Workaround while awaiting https://github.com/tildeio/route-recognizer/issues/53

View File

@ -45,6 +45,15 @@ QUnit.test("Anonymous Viewing Group", assert => {
assert.ok(find(".nav-pills li a[title='Logs']").length === 0, 'it should not show Logs tab if user is not admin'); assert.ok(find(".nav-pills li a[title='Logs']").length === 0, 'it should not show Logs tab if user is not admin');
assert.ok(count('.group-post') > 0, "it lists stream items"); assert.ok(count('.group-post') > 0, "it lists stream items");
}); });
selectKit('.group-dropdown').expand();
andThen(() => {
assert.equal(
find('.select-kit-row').text().trim(), 'discourse',
'it displays the right row'
);
});
}); });
QUnit.test("User Viewing Group", assert => { QUnit.test("User Viewing Group", assert => {

View File

@ -19,7 +19,7 @@ QUnit.test("Creating a new group", assert => {
visit("/groups"); visit("/groups");
selectKit('.groups-admin-dropdown').expand().selectRowByValue("new"); click(".groups-header-new");
fillIn("input[name='name']", '1'); fillIn("input[name='name']", '1');
andThen(() => { andThen(() => {

View File

@ -1,6 +1,6 @@
export default { export default {
"/groups/discourse.json":{ "/groups/discourse":{
"basic_group":{ "group":{
"id":47, "id":47,
"automatic":false, "automatic":false,
"name":"discourse", "name":"discourse",
@ -14,6 +14,9 @@ export default {
"is_group_owner":true, "is_group_owner":true,
"mentionable":true, "mentionable":true,
"messageable":true "messageable":true
},
"extras": {
"visible_group_names": ["discourse"]
} }
}, },
"/topics/groups/discourse.json":{ "/topics/groups/discourse.json":{

View File

@ -53,7 +53,7 @@ export default function(helpers) {
this.get('/widgets/:widget_id', function(request) { this.get('/widgets/:widget_id', function(request) {
const w = _widgets.findBy('id', parseInt(request.params.widget_id)); const w = _widgets.findBy('id', parseInt(request.params.widget_id));
if (w) { if (w) {
return response({widget: w}); return response({ widget: w, extras: { hello: 'world' }});
} else { } else {
return response(404); return response(404);
} }

View File

@ -49,14 +49,17 @@ QUnit.test('createRecord with a record as attributes returns that record from th
QUnit.test('find', assert => { QUnit.test('find', assert => {
const store = createStore(); const store = createStore();
return store.find('widget', 123).then(function(w) { return store.find('widget', 123).then(function(w) {
assert.equal(w.get('name'), 'Trout Lure'); assert.equal(w.get('name'), 'Trout Lure');
assert.equal(w.get('id'), 123); assert.equal(w.get('id'), 123);
assert.ok(!w.get('isNew'), 'found records are not new'); assert.ok(!w.get('isNew'), 'found records are not new');
assert.equal(w.get('extras.hello'), 'world', "extra attributes are set");
// A second find by id returns the same object // A second find by id returns the same object
store.find('widget', 123).then(function(w2) { store.find('widget', 123).then(function(w2) {
assert.equal(w, w2); assert.equal(w, w2);
assert.equal(w.get('extras.hello'), 'world', "extra attributes are set");
}); });
}); });
}); });