From 13e74151a9d8830c6d826edb6ec37b2ff2c23fe9 Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Wed, 31 Jul 2019 16:46:58 +0300 Subject: [PATCH] FEATURE: list category moderators on the about page (#7916) https://meta.discourse.org/t/category-group-review-moderation/116478?u=osama --- .../javascripts/discourse/routes/about.js.es6 | 8 ++++ .../javascripts/discourse/templates/about.hbs | 13 ++++++ app/assets/stylesheets/common/base/faqs.scss | 7 +++- app/controllers/about_controller.rb | 2 +- app/models/about.rb | 34 +++++++++++++++ app/serializers/about_serializer.rb | 7 ++++ config/locales/client.en.yml | 1 + spec/models/about_spec.rb | 42 +++++++++++++++++++ 8 files changed, 112 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/routes/about.js.es6 b/app/assets/javascripts/discourse/routes/about.js.es6 index 019a23380dd..3b19492a6b1 100644 --- a/app/assets/javascripts/discourse/routes/about.js.es6 +++ b/app/assets/javascripts/discourse/routes/about.js.es6 @@ -16,6 +16,14 @@ export default Discourse.Route.extend({ }); result.about.admins = activeAdmins; result.about.moderators = activeModerators; + + const { category_moderators: categoryModerators } = result.about; + if (categoryModerators && categoryModerators.length) { + categoryModerators.forEach((obj, index) => { + const category = this.site.categories.findBy("id", obj.category_id); + result.about.category_moderators[index].category = category; + }); + } return result.about; }); }, diff --git a/app/assets/javascripts/discourse/templates/about.hbs b/app/assets/javascripts/discourse/templates/about.hbs index 495a7a3db9b..5abd436bcad 100644 --- a/app/assets/javascripts/discourse/templates/about.hbs +++ b/app/assets/javascripts/discourse/templates/about.hbs @@ -57,6 +57,19 @@ connectorTagName='section' args=(hash model=model)}} + {{#if model.category_moderators.length}} + {{#each model.category_moderators as |cm|}} +
+

{{category-link cm.category}}{{i18n "about.moderators"}}

+
+ {{#each cm.moderators as |m|}} + {{user-info user=m}} + {{/each}} +
+
+
+ {{/each}} + {{/if}}

{{d-icon "far-chart-bar"}} {{i18n 'about.stats'}}

diff --git a/app/assets/stylesheets/common/base/faqs.scss b/app/assets/stylesheets/common/base/faqs.scss index acda62dcbb6..9102cc69ba3 100644 --- a/app/assets/stylesheets/common/base/faqs.scss +++ b/app/assets/stylesheets/common/base/faqs.scss @@ -4,9 +4,14 @@ max-width: 700px; .about-page & { max-width: unset; - section:not(.admins):not(.moderators) { + section:not(.admins):not(.moderators):not(.category-moderators) { max-width: 700px; } + .about.category-moderators { + .badge-wrapper.bullet .badge-category { + color: $primary; + } + } } .mobile-view & { font-size: $font-0; diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index 03336bdc004..8856a377b4f 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -11,7 +11,7 @@ class AboutController < ApplicationController def index return redirect_to path('/login') if SiteSetting.login_required? && current_user.nil? - @about = About.new + @about = About.new(current_user) @title = "#{I18n.t("js.about.simple_title")} - #{SiteSetting.title}" respond_to do |format| format.html do diff --git a/app/models/about.rb b/app/models/about.rb index 783c6829b4d..b24a62cd0f6 100644 --- a/app/models/about.rb +++ b/app/models/about.rb @@ -1,6 +1,16 @@ # frozen_string_literal: true class About + class CategoryMods + include ActiveModel::Serialization + attr_reader :category_id, :moderators + + def initialize(category_id, moderators) + @category_id = category_id + @moderators = moderators + end + end + include ActiveModel::Serialization include StatsCacheable @@ -15,6 +25,10 @@ class About About.new.stats end + def initialize(user = nil) + @user = user + end + def version Discourse::VERSION::STRING end @@ -66,4 +80,24 @@ class About } end + def category_moderators + category_ids = Guardian.new(@user).allowed_category_ids + return [] if category_ids.blank? + results = DB.query(<<~SQL, category_ids: category_ids) + SELECT c.id category_id, array_agg(gu.user_id) user_ids + FROM categories c + JOIN group_users gu + ON gu.group_id = reviewable_by_group_id + WHERE c.id IN (:category_ids) + GROUP BY c.id + SQL + moderators = {} + User.where(id: results.map(&:user_ids).flatten).each do |user| + moderators[user.id] = user + end + moderators + results.map do |row| + CategoryMods.new(row.category_id, row.user_ids.map { |id| moderators[id] }) + end + end end diff --git a/app/serializers/about_serializer.rb b/app/serializers/about_serializer.rb index af78559b6a3..308f990fd6c 100644 --- a/app/serializers/about_serializer.rb +++ b/app/serializers/about_serializer.rb @@ -6,8 +6,15 @@ class AboutSerializer < ApplicationSerializer attributes :title, :last_seen_at end + class AboutCategoryModsSerializer < ApplicationSerializer + attributes :category_id + + has_many :moderators, serializer: UserAboutSerializer, embed: :objects + end + has_many :moderators, serializer: UserAboutSerializer, embed: :objects has_many :admins, serializer: UserAboutSerializer, embed: :objects + has_many :category_moderators, serializer: AboutCategoryModsSerializer, embed: :objects attributes :stats, :description, diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4bafe972516..3f8b850c01d 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -274,6 +274,7 @@ en: stats: "Site Statistics" our_admins: "Our Admins" our_moderators: "Our Moderators" + moderators: "Moderators" stat: all_time: "All Time" last_7_days: "Last 7" diff --git a/spec/models/about_spec.rb b/spec/models/about_spec.rb index ea93c798941..c4ab5aba806 100644 --- a/spec/models/about_spec.rb +++ b/spec/models/about_spec.rb @@ -8,4 +8,46 @@ describe About do include_examples 'stats cachable' end + describe "#category_moderators" do + let(:user) { Fabricate(:user) } + let(:public_cat_moderator) { Fabricate(:user) } + let(:private_cat_moderator) { Fabricate(:user) } + let(:common_moderator) { Fabricate(:user) } + + let(:public_group) do + group = Fabricate(:public_group) + group.add(public_cat_moderator) + group.add(common_moderator) + group + end + + let(:private_group) do + group = Fabricate(:group) + group.add(private_cat_moderator) + group.add(common_moderator) + group + end + + let!(:public_cat) { Fabricate(:category, reviewable_by_group: public_group) } + let!(:private_cat) { Fabricate(:private_category, group: private_group, reviewable_by_group: private_group) } + + it "lists moderators of the category that the current user can see" do + results = About.new(private_cat_moderator).category_moderators + expect(results.map(&:category_id)).to contain_exactly(public_cat.id, private_cat.id) + expect(results.map(&:moderators).flatten.map(&:id).uniq).to contain_exactly( + public_cat_moderator.id, + common_moderator.id, + private_cat_moderator.id + ) + + [public_cat_moderator, user, nil].each do |u| + results = About.new(u).category_moderators + expect(results.map(&:category_id)).to contain_exactly(public_cat.id) + expect(results.map(&:moderators).flatten.map(&:id)).to contain_exactly( + public_cat_moderator.id, + common_moderator.id + ) + end + end + end end