FEATURE: new watched_precedence_over_muted setting (#22252)

New setting which allow admin to define behavior when topic is in watched category and muted topic and vice versa.

If watched_precedence_over_muted setting is true, that topic is still visible in list of topics and notification is created.

If watched_precedence_over_muted setting is false, that topic is not still visible in list of topics and notification is skipped as well.
This commit is contained in:
Krzysztof Kotlarek 2023-06-27 14:49:34 +10:00 committed by GitHub
parent 4f7f9ef87c
commit 9cf981f1f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 218 additions and 17 deletions

View File

@ -200,6 +200,10 @@ class PostAlerter
DiscourseEvent.trigger(:post_alerter_before_post, post, new_record, notified) DiscourseEvent.trigger(:post_alerter_before_post, post, new_record, notified)
if !SiteSetting.watched_precedence_over_muted
notified = notified + category_or_tag_muters(post.topic)
end
if new_record if new_record
if post.topic.private_message? if post.topic.private_message?
# private messages # private messages
@ -265,6 +269,18 @@ class PostAlerter
.pluck(:user_id) .pluck(:user_id)
end end
def category_or_tag_muters(topic)
User
.joins(
"LEFT JOIN category_users ON users.id = category_users.user_id AND category_users.category_id = #{topic.category_id.to_i} AND category_users.notification_level = #{CategoryUser.notification_levels[:muted].to_i}",
)
.joins("LEFT JOIN topic_tags ON topic_tags.topic_id = #{topic.id.to_i}")
.joins(
"LEFT JOIN tag_users ON users.id = tag_users.user_id AND tag_users.tag_id = topic_tags.tag_id AND tag_users.notification_level = #{TagUser.notification_levels[:muted].to_i}",
)
.where("category_users.id IS NOT NULL OR tag_users.id IS NOT NULL")
end
def notify_first_post_watchers(post, user_ids, notified = nil) def notify_first_post_watchers(post, user_ids, notified = nil)
return [] if user_ids.blank? return [] if user_ids.blank?
user_ids.uniq! user_ids.uniq!

View File

@ -2380,6 +2380,7 @@ en:
remove_muted_tags_from_latest: "Don't show topics tagged only with muted tags in the latest topic list." remove_muted_tags_from_latest: "Don't show topics tagged only with muted tags in the latest topic list."
force_lowercase_tags: "Force all new tags to be entirely lowercase." force_lowercase_tags: "Force all new tags to be entirely lowercase."
create_post_for_category_and_tag_changes: "Create a small action post when a topic's category or tags change" create_post_for_category_and_tag_changes: "Create a small action post when a topic's category or tags change"
watched_precedence_over_muted: "Notify me about topics in categories or tags Im watching that also belong to one I have muted"
company_name: "Company Name" company_name: "Company Name"
governing_law: "Governing Law" governing_law: "Governing Law"

View File

@ -2820,6 +2820,10 @@ tags:
type: enum type: enum
default: always default: always
enum: RemoveMutedTagsFromLatestSiteSetting enum: RemoveMutedTagsFromLatestSiteSetting
watched_precedence_over_muted:
client: true
default: false
force_lowercase_tags: force_lowercase_tags:
default: true default: true
client: true client: true

View File

@ -897,24 +897,42 @@ class TopicQuery
category_id = get_category_id(opts[:exclude]) if opts category_id = get_category_id(opts[:exclude]) if opts
if user if user
watched_tag_ids =
if SiteSetting.watched_precedence_over_muted
TagUser
.where(user: user)
.where("notification_level >= ?", TopicUser.notification_levels[:watching])
.pluck(:tag_id)
else
[]
end
# OR watched_topic_tags.id IS NOT NULL",
list = list =
list list.references("cu").joins(
.references("cu") "LEFT JOIN category_users ON category_users.category_id = topics.category_id AND category_users.user_id = #{user.id}",
.joins( )
"LEFT JOIN category_users ON category_users.category_id = topics.category_id AND category_users.user_id = #{user.id}", if watched_tag_ids.present?
list =
list.joins(
"LEFT JOIN topic_tags watched_topic_tags ON watched_topic_tags.topic_id = topics.id AND #{DB.sql_fragment("watched_topic_tags.tag_id IN (?)", watched_tag_ids)}",
) )
.where( end
"topics.category_id = :category_id
list =
list.where(
"topics.category_id = :category_id
OR OR
(COALESCE(category_users.notification_level, :default) <> :muted AND (topics.category_id IS NULL OR topics.category_id NOT IN(:indirectly_muted_category_ids))) (COALESCE(category_users.notification_level, :default) <> :muted AND (topics.category_id IS NULL OR topics.category_id NOT IN(:indirectly_muted_category_ids)))
#{watched_tag_ids.present? ? "OR watched_topic_tags.id IS NOT NULL" : ""}
OR tu.notification_level > :regular", OR tu.notification_level > :regular",
category_id: category_id || -1, category_id: category_id || -1,
default: CategoryUser.default_notification_level, default: CategoryUser.default_notification_level,
indirectly_muted_category_ids: indirectly_muted_category_ids:
CategoryUser.indirectly_muted_category_ids(user).presence || [-1], CategoryUser.indirectly_muted_category_ids(user).presence || [-1],
muted: CategoryUser.notification_levels[:muted], muted: CategoryUser.notification_levels[:muted],
regular: TopicUser.notification_levels[:regular], regular: TopicUser.notification_levels[:regular],
) )
elsif SiteSetting.mute_all_categories_by_default elsif SiteSetting.mute_all_categories_by_default
category_ids = [ category_ids = [
SiteSetting.default_categories_watching.split("|"), SiteSetting.default_categories_watching.split("|"),
@ -971,8 +989,18 @@ class TopicQuery
SELECT 1 SELECT 1
FROM topic_tags tt FROM topic_tags tt
WHERE tt.tag_id IN (:tag_ids) WHERE tt.tag_id IN (:tag_ids)
AND tt.topic_id = topics.id)", AND tt.topic_id = topics.id
#{user && !opts[:skip_categories] ? "AND COALESCE(category_users.notification_level, :regular) < :watching_or_infinite" : ""})",
tag_ids: muted_tag_ids, tag_ids: muted_tag_ids,
regular: CategoryUser.notification_levels[:regular],
watching_or_infinite:
(
if SiteSetting.watched_precedence_over_muted
CategoryUser.notification_levels[:watching]
else
99
end
),
) )
else else
list = list =
@ -981,10 +1009,20 @@ class TopicQuery
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM topic_tags tt FROM topic_tags tt
WHERE tt.tag_id NOT IN (:tag_ids) WHERE (tt.tag_id NOT IN (:tag_ids)
AND tt.topic_id = topics.id AND tt.topic_id = topics.id)
#{user && !opts[:skip_categories] ? "OR COALESCE(category_users.notification_level, :regular) >= :watching_or_infinite" : ""}
) OR NOT EXISTS (SELECT 1 FROM topic_tags tt WHERE tt.topic_id = topics.id)", ) OR NOT EXISTS (SELECT 1 FROM topic_tags tt WHERE tt.topic_id = topics.id)",
tag_ids: muted_tag_ids, tag_ids: muted_tag_ids,
regular: CategoryUser.notification_levels[:regular],
watching_or_infinite:
(
if SiteSetting.watched_precedence_over_muted
CategoryUser.notification_levels[:watching]
else
99
end
),
) )
end end
end end

