DEV: store user ids for different types of notifications in different hashes (#22049)

We had a bug in this code recently, sometimes users saw weird notifications 
like:

User mentioned all_mentioned_user_ids in the help chat channel

We fixed that bug in b85d057.

This refactoring is a follow-up to that fix. As that bug showed, it’s quite easy 
to introduce a key that may end up being sent to the `NotifyMentioned` job, 
which can lead to such weird notifications. This refactoring makes sure that 
the `to_notify` hash contains only IDs of users that should be notified about 
mentions.
This commit is contained in:
Andrei Prigorshnev 2023-06-20 20:47:00 +04:00 committed by GitHub
parent a4594b925b
commit f4add0ef6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 36 additions and 40 deletions

View File

@ -67,17 +67,16 @@ module Chat
### Public API ### Public API
def notify_new def notify_new
to_notify = list_users_to_notify to_notify, inaccessible, all_mentioned_user_ids = list_users_to_notify
mentioned_user_ids = to_notify.extract!(:all_mentioned_user_ids)[:all_mentioned_user_ids]
mentioned_user_ids.each do |member_id| all_mentioned_user_ids.each do |member_id|
Chat::Publisher.publish_new_mention(member_id, @chat_channel.id, @chat_message.id) Chat::Publisher.publish_new_mention(member_id, @chat_channel.id, @chat_message.id)
end end
notify_creator_of_inaccessible_mentions(to_notify) notify_creator_of_inaccessible_mentions(inaccessible)
notify_mentioned_users(to_notify) notify_mentioned_users(to_notify)
notify_watching_users(except: mentioned_user_ids << @user.id) notify_watching_users(except: all_mentioned_user_ids << @user.id)
to_notify to_notify
end end
@ -89,12 +88,11 @@ module Chat
.where.not(notification: nil) .where.not(notification: nil)
.pluck(:user_id) .pluck(:user_id)
to_notify = list_users_to_notify to_notify, inaccessible, all_mentioned_user_ids = list_users_to_notify
mentioned_user_ids = to_notify.extract!(:all_mentioned_user_ids)[:all_mentioned_user_ids] needs_notification_ids = all_mentioned_user_ids - already_notified_user_ids
needs_notification_ids = mentioned_user_ids - already_notified_user_ids
return if needs_notification_ids.blank? return if needs_notification_ids.blank?
notify_creator_of_inaccessible_mentions(to_notify) notify_creator_of_inaccessible_mentions(inaccessible)
notify_mentioned_users(to_notify, already_notified_user_ids: already_notified_user_ids) notify_mentioned_users(to_notify, already_notified_user_ids: already_notified_user_ids)
to_notify to_notify
@ -105,23 +103,22 @@ module Chat
def list_users_to_notify def list_users_to_notify
skip_notifications = @parsed_mentions.count > SiteSetting.max_mentions_per_chat_message skip_notifications = @parsed_mentions.count > SiteSetting.max_mentions_per_chat_message
{}.tap do |to_notify| to_notify = {}
# The order of these methods is the precedence inaccessible = {}
# between different mention types. all_mentioned_user_ids = []
already_covered_ids = [] # The order of these methods is the precedence
# between different mention types.
expand_direct_mentions(to_notify, already_covered_ids, skip_notifications) expand_direct_mentions(to_notify, inaccessible, all_mentioned_user_ids, skip_notifications)
if !skip_notifications if !skip_notifications
expand_group_mentions(to_notify, already_covered_ids) expand_group_mentions(to_notify, inaccessible, all_mentioned_user_ids)
expand_here_mention(to_notify, already_covered_ids) expand_here_mention(to_notify, all_mentioned_user_ids)
expand_global_mention(to_notify, already_covered_ids) expand_global_mention(to_notify, all_mentioned_user_ids)
end
filter_users_ignoring_or_muting_creator(to_notify, already_covered_ids)
to_notify[:all_mentioned_user_ids] = already_covered_ids
end end
filter_users_ignoring_or_muting_creator(to_notify, inaccessible, all_mentioned_user_ids)
[to_notify, inaccessible, all_mentioned_user_ids]
end end
def expand_global_mention(to_notify, already_covered_ids) def expand_global_mention(to_notify, already_covered_ids)
@ -183,7 +180,7 @@ module Chat
} }
end end
def expand_direct_mentions(to_notify, already_covered_ids, skip) def expand_direct_mentions(to_notify, inaccessible, already_covered_ids, skip)
if skip if skip
direct_mentions = [] direct_mentions = []
else else
@ -198,12 +195,12 @@ module Chat
grouped = group_users_to_notify(direct_mentions) grouped = group_users_to_notify(direct_mentions)
to_notify[:direct_mentions] = grouped[:already_participating].map(&:id) to_notify[:direct_mentions] = grouped[:already_participating].map(&:id)
to_notify[:welcome_to_join] = grouped[:welcome_to_join] inaccessible[:welcome_to_join] = grouped[:welcome_to_join]
to_notify[:unreachable] = grouped[:unreachable] inaccessible[:unreachable] = grouped[:unreachable]
already_covered_ids.concat(to_notify[:direct_mentions]) already_covered_ids.concat(to_notify[:direct_mentions])
end end
def expand_group_mentions(to_notify, already_covered_ids) def expand_group_mentions(to_notify, inaccessible, already_covered_ids)
return if @parsed_mentions.visible_groups.empty? return if @parsed_mentions.visible_groups.empty?
reached_by_group = reached_by_group =
@ -230,12 +227,13 @@ module Chat
already_covered_ids << user.id already_covered_ids << user.id
end end
to_notify[:welcome_to_join] = to_notify[:welcome_to_join].concat(grouped[:welcome_to_join]) inaccessible[:welcome_to_join] = inaccessible[:welcome_to_join].concat(
to_notify[:unreachable] = to_notify[:unreachable].concat(grouped[:unreachable]) grouped[:welcome_to_join],
)
inaccessible[:unreachable] = inaccessible[:unreachable].concat(grouped[:unreachable])
end end
def notify_creator_of_inaccessible_mentions(to_notify) def notify_creator_of_inaccessible_mentions(inaccessible)
inaccessible = to_notify.extract!(:unreachable, :welcome_to_join)
group_mentions_disabled = @parsed_mentions.groups_with_disabled_mentions.to_a group_mentions_disabled = @parsed_mentions.groups_with_disabled_mentions.to_a
too_many_members = @parsed_mentions.groups_with_too_many_members.to_a too_many_members = @parsed_mentions.groups_with_too_many_members.to_a
if inaccessible.values.all?(&:blank?) && group_mentions_disabled.empty? && if inaccessible.values.all?(&:blank?) && group_mentions_disabled.empty? &&
@ -257,20 +255,18 @@ module Chat
# ignoring or muting the creator of the message, so they will not receive # ignoring or muting the creator of the message, so they will not receive
# a notification via the Jobs::Chat::NotifyMentioned job and are not prompted for # a notification via the Jobs::Chat::NotifyMentioned job and are not prompted for
# invitation by the creator. # invitation by the creator.
def filter_users_ignoring_or_muting_creator(to_notify, already_covered_ids) def filter_users_ignoring_or_muting_creator(to_notify, inaccessible, already_covered_ids)
screen_targets = already_covered_ids.concat(to_notify[:welcome_to_join].map(&:id)) screen_targets = already_covered_ids.concat(inaccessible[:welcome_to_join].map(&:id))
return if screen_targets.blank? return if screen_targets.blank?
screener = UserCommScreener.new(acting_user: @user, target_user_ids: screen_targets) screener = UserCommScreener.new(acting_user: @user, target_user_ids: screen_targets)
to_notify to_notify.each do |key, user_ids|
.except(:unreachable, :welcome_to_join) to_notify[key] = user_ids.reject { |user_id| screener.ignoring_or_muting_actor?(user_id) }
.each do |key, user_ids| end
to_notify[key] = user_ids.reject { |user_id| screener.ignoring_or_muting_actor?(user_id) }
end
# :welcome_to_join contains users because it's serialized by MB. # :welcome_to_join contains users because it's serialized by MB.
to_notify[:welcome_to_join] = to_notify[:welcome_to_join].reject do |user| inaccessible[:welcome_to_join] = inaccessible[:welcome_to_join].reject do |user|
screener.ignoring_or_muting_actor?(user.id) screener.ignoring_or_muting_actor?(user.id)
end end