diff --git a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 index 3d6ff774db5..a73a6839fac 100644 --- a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 @@ -31,6 +31,18 @@ function isUnseen(topic) { return !topic.is_seen; } +function hasMutedTags(topicTagIds, mutedTagIds) { + if (!mutedTagIds || !topicTagIds) { + return false; + } + return ( + (Discourse.SiteSettings.remove_muted_tags_from_latest === "always" && + topicTagIds.any(tagId => mutedTagIds.includes(tagId))) || + (Discourse.SiteSettings.remove_muted_tags_from_latest === "only_muted" && + topicTagIds.every(tagId => mutedTagIds.includes(tagId))) + ); +} + const TopicTrackingState = EmberObject.extend({ messageCount: 0, @@ -60,6 +72,13 @@ const TopicTrackingState = EmberObject.extend({ } } + if (["new_topic", "latest"].includes(data.message_type)) { + const mutedTagIds = User.currentProp("muted_tag_ids"); + if (hasMutedTags(data.payload.topic_tag_ids, mutedTagIds)) { + return; + } + } + // fill parent_category_id we need it for counting new/unread if (data.payload && data.payload.category_id) { var category = Category.findById(data.payload.category_id); diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb index 3118214f8c0..9a5fb66a3b5 100644 --- a/app/models/topic_tracking_state.rb +++ b/app/models/topic_tracking_state.rb @@ -33,7 +33,8 @@ class TopicTrackingState created_at: topic.created_at, topic_id: topic.id, category_id: topic.category_id, - archetype: topic.archetype + archetype: topic.archetype, + topic_tag_ids: topic.tags.pluck(:id) } } @@ -52,7 +53,8 @@ class TopicTrackingState payload: { bumped_at: topic.bumped_at, category_id: topic.category_id, - archetype: topic.archetype + archetype: topic.archetype, + topic_tag_ids: topic.tags.pluck(:id) } } @@ -182,7 +184,8 @@ class TopicTrackingState skip_order: true, staff: user.staff?, admin: user.admin?, - user: user + user: user, + muted_tag_ids: muted_tag_ids(user) ) sql << "\nUNION ALL\n\n" @@ -194,7 +197,8 @@ class TopicTrackingState staff: user.staff?, filter_old_unread: true, admin: user.admin?, - user: user + user: user, + muted_tag_ids: muted_tag_ids(user) ) DB.query( @@ -205,6 +209,10 @@ class TopicTrackingState ) end + def self.muted_tag_ids(user) + TagUser.lookup(user, :muted).pluck(:tag_id) + end + def self.report_raw_sql(opts = nil) opts ||= {} @@ -268,6 +276,19 @@ class TopicTrackingState "(topics.visible #{append}) AND" end + tags_filter = + if opts[:muted_tag_ids].present? && SiteSetting.remove_muted_tags_from_latest == 'always' + <<~SQL + NOT ((select array_agg(tag_id) from topic_tags where topic_tags.topic_id = topics.id) && ARRAY[#{opts[:muted_tag_ids].join(',')}]) AND + SQL + elsif opts[:muted_tag_ids].present? && SiteSetting.remove_muted_tags_from_latest == 'only_muted' + <<~SQL + NOT ((select array_agg(tag_id) from topic_tags where topic_tags.topic_id = topics.id) <@ ARRAY[#{opts[:muted_tag_ids].join(',')}]) AND + SQL + else + "" + end + sql = +<<~SQL SELECT #{select} FROM topics @@ -282,6 +303,7 @@ class TopicTrackingState topics.archetype <> 'private_message' AND ((#{unread}) OR (#{new})) AND #{visibility_filter} + #{tags_filter} topics.deleted_at IS NULL AND #{category_filter} (category_users.id IS NULL OR diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index af6fe8c5722..9851baab7d7 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -27,6 +27,7 @@ class CurrentUserSerializer < BasicUserSerializer :redirected_to_top, :custom_fields, :muted_category_ids, + :muted_tag_ids, :dismissed_banner_key, :is_anonymous, :reviewable_count, @@ -169,6 +170,10 @@ class CurrentUserSerializer < BasicUserSerializer CategoryUser.lookup(object, :muted).pluck(:category_id) end + def muted_tag_ids + TagUser.lookup(object, :muted).pluck(:tag_id) + end + def ignored_users IgnoredUser.where(user: object.id).joins(:ignored_user).pluck(:username) end diff --git a/spec/models/topic_tracking_state_spec.rb b/spec/models/topic_tracking_state_spec.rb index c0b9be4f705..5f67317ff10 100644 --- a/spec/models/topic_tracking_state_spec.rb +++ b/spec/models/topic_tracking_state_spec.rb @@ -353,6 +353,77 @@ describe TopicTrackingState do expect(report.length).to eq(1) end + context 'muted tags' do + it "remove_muted_tags_from_latest is set to always" do + SiteSetting.remove_muted_tags_from_latest = 'always' + user = Fabricate(:user) + tag1 = Fabricate(:tag) + tag2 = Fabricate(:tag) + Fabricate(:topic_tag, tag: tag1, topic: topic) + Fabricate(:topic_tag, tag: tag2, topic: topic) + post + + report = TopicTrackingState.report(user) + expect(report.length).to eq(1) + + TagUser.create!(user_id: user.id, + notification_level: TagUser.notification_levels[:muted], + tag_id: tag1.id + ) + + report = TopicTrackingState.report(user) + expect(report.length).to eq(0) + end + + it "remove_muted_tags_from_latest is set to only_muted" do + SiteSetting.remove_muted_tags_from_latest = 'only_muted' + user = Fabricate(:user) + tag1 = Fabricate(:tag) + tag2 = Fabricate(:tag) + Fabricate(:topic_tag, tag: tag1, topic: topic) + Fabricate(:topic_tag, tag: tag2, topic: topic) + post + + report = TopicTrackingState.report(user) + expect(report.length).to eq(1) + + TagUser.create!(user_id: user.id, + notification_level: TagUser.notification_levels[:muted], + tag_id: tag1.id + ) + + report = TopicTrackingState.report(user) + expect(report.length).to eq(1) + + TagUser.create!(user_id: user.id, + notification_level: TagUser.notification_levels[:muted], + tag_id: tag2.id + ) + + report = TopicTrackingState.report(user) + expect(report.length).to eq(0) + end + + it "remove_muted_tags_from_latest is set to never" do + SiteSetting.remove_muted_tags_from_latest = 'never' + user = Fabricate(:user) + tag1 = Fabricate(:tag) + Fabricate(:topic_tag, tag: tag1, topic: topic) + post + + report = TopicTrackingState.report(user) + expect(report.length).to eq(1) + + TagUser.create!(user_id: user.id, + notification_level: TagUser.notification_levels[:muted], + tag_id: tag1.id + ) + + report = TopicTrackingState.report(user) + expect(report.length).to eq(1) + end + end + it "correctly handles seen categories" do user = Fabricate(:user) post diff --git a/spec/serializers/current_user_serializer_spec.rb b/spec/serializers/current_user_serializer_spec.rb index 30c6a404975..9a06bec7f74 100644 --- a/spec/serializers/current_user_serializer_spec.rb +++ b/spec/serializers/current_user_serializer_spec.rb @@ -68,6 +68,25 @@ RSpec.describe CurrentUserSerializer do end end + context "#muted_tag_ids" do + fab!(:user) { Fabricate(:user) } + fab!(:tag) { Fabricate(:tag) } + let!(:tag_user) do + TagUser.create!(user_id: user.id, + notification_level: TagUser.notification_levels[:muted], + tag_id: tag.id + ) + end + let :serializer do + CurrentUserSerializer.new(user, scope: Guardian.new, root: false) + end + + it 'include muted tag ids' do + payload = serializer.as_json + expect(payload[:muted_tag_ids]).to eq([tag.id]) + end + end + context "#second_factor_enabled" do fab!(:user) { Fabricate(:user) } let :serializer do