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)
if !SiteSetting.watched_precedence_over_muted
notified = notified + category_or_tag_muters(post.topic)
end
if new_record
if post.topic.private_message?
# private messages
@ -265,6 +269,18 @@ class PostAlerter
.pluck(:user_id)
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)
return [] if user_ids.blank?
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."
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"
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"
governing_law: "Governing Law"

View File

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

View File

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

View File

@ -49,7 +49,7 @@ class TopicQuery
def list_private_messages_new(user, type = :user)
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)
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))
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

View File

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

View File

@ -1871,6 +1871,77 @@ RSpec.describe PostAlerter do
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
fab!(:user) { Fabricate(:user) }
fab!(:other_tag) { Fabricate(:tag) }