View File

@ -49,7 +49,7 @@ class TopicQuery
def list_private_messages_new(user, type = :user) def list_private_messages_new(user, type = :user)
list = filter_private_message_new(user, type) list = filter_private_message_new(user, type)
list = TopicQuery.remove_muted_tags(list, user) list = TopicQuery.remove_muted_tags(list, user, skip_categories: true)
list = remove_dismissed(list, user) list = remove_dismissed(list, user)
create_list(:private_messages, {}, list) create_list(:private_messages, {}, list)

View File

@ -2092,4 +2092,73 @@ RSpec.describe TopicQuery do
expect(original_topic_query.list_latest.topics.map(&:id)).to eq([topic2, topic1].map(&:id)) expect(original_topic_query.list_latest.topics.map(&:id)).to eq([topic2, topic1].map(&:id))
end end
end end
describe "precedence of categories and tag setting" do
fab!(:watched_category) do
Fabricate(:category).tap do |category|
CategoryUser.create!(
user: user,
category: category,
notification_level: CategoryUser.notification_levels[:watching],
)
end
end
fab!(:muted_category) do
Fabricate(:category).tap do |category|
CategoryUser.create!(
user: user,
category: category,
notification_level: CategoryUser.notification_levels[:muted],
)
end
end
fab!(:watched_tag) do
Fabricate(:tag).tap do |tag|
TagUser.create!(
user: user,
tag: tag,
notification_level: TagUser.notification_levels[:watching],
)
end
end
fab!(:muted_tag) do
Fabricate(:tag).tap do |tag|
TagUser.create!(
user: user,
tag: tag,
notification_level: TagUser.notification_levels[:muted],
)
end
end
fab!(:topic) { Fabricate(:topic) }
fab!(:topic_in_watched_category_and_muted_tag) do
Fabricate(:topic, category: watched_category, tags: [muted_tag])
end
fab!(:topic_in_muted_category_and_watched_tag) do
Fabricate(:topic, category: muted_category, tags: [watched_tag])
end
fab!(:topic_in_watched_and_muted_tag) { Fabricate(:topic, tags: [watched_tag, muted_tag]) }
fab!(:topic_in_muted_category) { Fabricate(:topic, category: muted_category) }
fab!(:topic_in_muted_tag) { Fabricate(:topic, tags: [muted_tag]) }
context "when enabled" do
it "returns topics even if category or tag is muted but another tag or category is watched" do
SiteSetting.watched_precedence_over_muted = true
query = TopicQuery.new(user).list_latest
expect(query.topics.map(&:id)).to contain_exactly(
topic.id,
topic_in_watched_category_and_muted_tag.id,
topic_in_muted_category_and_watched_tag.id,
)
end
end
context "when disabled" do
it "returns topics without muted category or tag" do
SiteSetting.watched_precedence_over_muted = false
query = TopicQuery.new(user).list_latest
expect(query.topics.map(&:id)).to contain_exactly(topic.id)
end
end
end
end end

