diff --git a/app/assets/javascripts/discourse/components/group-membership-button.js.es6 b/app/assets/javascripts/discourse/components/group-membership-button.js.es6 index 167eade276c..52478ada28d 100644 --- a/app/assets/javascripts/discourse/components/group-membership-button.js.es6 +++ b/app/assets/javascripts/discourse/components/group-membership-button.js.es6 @@ -3,6 +3,8 @@ import { popupAjaxError } from 'discourse/lib/ajax-error'; import showModal from 'discourse/lib/show-modal'; export default Ember.Component.extend({ + classNames: ["group-membership-button"], + @computed("model.public_admission", "userIsGroupUser") canJoinGroup(publicAdmission, userIsGroupUser) { return publicAdmission && !userIsGroupUser; diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6 index 4ad96c34fb6..8420969644b 100644 --- a/app/assets/javascripts/discourse/models/group.js.es6 +++ b/app/assets/javascripts/discourse/models/group.js.es6 @@ -124,16 +124,21 @@ const Group = RestModel.extend({ return mentionableLevel === '99'; }, + @computed("visibility_level") + isPrivate(visibilityLevel) { + return visibilityLevel !== 0; + }, + @observes("visibility_level", "canEveryoneMention") _updateAllowMembershipRequests() { - if (this.get('visibility_level') !== 0 || !this.get('canEveryoneMention')) { + if (this.get('isPrivate') || !this.get('canEveryoneMention')) { this.set ('allow_membership_requests', false); } }, @observes("visibility_level") _updatePublic() { - if (this.get('visibility_level') !== 0) { + if (this.get('isPrivate')) { this.set('public', 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) { return ajax('/groups/' + name + '/members.json', { data: _.extend({ diff --git a/app/assets/javascripts/discourse/models/store.js.es6 b/app/assets/javascripts/discourse/models/store.js.es6 index d198883b58c..efd364ed341 100644 --- a/app/assets/javascripts/discourse/models/store.js.es6 +++ b/app/assets/javascripts/discourse/models/store.js.es6 @@ -107,6 +107,11 @@ export default Ember.Object.extend({ var adapter = this.adapterFor(type); return adapter.find(this, type, findArgs, opts).then(result => { var hydrated = this._hydrateFindResults(result, type, findArgs, opts); + + if (result.extras) { + hydrated.set('extras', result.extras); + } + if (adapter.cache) { const stale = adapter.findStale(this, type, findArgs, opts); hydrated = this._updateStale(stale, hydrated); diff --git a/app/assets/javascripts/discourse/routes/group.js.es6 b/app/assets/javascripts/discourse/routes/group.js.es6 index 3df20f794bf..11926198cca 100644 --- a/app/assets/javascripts/discourse/routes/group.js.es6 +++ b/app/assets/javascripts/discourse/routes/group.js.es6 @@ -1,13 +1,10 @@ -import Group from 'discourse/models/group'; - export default Discourse.Route.extend({ - titleToken() { return [ this.modelFor('group').get('name') ]; }, model(params) { - return Group.find(params.name); + return this.store.find("group", params.name); }, serialize(model) { @@ -15,6 +12,6 @@ export default Discourse.Route.extend({ }, setupController(controller, model) { - controller.setProperties({ model, counts: this.get('counts') }); + controller.setProperties({ model }); } }); diff --git a/app/assets/javascripts/discourse/templates/components/group-membership-button.hbs b/app/assets/javascripts/discourse/templates/components/group-membership-button.hbs index 435f77fa52d..2bd19564927 100644 --- a/app/assets/javascripts/discourse/templates/components/group-membership-button.hbs +++ b/app/assets/javascripts/discourse/templates/components/group-membership-button.hbs @@ -11,21 +11,9 @@ label="groups.leave" disabled=updatingMembership}} {{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" - class="group-index-request" - disabled=loading - icon="user-plus" - label="groups.request"}} - {{/if}} -{{else}} - {{yield}} + {{d-button action="showRequestMembershipForm" + class="group-index-request" + disabled=loading + icon="user-plus" + label="groups.request"}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/group-navigation.hbs b/app/assets/javascripts/discourse/templates/components/group-navigation.hbs index b97cdfa040a..29ad6b7f1e1 100644 --- a/app/assets/javascripts/discourse/templates/components/group-navigation.hbs +++ b/app/assets/javascripts/discourse/templates/components/group-navigation.hbs @@ -1,4 +1,14 @@ {{#mobile-nav class='group-nav' desktopClass="nav nav-pills" currentPath=currentPath}} + {{#if site.mobileView}} +
  • + {{#link-to "groups.index"}} + {{i18n "groups.index.all"}} + {{/link-to}} +
  • + {{else}} + {{group-dropdown content=group.extras.visible_group_names value=group.name}} + {{/if}} + {{#each tabs as |tab|}}
  • {{#link-to tab.route group title=tab.message class=tab.name}} diff --git a/app/assets/javascripts/discourse/templates/group-index.hbs b/app/assets/javascripts/discourse/templates/group-index.hbs index e3d578ac962..1da552bdc92 100644 --- a/app/assets/javascripts/discourse/templates/group-index.hbs +++ b/app/assets/javascripts/discourse/templates/group-index.hbs @@ -7,6 +7,7 @@ {{group-index-toggle order=order desc=desc field='username_lower' i18nKey='username'}} + {{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'}} @@ -16,10 +17,17 @@ {{#each model.members as |m|}} + + + diff --git a/app/assets/javascripts/discourse/templates/group.hbs b/app/assets/javascripts/discourse/templates/group.hbs index dfb900b91ed..e41a22b8c5c 100644 --- a/app/assets/javascripts/discourse/templates/group.hbs +++ b/app/assets/javascripts/discourse/templates/group.hbs @@ -1,12 +1,5 @@ {{plugin-outlet name="before-group-container" args=(hash group=model)}} -{{#link-to "groups"}} - {{d-icon 'arrow-left'}} - {{i18n "groups.index.title"}} -{{/link-to}} - -
    -
    @@ -36,21 +29,26 @@

    {{{model.bio_cooked}}}

    {{/if}} + +
    + {{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}} +
    {{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'}}
    diff --git a/app/assets/javascripts/discourse/templates/groups/index.hbs b/app/assets/javascripts/discourse/templates/groups/index.hbs index c86bc76ab85..991fed8dbc5 100644 --- a/app/assets/javascripts/discourse/templates/groups/index.hbs +++ b/app/assets/javascripts/discourse/templates/groups/index.hbs @@ -1,22 +1,24 @@ {{#d-section pageClass="groups"}} -

    {{i18n "groups.index.title"}}

    +
    + {{#if currentUser.admin}} + {{d-button action="new" + class="groups-header-new pull-right" + icon="plus" + label="groups.new.title"}} + {{/if}} - {{#if currentUser.admin}} -
    - {{group-admin-dropdown new="new"}} +
    + {{text-field value=filterInput + placeholderKey="groups.index.all_groups" + class="groups-header-filters-name no-blur"}} + + {{combo-box value=type + content=types + clearable=true + allowAutoSelectFirst=false + placeholder="groups.index.filter" + class="groups-header-filters-type"}}
    - {{/if}} - -
    - {{combo-box value=type - content=types - clearable=true - none="groups.index.all_groups" - class="groups-type-filter"}} - - {{text-field value=filterInput - placeholderKey="groups.filter_name" - class="groups-name-filter no-blur"}}
    {{#if model}} @@ -25,10 +27,10 @@
    {{i18n "groups.members.owner"}}
    - {{#user-info user=m skipName=skipName}} - {{#if m.owner}}{{i18n "groups.owner"}}{{/if}} - {{/user-info}} + {{user-info user=m skipName=skipName}} + {{#if m.owner}} + + {{d-icon "shield"}} + + {{/if}} + {{bound-date m.last_posted_at}}
    - + {{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}} + - @@ -64,25 +66,31 @@ - - {{/each}} diff --git a/app/assets/javascripts/select-kit/components/group-admin-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/group-admin-dropdown.js.es6 deleted file mode 100644 index 67817ef81ed..00000000000 --- a/app/assets/javascripts/select-kit/components/group-admin-dropdown.js.es6 +++ /dev/null @@ -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; - } - } - }, -}); diff --git a/app/assets/javascripts/select-kit/components/group-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/group-dropdown.js.es6 new file mode 100644 index 00000000000..4b8d7ab25db --- /dev/null +++ b/app/assets/javascripts/select-kit/components/group-dropdown.js.es6 @@ -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 = `${I18n.t("groups.index.all")}`; + } + + return content; + }, + + @computed + collectionHeader() { + return ` + + ${I18n.t("groups.index.all").toLowerCase()} + + `.htmlSafe(); + }, + + actions: { + onSelect(groupName) { + DiscourseURL.routeTo(Discourse.getURL(`/groups/${groupName}`)); + } + } +}); diff --git a/app/assets/stylesheets/common/base/directory.scss b/app/assets/stylesheets/common/base/directory.scss index 65114a11b57..516922baed6 100644 --- a/app/assets/stylesheets/common/base/directory.scss +++ b/app/assets/stylesheets/common/base/directory.scss @@ -1,10 +1,10 @@ .directory { margin-bottom: 100px; - + .user-info { margin-bottom: 0; } - + .period-chooser { float: left; } @@ -18,25 +18,25 @@ .spinner { clear: both; } - + table { width: 100%; margin-bottom: 1em; - + td, th { padding: 0.5em; text-align: left; - border-bottom: 1px solid $primary-low; - + border-bottom: 1px solid $primary-low; + .number, .time-read { font-size: $font-up-3; - color: $primary-medium; + color: $primary-medium; } .time-read { white-space: nowrap; } } - + th.sortable { cursor: pointer; white-space: nowrap; @@ -48,9 +48,9 @@ .d-icon-chevron-down, .d-icon-chevron-up { margin-left: 0.5em; } - + &:hover { - background-color: $primary-low; + background-color: $primary-low; } } } diff --git a/app/assets/stylesheets/common/base/group.scss b/app/assets/stylesheets/common/base/group.scss index b0e90f9f0de..452e79f9cee 100644 --- a/app/assets/stylesheets/common/base/group.scss +++ b/app/assets/stylesheets/common/base/group.scss @@ -2,6 +2,23 @@ background: rgba(230, 230, 230, 0.3); padding: 20px; 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 { @@ -120,7 +137,7 @@ table.group-members { } th:first-child { - width: 60%; + width: 30%; text-align: left; } @@ -201,13 +218,8 @@ table.group-members { } } -.group-manage, -.groups-new-page { - .form-horizontal { - label { - font-weight: bold; - } - } +.group-form-save { + margin-right: 20px; } .group-manage-members { diff --git a/app/assets/stylesheets/common/base/groups.scss b/app/assets/stylesheets/common/base/groups.scss index 1c8ae40b3fa..75585210384 100644 --- a/app/assets/stylesheets/common/base/groups.scss +++ b/app/assets/stylesheets/common/base/groups.scss @@ -5,15 +5,18 @@ } } -.groups-filter { - display: inline-block; - float: right; +.groups-header { + margin-bottom: 30px; +} - .groups-type-filter { +.groups-header-filters { + display: inline-block; + + .groups-header-filters-type { vertical-align: middle; } - .groups-name-filter { + .groups-header-filters-name { vertical-align: middle; margin: 0; } @@ -39,8 +42,8 @@ border-bottom: 1px solid $primary-low; td { - color: blend-primary-secondary(50%); padding: 0.8em; + color: $primary-medium; } td.groups-info { @@ -54,9 +57,26 @@ } 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; + } + } + } .groups-info { .groups-info-name { diff --git a/app/assets/stylesheets/common/select-kit/group-dropdown.scss b/app/assets/stylesheets/common/select-kit/group-dropdown.scss new file mode 100644 index 00000000000..e38c0a42833 --- /dev/null +++ b/app/assets/stylesheets/common/select-kit/group-dropdown.scss @@ -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; + } + } + } +} diff --git a/app/assets/stylesheets/desktop/group.scss b/app/assets/stylesheets/desktop/group.scss index 651d154d5aa..dc1102d658c 100644 --- a/app/assets/stylesheets/desktop/group.scss +++ b/app/assets/stylesheets/desktop/group.scss @@ -1,4 +1,12 @@ .group-nav { + .group-dropdown { + margin-right: 10px; + + i { + color: $primary; + } + } + li { float: left; diff --git a/app/assets/stylesheets/desktop/groups.scss b/app/assets/stylesheets/desktop/groups.scss index 5c12eae2921..a43ce6edba7 100644 --- a/app/assets/stylesheets/desktop/groups.scss +++ b/app/assets/stylesheets/desktop/groups.scss @@ -6,8 +6,8 @@ $filter-line-height: 1.5; -.groups-filter { - .groups-type-filter { +.groups-header-filters { + .groups-header-filters-type { .select-kit-header { line-height: $filter-line-height; } diff --git a/app/assets/stylesheets/mobile/group.scss b/app/assets/stylesheets/mobile/group.scss index c7e3670e6e6..d6fd23eb982 100644 --- a/app/assets/stylesheets/mobile/group.scss +++ b/app/assets/stylesheets/mobile/group.scss @@ -80,3 +80,8 @@ table.group-manage-logs { } } } + +.group-username-filter { + top: -57px; + height: 27px; +} diff --git a/app/assets/stylesheets/mobile/groups.scss b/app/assets/stylesheets/mobile/groups.scss index 71d3caa4f9e..a7e6df7af16 100644 --- a/app/assets/stylesheets/mobile/groups.scss +++ b/app/assets/stylesheets/mobile/groups.scss @@ -3,12 +3,14 @@ margin-top: 20px; } - .groups-filter { + .groups-header-filters { display: block; float: none; } - .groups-name-filter { + .groups-header-filters-name, + .groups-header-filters-type, + .groups-header-new { margin-top: 10px; } } diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index a660d2a628e..8233d5ab99e 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -109,7 +109,18 @@ class GroupsController < ApplicationController end 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 @@ -436,7 +447,8 @@ class GroupsController < ApplicationController def find_group(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) group end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index ebd4ee53465..e0a38831552 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -448,15 +448,10 @@ en: topics: "There are no topics by members of this group." logs: "There are no logs for this group." add: "Add" - join: "Join Group" - leave: "Leave Group" - request: "Request to Join Group" - filter_name: "filter by group name" + join: "Join" + leave: "Leave" + request: "Request" 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" membership_request_template: "Custom template to display to users when sending a membership request" membership_request: @@ -465,19 +460,31 @@ en: reason: "Let the group owners know why you belong in this group" membership: "Membership" name: "Name" - user_count: "Members Count" + group_name: "Group name" + user_count: "Users" bio: "About Group" selector_placeholder: "enter username" owner: "owner" index: title: "Groups" + all: "All Groups" empty: "There are no visible groups." + filter: "Filter by group type" all_groups: "All Groups" owner_groups: "Groups I am an owner of" close_groups: "Close Groups" automatic_groups: "Automatic Groups" + automatic: "Automatic" + closed: "Closed" + public: "Public" + private: "Private" public_groups: "Public Groups" + automatic_group: Automatic Group + close_group: Close Group my_groups: "My Groups" + group_type: "Group type" + is_group_user: "Member" + is_group_owner: "Owner" title: one: "Group" other: "Groups" @@ -492,6 +499,7 @@ en: make_owner_description: "Make %{username} an owner of this group" remove_owner: "Remove as Owner" remove_owner_description: "Remove %{username} as an owner of this group" + owner: "Owner" topics: "Topics" posts: "Posts" mentions: "Mentions" diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb deleted file mode 100644 index 05b082a9351..00000000000 --- a/spec/controllers/groups_controller_spec.rb +++ /dev/null @@ -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 diff --git a/spec/requests/groups_controller_spec.rb b/spec/requests/groups_controller_spec.rb index eefb9048e8c..67e2afa1a29 100644 --- a/spec/requests/groups_controller_spec.rb +++ b/spec/requests/groups_controller_spec.rb @@ -206,6 +206,47 @@ describe GroupsController do end 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 group.update_attribute(:bio_cooked, 'testing group bio') @@ -228,13 +269,91 @@ describe GroupsController do 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) 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 it "should return the right response" do sign_in(user) diff --git a/test/javascripts/acceptance/group-manage-logs-test.js.es6 b/test/javascripts/acceptance/group-manage-logs-test.js.es6 index 285ce77899b..26446972942 100644 --- a/test/javascripts/acceptance/group-manage-logs-test.js.es6 +++ b/test/javascripts/acceptance/group-manage-logs-test.js.es6 @@ -11,8 +11,8 @@ acceptance("Group logs", { ]; }; - server.get('/groups/snorlax.json', () => { // 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}}); + server.get('/groups/snorlax', () => { // eslint-disable-line no-undef + 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 diff --git a/test/javascripts/acceptance/group-test.js.es6 b/test/javascripts/acceptance/group-test.js.es6 index 0e0181085ca..21ed7a9d97c 100644 --- a/test/javascripts/acceptance/group-test.js.es6 +++ b/test/javascripts/acceptance/group-test.js.es6 @@ -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(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 => { diff --git a/test/javascripts/acceptance/groups-new-test.js.es6 b/test/javascripts/acceptance/groups-new-test.js.es6 index 7d8b3d9f744..9c737e63c2e 100644 --- a/test/javascripts/acceptance/groups-new-test.js.es6 +++ b/test/javascripts/acceptance/groups-new-test.js.es6 @@ -19,7 +19,7 @@ QUnit.test("Creating a new group", assert => { visit("/groups"); - selectKit('.groups-admin-dropdown').expand().selectRowByValue("new"); + click(".groups-header-new"); fillIn("input[name='name']", '1'); andThen(() => { diff --git a/test/javascripts/fixtures/group-fixtures.js.es6 b/test/javascripts/fixtures/group-fixtures.js.es6 index 27a4a6bbf15..44e616cb17f 100644 --- a/test/javascripts/fixtures/group-fixtures.js.es6 +++ b/test/javascripts/fixtures/group-fixtures.js.es6 @@ -1,6 +1,6 @@ export default { - "/groups/discourse.json":{ - "basic_group":{ + "/groups/discourse":{ + "group":{ "id":47, "automatic":false, "name":"discourse", @@ -14,6 +14,9 @@ export default { "is_group_owner":true, "mentionable":true, "messageable":true + }, + "extras": { + "visible_group_names": ["discourse"] } }, "/topics/groups/discourse.json":{ diff --git a/test/javascripts/helpers/store-pretender.js.es6 b/test/javascripts/helpers/store-pretender.js.es6 index d86f1260e81..e0420538821 100644 --- a/test/javascripts/helpers/store-pretender.js.es6 +++ b/test/javascripts/helpers/store-pretender.js.es6 @@ -53,7 +53,7 @@ export default function(helpers) { this.get('/widgets/:widget_id', function(request) { const w = _widgets.findBy('id', parseInt(request.params.widget_id)); if (w) { - return response({widget: w}); + return response({ widget: w, extras: { hello: 'world' }}); } else { return response(404); } diff --git a/test/javascripts/models/store-test.js.es6 b/test/javascripts/models/store-test.js.es6 index 232055bb285..e62d77dc4e0 100644 --- a/test/javascripts/models/store-test.js.es6 +++ b/test/javascripts/models/store-test.js.es6 @@ -49,14 +49,17 @@ QUnit.test('createRecord with a record as attributes returns that record from th QUnit.test('find', assert => { const store = createStore(); + return store.find('widget', 123).then(function(w) { assert.equal(w.get('name'), 'Trout Lure'); assert.equal(w.get('id'), 123); 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 store.find('widget', 123).then(function(w2) { assert.equal(w, w2); + assert.equal(w.get('extras.hello'), 'world', "extra attributes are set"); }); }); });
    {{i18n "groups.index.group_type"}} {{i18n "groups.membership"}}
    {{group.user_count}} - {{#group-membership-button model=group - showMembershipStatus=true - showLogin='showLogin'}} - - {{d-button icon="ban" - label=(if group.automatic 'groups.automatic_group' 'groups.close_group') - disabled=true}} - {{/group-membership-button}} + + {{#if group.public_admission}} + {{i18n 'groups.index.public'}} + {{else if group.isPrivate}} + {{d-icon "eye-slash"}} + {{i18n 'groups.index.private'}} + {{else}} + {{#if group.automatic}} + {{i18n 'groups.index.automatic'}} + {{else}} + {{i18n 'groups.index.closed'}} + {{/if}} + {{/if}} - {{#if group.is_group_user}} - {{d-icon "user" title="groups.is_group_user"}} - {{/if}} + + + {{#if group.is_group_owner}} + {{i18n "groups.index.is_group_owner"}} + {{else if group.is_group_user}} + {{i18n "groups.index.is_group_user"}} + {{/if}} + - {{#if group.is_group_owner}} - {{d-icon "shield" title="groups.is_group_owner"}} - {{/if}} + {{group-membership-button model=group showLogin='showLogin'}}