diff --git a/app/assets/javascripts/discourse/app/controllers/group-permissions.js b/app/assets/javascripts/discourse/app/controllers/group-permissions.js new file mode 100644 index 00000000000..7ae8f5a1e2e --- /dev/null +++ b/app/assets/javascripts/discourse/app/controllers/group-permissions.js @@ -0,0 +1,3 @@ +import Controller from "@ember/controller"; + +export default Controller.extend(); diff --git a/app/assets/javascripts/discourse/app/controllers/group.js b/app/assets/javascripts/discourse/app/controllers/group.js index 9b2f0631286..e4b62a0b953 100644 --- a/app/assets/javascripts/discourse/app/controllers/group.js +++ b/app/assets/javascripts/discourse/app/controllers/group.js @@ -79,6 +79,13 @@ export default Controller.extend({ ); } + defaultTabs.push( + Tab.create({ + name: "permissions", + i18nKey: "permissions.title" + }) + ); + return defaultTabs; }, diff --git a/app/assets/javascripts/discourse/app/models/permission-type.js b/app/assets/javascripts/discourse/app/models/permission-type.js index 698c2db2286..305be2de57e 100644 --- a/app/assets/javascripts/discourse/app/models/permission-type.js +++ b/app/assets/javascripts/discourse/app/models/permission-type.js @@ -2,28 +2,24 @@ import I18n from "I18n"; import discourseComputed from "discourse-common/utils/decorators"; import EmberObject from "@ember/object"; +export function buildPermissionDescription(id) { + return I18n.t("permission_types." + PermissionType.DESCRIPTION_KEYS[id]); +} + const PermissionType = EmberObject.extend({ @discourseComputed("id") description(id) { - var key = ""; - - switch (id) { - case 1: - key = "full"; - break; - case 2: - key = "create_post"; - break; - case 3: - key = "readonly"; - break; - } - return I18n.t("permission_types." + key); + return buildPermissionDescription(id); } }); PermissionType.FULL = 1; PermissionType.CREATE_POST = 2; PermissionType.READONLY = 3; +PermissionType.DESCRIPTION_KEYS = { + 1: "full", + 2: "create_post", + 3: "readonly" +}; export default PermissionType; diff --git a/app/assets/javascripts/discourse/app/routes/app-route-map.js b/app/assets/javascripts/discourse/app/routes/app-route-map.js index 1e9df27af2c..bee1fd74dc9 100644 --- a/app/assets/javascripts/discourse/app/routes/app-route-map.js +++ b/app/assets/javascripts/discourse/app/routes/app-route-map.js @@ -103,6 +103,8 @@ export default function() { this.route("inbox"); this.route("archive"); }); + + this.route("permissions"); }); // User routes diff --git a/app/assets/javascripts/discourse/app/routes/group-permissions.js b/app/assets/javascripts/discourse/app/routes/group-permissions.js new file mode 100644 index 00000000000..141721f6c60 --- /dev/null +++ b/app/assets/javascripts/discourse/app/routes/group-permissions.js @@ -0,0 +1,34 @@ +import I18n from "I18n"; +import DiscourseRoute from "discourse/routes/discourse"; +import { ajax } from "discourse/lib/ajax"; +import { buildPermissionDescription } from "discourse/models/permission-type"; + +export default DiscourseRoute.extend({ + showFooter: true, + + titleToken() { + return I18n.t("groups.permissions.title"); + }, + + model() { + let group = this.modelFor("group"); + + return ajax(`/g/${group.name}/permissions`) + .then(permissions => { + permissions.forEach(permission => { + permission.description = buildPermissionDescription( + permission.permission_type + ); + }); + return { permissions }; + }) + .catch(() => { + this.transitionTo("group.members", group); + }); + }, + + setupController(controller, model) { + this.controllerFor("group-permissions").setProperties({ model }); + this.controllerFor("group").set("showing", "permissions"); + } +}); diff --git a/app/assets/javascripts/discourse/app/templates/group/permissions.hbs b/app/assets/javascripts/discourse/app/templates/group/permissions.hbs new file mode 100644 index 00000000000..f4f9d82b5ad --- /dev/null +++ b/app/assets/javascripts/discourse/app/templates/group/permissions.hbs @@ -0,0 +1,20 @@ +
+ {{#if model.permissions}} + + + + {{#each model.permissions as |permission|}} + + + + + {{/each}} + +
{{category-link permission.category}}{{permission.description}}
+ {{else}} + {{i18n "groups.permissions.none"}} + {{/if}} +
+ diff --git a/app/assets/stylesheets/common/base/group.scss b/app/assets/stylesheets/common/base/group.scss index d77e1491fd2..c53d771c191 100644 --- a/app/assets/stylesheets/common/base/group.scss +++ b/app/assets/stylesheets/common/base/group.scss @@ -189,3 +189,21 @@ table.group-members { } } } + +label.group-category-permissions-desc { + font-size: 1.15em; + margin-bottom: 1em; +} + +table.group-category-permissions { + width: 100%; + + tr { + line-height: 3em; + width: 100%; + + .category-name { + font-size: 1.25em; + } + } +} diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 47b1c16dea2..53ca79530b2 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -546,6 +546,12 @@ class GroupsController < ApplicationController render_serialized(groups, BasicGroupSerializer) end + def permissions + group = find_group(:id) + category_groups = group.category_groups.select { |category_group| guardian.can_see_category?(category_group.category) } + render_serialized(category_groups.sort_by { |category_group| category_group.category.name }, CategoryGroupSerializer) + end + private def group_params(automatic: false) diff --git a/app/serializers/category_group_serializer.rb b/app/serializers/category_group_serializer.rb new file mode 100644 index 00000000000..fc449828cd9 --- /dev/null +++ b/app/serializers/category_group_serializer.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class CategoryGroupSerializer < ApplicationSerializer + has_one :category, serializer: CategorySerializer, embed: :objects + has_one :group, serializer: BasicGroupSerializer, embed: :objects + + attributes :permission_type +end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 75d77f0e18f..9a0a16a1332 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -682,6 +682,10 @@ en: details: "Details" from: "From" to: "To" + permissions: + title: "Permissions" + none: "There are no categories associated with this group." + description: "Members of this group can access these categories" public_admission: "Allow users to join the group freely (Requires publicly visible group)" public_exit: "Allow users to leave the group freely" empty: diff --git a/config/routes.rb b/config/routes.rb index c124e0b7b3d..50083ce79c3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -585,6 +585,7 @@ Discourse::Application.routes.draw do get path => 'groups#show' end + get "permissions" => "groups#permissions" put "members" => "groups#add_members" delete "members" => "groups#remove_member" post "request_membership" => "groups#request_membership" diff --git a/spec/requests/groups_controller_spec.rb b/spec/requests/groups_controller_spec.rb index dc9a9e4399e..c19e0f2464a 100644 --- a/spec/requests/groups_controller_spec.rb +++ b/spec/requests/groups_controller_spec.rb @@ -1747,4 +1747,55 @@ describe GroupsController do expect(response.parsed_body["available"]).to eq(true) end end + + describe "#permissions" do + before do + sign_in(other_user) + end + + it "ensures the group can be seen" do + group.update!(visibility_level: Group.visibility_levels[:owners]) + + get "/groups/#{group.name}/permissions.json" + + expect(response.status).to eq(403) + end + + describe "with varying category permissions" do + fab!(:category) { Fabricate(:category) } + + before do + category.set_permissions("#{group.name}": :full) + category.save! + end + + it "does not return categories the user cannot see" do + get "/groups/#{group.name}/permissions.json" + expect(response.parsed_body).to eq([]) + end + + it "returns categories the user can see" do + group.add(other_user) + + get "/groups/#{group.name}/permissions.json" + expect(response.parsed_body.count).to eq(1) + expect(response.parsed_body.first["category"]["id"]).to eq(category.id) + end + end + + it "returns categories alphabetically" do + sign_in(user) + + ["Three", "New Cat", "Abc", "Hello"].each do |name| + category = Fabricate(:category, name: name) + category.set_permissions("#{group.name}": :full) + category.save! + end + + get "/groups/#{group.name}/permissions.json" + expect(response.parsed_body.map { |permission| permission["category"]["name"] }).to eq( + ["Abc", "Hello", "New Cat", "Three"] + ) + end + end end