View File

@ -230,6 +230,8 @@ RSpec.describe TagUser do
end end
it "sets notification level to the highest one if there are multiple tags" do it "sets notification level to the highest one if there are multiple tags" do
SiteSetting.watched_precedence_over_muted = true
TagUser.create!( TagUser.create!(
user: user, user: user,
tag: tracked_tag, tag: tracked_tag,

View File

@ -1871,6 +1871,77 @@ RSpec.describe PostAlerter do
end end
end end
context "with category and tags" do
fab!(:muted_category) do
Fabricate(:category).tap do |category|
CategoryUser.set_notification_level_for_category(
user,
CategoryUser.notification_levels[:muted],
category.id,
)
end
end
fab!(:muted_tag) do
Fabricate(:tag).tap do |tag|
TagUser.create!(
user: user,
tag: tag,
notification_level: TagUser.notification_levels[:muted],
)
end
end
fab!(:watched_tag) do
Fabricate(:tag).tap do |tag|
TagUser.create!(
user: user,
tag: tag,
notification_level: TagUser.notification_levels[:watching],
)
end
end
fab!(:topic_with_muted_tag_and_watched_category) do
Fabricate(:topic, category: category, tags: [muted_tag])
end
fab!(:topic_with_muted_category_and_watched_tag) do
Fabricate(:topic, category: muted_category, tags: [watched_tag])
end
fab!(:topic_with_watched_category) { Fabricate(:topic, category: category) }
fab!(:post) { Fabricate(:post, topic: topic_with_muted_tag_and_watched_category) }
fab!(:post_2) { Fabricate(:post, topic: topic_with_muted_category_and_watched_tag) }
fab!(:post_3) { Fabricate(:post, topic: topic_with_watched_category) }
before do
CategoryUser.set_notification_level_for_category(
user,
CategoryUser.notification_levels[:watching],
category.id,
)
end
it "adds notification when watched_precedence_over_mute setting is true" do
SiteSetting.watched_precedence_over_muted = true
expect {
PostAlerter.post_created(topic_with_muted_tag_and_watched_category.posts.first)
}.to change { Notification.count }.by(1)
expect {
PostAlerter.post_created(topic_with_muted_category_and_watched_tag.posts.first)
}.to change { Notification.count }.by(1)
end
it "does not add notification when watched_precedence_over_mute setting is false" do
SiteSetting.watched_precedence_over_muted = false
expect {
PostAlerter.post_created(topic_with_muted_tag_and_watched_category.posts.first)
}.not_to change { Notification.count }
expect {
PostAlerter.post_created(topic_with_muted_category_and_watched_tag.posts.first)
}.not_to change { Notification.count }
expect { PostAlerter.post_created(topic_with_watched_category.posts.first) }.to change {
Notification.count
}.by(1)
end
end
context "with on change" do context "with on change" do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
fab!(:other_tag) { Fabricate(:tag) } fab!(:other_tag) { Fabricate(:tag) }