From 74ab4f3bffe3445e75801b66df3e095471053482 Mon Sep 17 00:00:00 2001 From: jbrw Date: Tue, 28 Jul 2020 17:15:04 -0400 Subject: [PATCH] FEATURE - group modetators visual indicator (#10310) --- .../discourse/app/lib/transform-post.js | 1 + .../javascripts/discourse/app/widgets/post.js | 3 + .../discourse/app/widgets/poster-name.js | 5 +- app/serializers/post_serializer.rb | 15 + lib/guardian.rb | 9 + lib/guardian/topic_guardian.rb | 10 +- lib/topic_view.rb | 13 + spec/serializers/post_serializer_spec.rb | 21 ++ test/javascripts/acceptance/topic-test.js | 7 + test/javascripts/fixtures/topic.js | 338 ++++++++++++++++++ test/javascripts/helpers/create-pretender.js | 9 +- 11 files changed, 414 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/discourse/app/lib/transform-post.js b/app/assets/javascripts/discourse/app/lib/transform-post.js index e2dd44b967..efc1ac2882 100644 --- a/app/assets/javascripts/discourse/app/lib/transform-post.js +++ b/app/assets/javascripts/discourse/app/lib/transform-post.js @@ -44,6 +44,7 @@ export function transformBasicPost(post) { staff: post.staff, admin: post.admin, moderator: post.moderator, + groupModerator: post.group_moderator, new_user: post.trust_level === 0, name: post.name, user_title: post.user_title, diff --git a/app/assets/javascripts/discourse/app/widgets/post.js b/app/assets/javascripts/discourse/app/widgets/post.js index 679f0720c5..ae87f5cbc1 100644 --- a/app/assets/javascripts/discourse/app/widgets/post.js +++ b/app/assets/javascripts/discourse/app/widgets/post.js @@ -675,6 +675,9 @@ export default createWidget("post", { if (attrs.topicOwner) { classNames.push("topic-owner"); } + if (attrs.groupModerator) { + classNames.push("category-moderator"); + } if (attrs.hidden) { classNames.push("post-hidden"); } diff --git a/app/assets/javascripts/discourse/app/widgets/poster-name.js b/app/assets/javascripts/discourse/app/widgets/poster-name.js index 04457539ce..ebc12a8c85 100644 --- a/app/assets/javascripts/discourse/app/widgets/poster-name.js +++ b/app/assets/javascripts/discourse/app/widgets/poster-name.js @@ -44,7 +44,7 @@ export default createWidget("poster-name", { // TODO: Allow extensibility posterGlyph(attrs) { - if (attrs.moderator) { + if (attrs.moderator || attrs.groupModerator) { return iconNode("shield-alt", { title: I18n.t("user.moderator_tooltip") }); @@ -83,6 +83,9 @@ export default createWidget("poster-name", { if (attrs.moderator) { classNames.push("moderator"); } + if (attrs.groupModerator) { + classNames.push("category-moderator"); + } if (attrs.new_user) { classNames.push("new-user"); } diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb index 56ac4fe766..12ca90eddc 100644 --- a/app/serializers/post_serializer.rb +++ b/app/serializers/post_serializer.rb @@ -60,6 +60,7 @@ class PostSerializer < BasicPostSerializer :moderator?, :admin?, :staff?, + :group_moderator, :user_id, :draft_sequence, :hidden, @@ -140,6 +141,20 @@ class PostSerializer < BasicPostSerializer !!(object&.user&.staff?) end + def group_moderator + !!@group_moderator + end + + def include_group_moderator? + @group_moderator ||= begin + if @topic_view + @topic_view.category_group_moderator_user_ids.include?(object.user_id) + else + object&.user&.guardian&.is_category_group_moderator?(object&.topic&.category) + end + end + end + def yours scope.user == object.user end diff --git a/lib/guardian.rb b/lib/guardian.rb index 9e31492c77..be5378e26d 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -93,6 +93,15 @@ class Guardian @user.moderator? end + def is_category_group_moderator?(category) + @is_category_group_moderator ||= begin + SiteSetting.enable_category_group_moderation? && + category.present? && + category.reviewable_by_group_id.present? && + GroupUser.where(group_id: category.reviewable_by_group_id, user_id: @user.id).exists? + end + end + def is_silenced? @user.silenced? end diff --git a/lib/guardian/topic_guardian.rb b/lib/guardian/topic_guardian.rb index 5a0f9b54df..60a7c37102 100644 --- a/lib/guardian/topic_guardian.rb +++ b/lib/guardian/topic_guardian.rb @@ -17,10 +17,7 @@ module TopicGuardian return false if anonymous? || topic.nil? return true if is_staff? - SiteSetting.enable_category_group_moderation? && - topic.category.present? && - topic.category.reviewable_by_group_id.present? && - GroupUser.where(group_id: topic.category.reviewable_by_group_id, user_id: user.id).exists? + is_category_group_moderator?(topic.category) end def can_create_shared_draft? @@ -209,10 +206,7 @@ module TopicGuardian return true if is_staff? return true if @user.has_trust_level?(TrustLevel[4]) - SiteSetting.enable_category_group_moderation? && - topic.category.present? && - topic.category.reviewable_by_group_id.present? && - GroupUser.where(group_id: topic.category.reviewable_by_group_id, user_id: @user.id).exists? + is_category_group_moderator?(topic.category) end alias :can_archive_topic? :can_perform_action_available_to_group_moderators? alias :can_close_topic? :can_perform_action_available_to_group_moderators? diff --git a/lib/topic_view.rb b/lib/topic_view.rb index 0601a59f0c..0568e07a05 100644 --- a/lib/topic_view.rb +++ b/lib/topic_view.rb @@ -429,6 +429,19 @@ class TopicView @group_allowed_user_ids = Set.new(GroupUser.where(group_id: group_ids).pluck('distinct user_id')) end + def category_group_moderator_user_ids + @category_group_moderator_user_ids ||= begin + if SiteSetting.enable_category_group_moderation? && @topic.category.reviewable_by_group.present? + posts_user_ids = Set.new(@posts.map(&:user_id)) + Set.new( + @topic.category.reviewable_by_group.group_users.where(user_id: posts_user_ids).pluck('distinct user_id') + ) + else + Set.new + end + end + end + def all_post_actions @all_post_actions ||= PostAction.counts_for(@posts, @user) end diff --git a/spec/serializers/post_serializer_spec.rb b/spec/serializers/post_serializer_spec.rb index 06ca8d4716..b62c32ee4c 100644 --- a/spec/serializers/post_serializer_spec.rb +++ b/spec/serializers/post_serializer_spec.rb @@ -258,6 +258,27 @@ describe PostSerializer do end end + context "posts when group moderation is enabled" do + fab!(:topic) { Fabricate(:topic) } + fab!(:group_user) { Fabricate(:group_user) } + fab!(:post) { Fabricate(:post, topic: topic) } + + before do + SiteSetting.enable_category_group_moderation = true + topic.category.update!(reviewable_by_group_id: group_user.group.id) + end + + it "does nothing for regular users" do + expect(serialized_post_for_user(nil)[:group_moderator]).to eq(nil) + end + + it "returns a group_moderator attribute for category group moderators" do + post.update!(user: group_user.user) + expect(serialized_post_for_user(nil)[:group_moderator]).to eq(true) + end + + end + def serialized_post(u) s = PostSerializer.new(post, scope: Guardian.new(u), root: false) s.add_raw = true diff --git a/test/javascripts/acceptance/topic-test.js b/test/javascripts/acceptance/topic-test.js index 4e4cb0866f..27c5e3b5a8 100644 --- a/test/javascripts/acceptance/topic-test.js +++ b/test/javascripts/acceptance/topic-test.js @@ -228,6 +228,13 @@ QUnit.skip("Deleting a topic", async assert => { assert.ok(exists(".widget-button.recover"), "it shows the recover button"); }); +QUnit.test("Group category moderator posts", async assert => { + await visit("/t/topic-for-group-moderators/2480"); + + assert.ok(exists(".category-moderator"), "it has a class applied"); + assert.ok(exists(".d-icon-shield-alt"), "it shows an icon"); +}); + acceptance("Topic featured links", { loggedIn: true, settings: { diff --git a/test/javascripts/fixtures/topic.js b/test/javascripts/fixtures/topic.js index e4917aab24..6b91b712fa 100644 --- a/test/javascripts/fixtures/topic.js +++ b/test/javascripts/fixtures/topic.js @@ -5075,5 +5075,343 @@ export default { message_bus_last_id: 7, participant_count: 2, pm_with_non_human_user: false + }, + "/t/2480/1.json": { + "post_stream": { + "posts": [ + { + "id": 41, + "name": "", + "username": "group_moderator", + "avatar_template": "/images/avatar.png", + "created_at": "2020-07-24T17:48:55.419Z", + "cooked": "

Here is my new topic. I am a group category moderator!

", + "post_number": 1, + "post_type": 1, + "updated_at": "2020-07-24T17:48:55.419Z", + "reply_count": 0, + "reply_to_post_number": null, + "quote_count": 0, + "incoming_link_count": 0, + "reads": 2, + "readers_count": 1, + "score": 0, + "yours": true, + "topic_id": 2480, + "topic_slug": "a-topic-with-group-category-moderators", + "display_username": "", + "primary_group_name": "group_moderators", + "primary_group_flair_url": "cheese", + "primary_group_flair_bg_color": "ff0", + "primary_group_flair_color": "", + "version": 1, + "can_edit": true, + "can_delete": false, + "can_recover": false, + "can_wiki": false, + "read": true, + "user_title": "a title", + "title_is_group": false, + "bookmarked": false, + "actions_summary": [ + { + "id": 3, + "can_act": true + }, + { + "id": 4, + "can_act": true + }, + { + "id": 8, + "can_act": true + }, + { + "id": 7, + "can_act": true + } + ], + "moderator": false, + "admin": false, + "staff": false, + "group_moderator": true, + "user_id": 3, + "hidden": false, + "trust_level": 1, + "deleted_at": null, + "user_deleted": false, + "edit_reason": null, + "can_view_edit_history": true, + "wiki": false, + "reviewable_id": 0, + "reviewable_score_count": 0, + "reviewable_score_pending_count": 0 + }, + { + "id": 42, + "name": "", + "username": "normal_user", + "avatar_template": "/images/avatar.png", + "created_at": "2020-07-24T17:50:01.263Z", + "cooked": "

A fascinating topic worthy of discussion.

", + "post_number": 2, + "post_type": 1, + "updated_at": "2020-07-24T17:50:01.263Z", + "reply_count": 0, + "reply_to_post_number": null, + "quote_count": 0, + "incoming_link_count": 0, + "reads": 2, + "readers_count": 1, + "score": 0, + "yours": false, + "topic_id": 2480, + "topic_slug": "a-topic-with-group-category-moderators", + "display_username": "", + "primary_group_name": null, + "primary_group_flair_url": null, + "primary_group_flair_bg_color": null, + "primary_group_flair_color": null, + "version": 1, + "can_edit": false, + "can_delete": false, + "can_recover": false, + "can_wiki": false, + "read": true, + "user_title": null, + "bookmarked": false, + "actions_summary": [ + { + "id": 2, + "can_act": true + }, + { + "id": 3, + "can_act": true + }, + { + "id": 4, + "can_act": true + }, + { + "id": 8, + "can_act": true + }, + { + "id": 6, + "can_act": true + }, + { + "id": 7, + "can_act": true + } + ], + "moderator": false, + "admin": false, + "staff": false, + "user_id": 2, + "hidden": false, + "trust_level": 1, + "deleted_at": null, + "user_deleted": false, + "edit_reason": null, + "can_view_edit_history": true, + "wiki": false, + "reviewable_id": 0, + "reviewable_score_count": 0, + "reviewable_score_pending_count": 0 + }, + { + "id": 43, + "name": "", + "username": "group_moderator", + "avatar_template": "/images/avatar.png", + "created_at": "2020-07-24T17:50:17.274Z", + "cooked": "

Thank you for your reply!

", + "post_number": 3, + "post_type": 1, + "updated_at": "2020-07-24T17:50:17.274Z", + "reply_count": 0, + "reply_to_post_number": null, + "quote_count": 0, + "incoming_link_count": 0, + "reads": 2, + "readers_count": 1, + "score": 0, + "yours": true, + "topic_id": 2480, + "topic_slug": "a-topic-with-group-category-moderators", + "display_username": "", + "primary_group_name": "group_moderators", + "primary_group_flair_url": "cheese", + "primary_group_flair_bg_color": "ff0", + "primary_group_flair_color": "", + "version": 1, + "can_edit": true, + "can_delete": true, + "can_recover": false, + "can_wiki": false, + "read": true, + "user_title": "a title", + "title_is_group": false, + "bookmarked": false, + "actions_summary": [ + { + "id": 3, + "can_act": true + }, + { + "id": 4, + "can_act": true + }, + { + "id": 8, + "can_act": true + }, + { + "id": 7, + "can_act": true + } + ], + "moderator": false, + "admin": false, + "staff": false, + "group_moderator": true, + "user_id": 3, + "hidden": false, + "trust_level": 1, + "deleted_at": null, + "user_deleted": false, + "edit_reason": null, + "can_view_edit_history": true, + "wiki": false, + "reviewable_id": 0, + "reviewable_score_count": 0, + "reviewable_score_pending_count": 0 + } + ], + "stream": [ + 41, + 42, + 43 + ] + }, + "timeline_lookup": [ + [ + 1, + 0 + ] + ], + "id": 2480, + "title": "A Topic with Group Category Moderators", + "fancy_title": "A Topic with Group Category Moderators", + "posts_count": 3, + "created_at": "2020-07-24T17:48:54.986Z", + "views": 2, + "reply_count": 0, + "like_count": 0, + "last_posted_at": "2020-07-24T17:50:17.274Z", + "visible": true, + "closed": false, + "archived": false, + "has_summary": false, + "archetype": "regular", + "slug": "a-topic-with-group-category-moderators", + "category_id": 5, + "word_count": 22, + "deleted_at": null, + "user_id": 3, + "featured_link": null, + "pinned_globally": false, + "pinned_at": null, + "pinned_until": null, + "image_url": null, + "draft": null, + "draft_key": "topic_2480", + "draft_sequence": 1, + "posted": true, + "unpinned": null, + "pinned": false, + "current_post_number": 3, + "highest_post_number": 3, + "last_read_post_number": 3, + "last_read_post_id": 43, + "deleted_by": null, + "actions_summary": [ + { + "id": 4, + "count": 0, + "hidden": false, + "can_act": true + }, + { + "id": 8, + "count": 0, + "hidden": false, + "can_act": true + }, + { + "id": 7, + "count": 0, + "hidden": false, + "can_act": true + } + ], + "chunk_size": 20, + "bookmarked": false, + "topic_timer": null, + "private_topic_timer": null, + "message_bus_last_id": 4, + "participant_count": 2, + "show_read_indicator": false, + "thumbnails": null, + "details": { + "notification_level": 3, + "notifications_reason_id": 1, + "can_edit": true, + "can_create_post": true, + "can_reply_as_new_topic": true, + "can_flag_topic": true, + "can_review_topic": true, + "can_close_topic": true, + "can_archive_topic": true, + "can_edit_staff_notes": true, + "participants": [ + { + "id": 3, + "username": "group_moderator", + "name": "", + "avatar_template": "/images/avatar.png", + "post_count": 2, + "primary_group_name": "group_moderators", + "primary_group_flair_url": "cheese", + "primary_group_flair_color": "", + "primary_group_flair_bg_color": "ff0" + }, + { + "id": 2, + "username": "normal_user", + "name": "", + "avatar_template": "/images/avatar.png", + "post_count": 1, + "primary_group_name": null, + "primary_group_flair_url": null, + "primary_group_flair_color": null, + "primary_group_flair_bg_color": null + } + ], + "created_by": { + "id": 3, + "username": "group_moderator", + "name": "", + "avatar_template": "/images/avatar.png" + }, + "last_poster": { + "id": 3, + "username": "group_moderator", + "name": "", + "avatar_template": "/images/avatar.png" + } + } } }; diff --git a/test/javascripts/helpers/create-pretender.js b/test/javascripts/helpers/create-pretender.js index 682c27cbe7..371e651775 100644 --- a/test/javascripts/helpers/create-pretender.js +++ b/test/javascripts/helpers/create-pretender.js @@ -255,14 +255,7 @@ export function applyDefaultHandlers(pretender) { pretender.get("/t/12.json", () => response(fixturesByUrl["/t/12/1.json"])); pretender.put("/t/1234/re-pin", success); - pretender.get("/t/2480.json", () => { - const json = fixturesByUrl["/t/34/1.json"]; - json.details.can_archive_topic = true; - json.details.can_close_topic = true; - json.details.can_edit_staff_notes = true; - - return response(json); - }); + pretender.get("/t/2480.json", () => response(fixturesByUrl["/t/2480/1.json"])); pretender.get("/t/id_for/:slug", () => { return response({