DEV: Redesign chat mentions (#24752)

At the moment, when someone is mentioning a group, or using here or 
all mention, we create a chat_mention record per user. What we want 
instead is to have special kinds of mentions, so we can create only one 
chat_mention record in such cases. This PR implements that.

Note, that such mentions will still have N related notifications, one 
notification per a user. We don't expect we'll have performance 
problems on the notifications side, but if at some point we do, we 
should be able to solve them on the side of notifications 
(notifications are handled in jobs, also some little delays with 
the notifications are acceptable, so we can make sure notifications 
are properly queued, and that processing of every notification is 
fast enough to make delays small enough).

The preparation work for this PR was done in fbd24fa, where we make 
it possible for one mention to have several related notifications.

A pretty tricky part of this PR is schema and data migration, I've explained 
related details inline on the migration files.
This commit is contained in:
Andrei Prigorshnev
2024-01-17 15:24:01 +04:00
committed by GitHub
parent 6876c52857
commit 62f423da15
40 changed files with 613 additions and 157 deletions

View File

@@ -145,13 +145,43 @@ module Jobs
memberships = get_memberships(user_ids)
memberships.each do |membership|
mention = ::Chat::Mention.find_by(user: membership.user, chat_message: @chat_message)
mention = find_mention(@chat_message, mention_type, membership.user.id)
if mention.present?
create_notification!(membership, mention, mention_type)
send_notifications(membership, mention_type)
end
end
end
def find_mention(chat_message, mention_type, user_id)
mention_klass = resolve_mention_klass(mention_type)
target_id = nil
if mention_klass == ::Chat::UserMention
target_id = user_id
elsif mention_klass == ::Chat::GroupMention
begin
target_id = Group.where("LOWER(name) = ?", "#{mention_type}").first.id
rescue => e
Discourse.warn_exception(e, message: "Mentioned group doesn't exist")
end
end
mention_klass.find_by(chat_message: chat_message, target_id: target_id)
end
def resolve_mention_klass(mention_type)
case mention_type
when :global_mentions
::Chat::AllMention
when :here_mentions
::Chat::HereMention
when :direct_mentions
::Chat::UserMention
else
::Chat::GroupMention
end
end
end
end
end

View File

@@ -0,0 +1,6 @@
# frozen_string_literal: true
module Chat
class AllMention < Mention
end
end

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module Chat
class GroupMention < Mention
belongs_to :group, foreign_key: :target_id
end
end

View File

@@ -0,0 +1,6 @@
# frozen_string_literal: true
module Chat
class HereMention < Mention
end
end

View File

@@ -3,9 +3,8 @@
module Chat
class Mention < ActiveRecord::Base
self.table_name = "chat_mentions"
self.ignored_columns = ["notification_id"]
self.ignored_columns = %w[notification_id user_id]
belongs_to :user
belongs_to :chat_message, class_name: "Chat::Message"
has_many :mention_notifications,
class_name: "Chat::MentionNotification",
@@ -20,12 +19,15 @@ end
#
# id :bigint not null, primary key
# chat_message_id :integer not null
# user_id :integer not null
# user_id :integer
# notification_id :integer not null
# target_id :integer
# type :integer not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# chat_mentions_index (chat_message_id,user_id,notification_id) UNIQUE
# index_chat_mentions_on_chat_message_id (chat_message_id)
# index_chat_mentions_on_target_id (target_id)
#

View File

@@ -51,6 +51,22 @@ module Chat
dependent: :destroy,
class_name: "Chat::Mention",
foreign_key: :chat_message_id
has_many :user_mentions,
dependent: :destroy,
class_name: "Chat::UserMention",
foreign_key: :chat_message_id
has_many :group_mentions,
dependent: :destroy,
class_name: "Chat::GroupMention",
foreign_key: :chat_message_id
has_one :all_mention,
dependent: :destroy,
class_name: "Chat::AllMention",
foreign_key: :chat_message_id
has_one :here_mention,
dependent: :destroy,
class_name: "Chat::HereMention",
foreign_key: :chat_message_id
scope :in_public_channel,
-> do
@@ -248,14 +264,10 @@ module Chat
end
def upsert_mentions
mentioned_user_ids = parsed_mentions.all_mentioned_users_ids
old_mentions = chat_mentions.pluck(:user_id)
mentioned_user_ids_to_drop = old_mentions - mentioned_user_ids
delete_mentions(mentioned_user_ids_to_drop)
mentioned_user_ids_to_add = mentioned_user_ids - old_mentions
insert_mentions(mentioned_user_ids_to_add)
upsert_user_mentions
upsert_group_mentions
create_or_delete_all_mention
create_or_delete_here_mention
end
def in_thread?
@@ -280,24 +292,16 @@ module Chat
private
def delete_mentions(user_ids)
chat_mentions.where(user_id: user_ids).destroy_all
def delete_mentions(mention_type, target_ids)
chat_mentions.where(type: mention_type, target_id: target_ids).destroy_all
end
def insert_mentions(user_ids)
return if user_ids.empty?
def insert_mentions(type, target_ids)
return if target_ids.empty?
now = Time.zone.now
mentions = []
User
.where(id: user_ids)
.find_each do |user|
mentions << {
chat_message_id: self.id,
user_id: user.id,
created_at: now,
updated_at: now,
}
mentions =
target_ids.map do |target_id|
{ chat_message_id: self.id, target_id: target_id, type: type }
end
Chat::Mention.insert_all(mentions)
@@ -314,6 +318,38 @@ module Chat
def ensure_last_editor_id
self.last_editor_id ||= self.user_id
end
def create_or_delete_all_mention
if !parsed_mentions.has_global_mention && all_mention.present?
all_mention.destroy!
association(:all_mention).reload
elsif parsed_mentions.has_global_mention && all_mention.blank?
build_all_mention.save!
end
end
def create_or_delete_here_mention
if !parsed_mentions.has_here_mention && here_mention.present?
here_mention.destroy!
association(:here_mention).reload
elsif parsed_mentions.has_here_mention && here_mention.blank?
build_here_mention.save!
end
end
def upsert_group_mentions
old_mentions = group_mentions.pluck(:target_id)
new_mentions = parsed_mentions.groups_to_mention.pluck(:id)
delete_mentions("Chat::GroupMention", old_mentions - new_mentions)
insert_mentions("Chat::GroupMention", new_mentions - old_mentions)
end
def upsert_user_mentions
old_mentions = user_mentions.pluck(:target_id)
new_mentions = parsed_mentions.direct_mentions.pluck(:id)
delete_mentions("Chat::UserMention", old_mentions - new_mentions)
insert_mentions("Chat::UserMention", new_mentions - old_mentions)
end
end
end

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module Chat
class UserMention < Mention
belongs_to :user, foreign_key: :target_id
end
end

View File

@@ -87,9 +87,9 @@ module Chat
if SiteSetting.enable_user_status
query = query.includes(user: :user_status)
query = query.includes(chat_mentions: { user: :user_status })
query = query.includes(user_mentions: { user: :user_status })
else
query = query.includes(chat_mentions: :user)
query = query.includes(user_mentions: :user)
end
query

View File

@@ -36,7 +36,7 @@ module Chat
def mentioned_users
object
.chat_mentions
.user_mentions
.limit(SiteSetting.max_mentions_per_chat_message)
.map(&:user)
.compact

View File

@@ -17,7 +17,7 @@ module Chat
def mentioned_users
object
.chat_mentions
.user_mentions
.limit(SiteSetting.max_mentions_per_chat_message)
.map(&:user)
.compact