mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
DEV: Add last_message_id to channel and thread (#22488)
Initial migration and changes to models as well as changing the following services to update last_message_id: * Chat::MessageCreator * Chat::RestoreMessage * Chat::TrashMessage The data migration will set the `last_message_id` for all existing threads and channels in the database. When we query the thread list as well as the channel, we look at the last message ID for the following: * Channel - Sorting DM channels, and channel metadata for the list of channels * Thread - Last reply details for thread indicators and thread list
This commit is contained in:
parent
4ae26bcaac
commit
b1978e7ad8
@ -5,6 +5,9 @@ module Chat
|
|||||||
include Trashable
|
include Trashable
|
||||||
include TypeMappable
|
include TypeMappable
|
||||||
|
|
||||||
|
# TODO (martin) Remove once we are using last_message instead,
|
||||||
|
# should be around August 2023.
|
||||||
|
self.ignored_columns = %w[last_message_sent_at]
|
||||||
self.table_name = "chat_channels"
|
self.table_name = "chat_channels"
|
||||||
|
|
||||||
belongs_to :chatable, polymorphic: true
|
belongs_to :chatable, polymorphic: true
|
||||||
@ -20,6 +23,10 @@ module Chat
|
|||||||
foreign_key: :chat_channel_id
|
foreign_key: :chat_channel_id
|
||||||
has_many :threads, class_name: "Chat::Thread", foreign_key: :channel_id
|
has_many :threads, class_name: "Chat::Thread", foreign_key: :channel_id
|
||||||
has_one :chat_channel_archive, class_name: "Chat::ChannelArchive", foreign_key: :chat_channel_id
|
has_one :chat_channel_archive, class_name: "Chat::ChannelArchive", foreign_key: :chat_channel_id
|
||||||
|
belongs_to :last_message,
|
||||||
|
class_name: "Chat::Message",
|
||||||
|
foreign_key: :last_message_id,
|
||||||
|
optional: true
|
||||||
|
|
||||||
enum :status, { open: 0, read_only: 1, closed: 2, archived: 3 }, scopes: false
|
enum :status, { open: 0, read_only: 1, closed: 2, archived: 3 }, scopes: false
|
||||||
|
|
||||||
@ -103,6 +110,10 @@ module Chat
|
|||||||
"#{Discourse.base_path}/chat/c/#{self.slug || "-"}/#{self.id}"
|
"#{Discourse.base_path}/chat/c/#{self.slug || "-"}/#{self.id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_last_message_id!
|
||||||
|
self.update!(last_message_id: self.latest_not_deleted_message_id)
|
||||||
|
end
|
||||||
|
|
||||||
def self.ensure_consistency!
|
def self.ensure_consistency!
|
||||||
update_message_counts
|
update_message_counts
|
||||||
update_user_counts
|
update_user_counts
|
||||||
@ -155,11 +166,15 @@ module Chat
|
|||||||
|
|
||||||
def latest_not_deleted_message_id(anchor_message_id: nil)
|
def latest_not_deleted_message_id(anchor_message_id: nil)
|
||||||
DB.query_single(<<~SQL, channel_id: self.id, anchor_message_id: anchor_message_id).first
|
DB.query_single(<<~SQL, channel_id: self.id, anchor_message_id: anchor_message_id).first
|
||||||
SELECT id FROM chat_messages
|
SELECT chat_messages.id
|
||||||
|
FROM chat_messages
|
||||||
|
LEFT JOIN chat_threads original_message_threads ON original_message_threads.original_message_id = chat_messages.id
|
||||||
WHERE chat_channel_id = :channel_id
|
WHERE chat_channel_id = :channel_id
|
||||||
AND deleted_at IS NULL
|
AND deleted_at IS NULL
|
||||||
#{anchor_message_id ? "AND id < :anchor_message_id" : ""}
|
-- this is so only the original message of a thread is counted not all thread replies
|
||||||
ORDER BY created_at DESC, id DESC
|
AND (chat_messages.thread_id IS NULL OR original_message_threads.id IS NOT NULL)
|
||||||
|
#{anchor_message_id ? "AND chat_messages.id < :anchor_message_id" : ""}
|
||||||
|
ORDER BY chat_messages.created_at DESC, chat_messages.id DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
@ -171,19 +186,12 @@ module Chat
|
|||||||
|
|
||||||
DB.exec(<<~SQL, channel_id: self.id)
|
DB.exec(<<~SQL, channel_id: self.id)
|
||||||
UPDATE user_chat_thread_memberships
|
UPDATE user_chat_thread_memberships
|
||||||
SET last_read_message_id = subquery.last_message_id
|
SET last_read_message_id = chat_threads.last_message_id
|
||||||
FROM (
|
FROM chat_threads
|
||||||
SELECT chat_threads.id AS thread_id, MAX(chat_messages.id) AS last_message_id
|
WHERE user_chat_thread_memberships.thread_id = chat_threads.id
|
||||||
FROM chat_threads
|
|
||||||
INNER JOIN chat_messages ON chat_messages.thread_id = chat_threads.id
|
|
||||||
WHERE chat_threads.channel_id = :channel_id
|
|
||||||
AND chat_messages.deleted_at IS NULL
|
|
||||||
GROUP BY chat_threads.id
|
|
||||||
) subquery
|
|
||||||
WHERE user_chat_thread_memberships.thread_id = subquery.thread_id
|
|
||||||
#{user ? "AND user_chat_thread_memberships.user_id = #{user.id}" : ""}
|
#{user ? "AND user_chat_thread_memberships.user_id = #{user.id}" : ""}
|
||||||
AND (
|
AND (
|
||||||
user_chat_thread_memberships.last_read_message_id < subquery.last_message_id OR
|
user_chat_thread_memberships.last_read_message_id < chat_threads.last_message_id OR
|
||||||
user_chat_thread_memberships.last_read_message_id IS NULL
|
user_chat_thread_memberships.last_read_message_id IS NULL
|
||||||
)
|
)
|
||||||
SQL
|
SQL
|
||||||
@ -249,11 +257,13 @@ end
|
|||||||
# allow_channel_wide_mentions :boolean default(TRUE), not null
|
# allow_channel_wide_mentions :boolean default(TRUE), not null
|
||||||
# messages_count :integer default(0), not null
|
# messages_count :integer default(0), not null
|
||||||
# threading_enabled :boolean default(FALSE), not null
|
# threading_enabled :boolean default(FALSE), not null
|
||||||
|
# last_message_id :bigint
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_chat_channels_on_chatable_id (chatable_id)
|
# index_chat_channels_on_chatable_id (chatable_id)
|
||||||
# index_chat_channels_on_chatable_id_and_chatable_type (chatable_id,chatable_type)
|
# index_chat_channels_on_chatable_id_and_chatable_type (chatable_id,chatable_type)
|
||||||
|
# index_chat_channels_on_last_message_id (last_message_id)
|
||||||
# index_chat_channels_on_messages_count (messages_count)
|
# index_chat_channels_on_messages_count (messages_count)
|
||||||
# index_chat_channels_on_slug (slug) UNIQUE
|
# index_chat_channels_on_slug (slug) UNIQUE
|
||||||
# index_chat_channels_on_status (status)
|
# index_chat_channels_on_status (status)
|
||||||
|
@ -23,6 +23,10 @@ module Chat
|
|||||||
primary_key: :id,
|
primary_key: :id,
|
||||||
class_name: "Chat::Message"
|
class_name: "Chat::Message"
|
||||||
has_many :user_chat_thread_memberships
|
has_many :user_chat_thread_memberships
|
||||||
|
belongs_to :last_message,
|
||||||
|
class_name: "Chat::Message",
|
||||||
|
foreign_key: :last_message_id,
|
||||||
|
optional: true
|
||||||
|
|
||||||
enum :status, { open: 0, read_only: 1, closed: 2, archived: 3 }, scopes: false
|
enum :status, { open: 0, read_only: 1, closed: 2, archived: 3 }, scopes: false
|
||||||
|
|
||||||
@ -30,18 +34,8 @@ module Chat
|
|||||||
|
|
||||||
# Since the `replies` for the thread can all be deleted, to avoid errors
|
# Since the `replies` for the thread can all be deleted, to avoid errors
|
||||||
# in lists and previews of the thread, we can consider the original message
|
# in lists and previews of the thread, we can consider the original message
|
||||||
# as the last "reply" in this case, so we don't exclude that here.
|
# as the last message in this case as a fallback.
|
||||||
#
|
before_create { self.last_message_id = self.original_message_id }
|
||||||
# This is a manual getter/setter so we can avoid N1 queries. This used to be
|
|
||||||
# a has_one relationship on the model, but that has some awkward behaviour
|
|
||||||
# and still caused N1s, and ordering was not applied in complex AR queries.
|
|
||||||
def last_reply
|
|
||||||
@last_reply ||= self.chat_messages.reorder("created_at DESC, id DESC").first
|
|
||||||
end
|
|
||||||
|
|
||||||
def last_reply=(message)
|
|
||||||
@last_reply = message
|
|
||||||
end
|
|
||||||
|
|
||||||
def add(user)
|
def add(user)
|
||||||
Chat::UserChatThreadMembership.find_or_create_by!(user: user, thread: self)
|
Chat::UserChatThreadMembership.find_or_create_by!(user: user, thread: self)
|
||||||
@ -71,6 +65,10 @@ module Chat
|
|||||||
original_message.excerpt(max_length: EXCERPT_LENGTH)
|
original_message.excerpt(max_length: EXCERPT_LENGTH)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_last_message_id!
|
||||||
|
self.update!(last_message_id: self.latest_not_deleted_message_id)
|
||||||
|
end
|
||||||
|
|
||||||
def latest_not_deleted_message_id(anchor_message_id: nil)
|
def latest_not_deleted_message_id(anchor_message_id: nil)
|
||||||
DB.query_single(
|
DB.query_single(
|
||||||
<<~SQL,
|
<<~SQL,
|
||||||
@ -148,11 +146,13 @@ end
|
|||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# replies_count :integer default(0), not null
|
# replies_count :integer default(0), not null
|
||||||
|
# last_message_id :bigint
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_chat_threads_on_channel_id (channel_id)
|
# index_chat_threads_on_channel_id (channel_id)
|
||||||
# index_chat_threads_on_channel_id_and_status (channel_id,status)
|
# index_chat_threads_on_channel_id_and_status (channel_id,status)
|
||||||
|
# index_chat_threads_on_last_message_id (last_message_id)
|
||||||
# index_chat_threads_on_original_message_id (original_message_id)
|
# index_chat_threads_on_original_message_id (original_message_id)
|
||||||
# index_chat_threads_on_original_message_user_id (original_message_user_id)
|
# index_chat_threads_on_original_message_user_id (original_message_user_id)
|
||||||
# index_chat_threads_on_replies_count (replies_count)
|
# index_chat_threads_on_replies_count (replies_count)
|
||||||
|
@ -12,7 +12,6 @@ module Chat
|
|||||||
:description,
|
:description,
|
||||||
:title,
|
:title,
|
||||||
:slug,
|
:slug,
|
||||||
:last_message_sent_at,
|
|
||||||
:status,
|
:status,
|
||||||
:archive_failed,
|
:archive_failed,
|
||||||
:archive_completed,
|
:archive_completed,
|
||||||
@ -24,6 +23,8 @@ module Chat
|
|||||||
:meta,
|
:meta,
|
||||||
:threading_enabled
|
:threading_enabled
|
||||||
|
|
||||||
|
has_one :last_message, serializer: Chat::LastMessageSerializer, embed: :objects
|
||||||
|
|
||||||
def threading_enabled
|
def threading_enabled
|
||||||
SiteSetting.enable_experimental_chat_threaded_discussions && object.threading_enabled
|
SiteSetting.enable_experimental_chat_threaded_discussions && object.threading_enabled
|
||||||
end
|
end
|
||||||
|
11
plugins/chat/app/serializers/chat/last_message_serializer.rb
Normal file
11
plugins/chat/app/serializers/chat/last_message_serializer.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Chat
|
||||||
|
class LastMessageSerializer < ::ApplicationSerializer
|
||||||
|
# NOTE: The channel last message does not need to serialize relations
|
||||||
|
# etc. at this point in time, since the only thing we are using is
|
||||||
|
# created_at. In future we may want to serialize more for this, at which
|
||||||
|
# point we need to check existing code so we don't introduce N1s.
|
||||||
|
attributes *Chat::MessageSerializer::BASIC_ATTRIBUTES
|
||||||
|
end
|
||||||
|
end
|
@ -2,22 +2,31 @@
|
|||||||
|
|
||||||
module Chat
|
module Chat
|
||||||
class MessageSerializer < ::ApplicationSerializer
|
class MessageSerializer < ::ApplicationSerializer
|
||||||
attributes :id,
|
BASIC_ATTRIBUTES = %i[
|
||||||
:message,
|
id
|
||||||
:cooked,
|
message
|
||||||
:created_at,
|
cooked
|
||||||
:excerpt,
|
created_at
|
||||||
:deleted_at,
|
excerpt
|
||||||
:deleted_by_id,
|
deleted_at
|
||||||
:reviewable_id,
|
deleted_by_id
|
||||||
:user_flag_status,
|
thread_id
|
||||||
:edited,
|
chat_channel_id
|
||||||
:reactions,
|
]
|
||||||
:bookmark,
|
attributes(
|
||||||
:available_flags,
|
*(
|
||||||
:thread_id,
|
BASIC_ATTRIBUTES +
|
||||||
:chat_channel_id,
|
%i[
|
||||||
:mentioned_users
|
mentioned_users
|
||||||
|
reactions
|
||||||
|
bookmark
|
||||||
|
available_flags
|
||||||
|
user_flag_status
|
||||||
|
reviewable_id
|
||||||
|
edited
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
has_one :user, serializer: Chat::MessageUserSerializer, embed: :objects
|
has_one :user, serializer: Chat::MessageUserSerializer, embed: :objects
|
||||||
has_one :chat_webhook_event, serializer: Chat::WebhookEventSerializer, embed: :objects
|
has_one :chat_webhook_event, serializer: Chat::WebhookEventSerializer, embed: :objects
|
||||||
|
@ -20,19 +20,19 @@ module Chat
|
|||||||
end
|
end
|
||||||
|
|
||||||
def last_reply_created_at
|
def last_reply_created_at
|
||||||
object.last_reply.created_at
|
object.last_message.created_at
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_reply_id
|
def last_reply_id
|
||||||
object.last_reply.id
|
object.last_message.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_reply_excerpt
|
def last_reply_excerpt
|
||||||
object.last_reply.excerpt(max_length: Chat::Thread::EXCERPT_LENGTH)
|
object.last_message.excerpt(max_length: Chat::Thread::EXCERPT_LENGTH)
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_reply_user
|
def last_reply_user
|
||||||
object.last_reply.user
|
object.last_message.user
|
||||||
end
|
end
|
||||||
|
|
||||||
def include_participant_data?
|
def include_participant_data?
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Chat
|
||||||
|
module Action
|
||||||
|
class ResetChannelsLastMessageIds
|
||||||
|
# @param [Array] last_message_ids The message IDs to match with the
|
||||||
|
# last_message_id in Chat::Channel which will be reset
|
||||||
|
# to NULL or the most recent non-deleted message in the channel to
|
||||||
|
# update read state.
|
||||||
|
# @param [Integer] channel_ids The channel IDs to update. This is used
|
||||||
|
# to scope the queries better.
|
||||||
|
def self.call(last_message_ids, channel_ids)
|
||||||
|
Chat::Channel
|
||||||
|
.where(id: channel_ids)
|
||||||
|
.where("last_message_id IN (?)", last_message_ids)
|
||||||
|
.find_in_batches { |channels| channels.each(&:update_last_message_id!) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -16,17 +16,12 @@ module Chat
|
|||||||
-- the cte row_number is necessary to only return a single row
|
-- the cte row_number is necessary to only return a single row
|
||||||
-- for each channel to prevent additional data being returned
|
-- for each channel to prevent additional data being returned
|
||||||
WITH cte AS (
|
WITH cte AS (
|
||||||
SELECT * FROM (
|
SELECT chat_channels.id AS chat_channel_id, last_message_id
|
||||||
SELECT id, chat_channel_id, row_number() OVER (
|
FROM chat_channels
|
||||||
PARTITION BY chat_channel_id ORDER BY created_at DESC, id DESC
|
WHERE chat_channels.id IN (:channel_ids)
|
||||||
) AS row_number
|
|
||||||
FROM chat_messages
|
|
||||||
WHERE deleted_at IS NULL AND chat_channel_id IN (:channel_ids)
|
|
||||||
) AS recent_messages
|
|
||||||
WHERE recent_messages.row_number = 1
|
|
||||||
)
|
)
|
||||||
UPDATE user_chat_channel_memberships
|
UPDATE user_chat_channel_memberships
|
||||||
SET last_read_message_id = cte.id
|
SET last_read_message_id = cte.last_message_id
|
||||||
FROM cte
|
FROM cte
|
||||||
WHERE user_chat_channel_memberships.last_read_message_id IN (:last_read_message_ids)
|
WHERE user_chat_channel_memberships.last_read_message_id IN (:last_read_message_ids)
|
||||||
AND cte.chat_channel_id = user_chat_channel_memberships.chat_channel_id;
|
AND cte.chat_channel_id = user_chat_channel_memberships.chat_channel_id;
|
||||||
|
@ -77,7 +77,7 @@ module Chat
|
|||||||
private
|
private
|
||||||
|
|
||||||
def fetch_channel(contract:, **)
|
def fetch_channel(contract:, **)
|
||||||
Chat::Channel.includes(:chatable).find_by(id: contract.channel_id)
|
Chat::Channel.includes(:chatable, :last_message).find_by(id: contract.channel_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_view_channel(guardian:, channel:, **)
|
def can_view_channel(guardian:, channel:, **)
|
||||||
@ -181,9 +181,10 @@ module Chat
|
|||||||
context.threads = []
|
context.threads = []
|
||||||
else
|
else
|
||||||
context.threads =
|
context.threads =
|
||||||
::Chat::Thread.includes(original_message_user: :user_status).where(
|
::Chat::Thread
|
||||||
id: messages.map(&:thread_id).compact.uniq,
|
.strict_loading
|
||||||
)
|
.includes(last_message: [:user], original_message_user: :user_status)
|
||||||
|
.where(id: messages.map(&:thread_id).compact.uniq)
|
||||||
|
|
||||||
# Saves us having to load the same message we already have.
|
# Saves us having to load the same message we already have.
|
||||||
context.threads.each do |thread|
|
context.threads.each do |thread|
|
||||||
|
@ -102,29 +102,7 @@ module Chat
|
|||||||
.to_a
|
.to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
threads = unread_threads + read_threads
|
unread_threads + read_threads
|
||||||
|
|
||||||
if threads.present?
|
|
||||||
last_replies =
|
|
||||||
::Chat::Message
|
|
||||||
.strict_loading
|
|
||||||
.includes(:user, :uploads)
|
|
||||||
.from(<<~SQL)
|
|
||||||
(
|
|
||||||
SELECT thread_id, MAX(created_at) AS latest_created_at, MAX(id) AS latest_message_id
|
|
||||||
FROM chat_messages
|
|
||||||
WHERE thread_id IN (#{threads.map(&:id).join(",")})
|
|
||||||
GROUP BY thread_id
|
|
||||||
) AS last_replies_subquery
|
|
||||||
SQL
|
|
||||||
.joins(
|
|
||||||
"INNER JOIN chat_messages ON chat_messages.id = last_replies_subquery.latest_message_id",
|
|
||||||
)
|
|
||||||
.index_by(&:thread_id)
|
|
||||||
threads.each { |thread| thread.last_reply = last_replies[thread.id] }
|
|
||||||
end
|
|
||||||
|
|
||||||
threads
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_tracking(guardian:, threads:, **)
|
def fetch_tracking(guardian:, threads:, **)
|
||||||
@ -151,6 +129,14 @@ module Chat
|
|||||||
:channel,
|
:channel,
|
||||||
:user_chat_thread_memberships,
|
:user_chat_thread_memberships,
|
||||||
original_message_user: :user_status,
|
original_message_user: :user_status,
|
||||||
|
last_message: [
|
||||||
|
:chat_webhook_event,
|
||||||
|
:chat_channel,
|
||||||
|
chat_mentions: {
|
||||||
|
user: :user_status,
|
||||||
|
},
|
||||||
|
user: :user_status,
|
||||||
|
],
|
||||||
original_message: [
|
original_message: [
|
||||||
:uploads,
|
:uploads,
|
||||||
:chat_webhook_event,
|
:chat_webhook_event,
|
||||||
@ -180,6 +166,9 @@ module Chat
|
|||||||
.joins(
|
.joins(
|
||||||
"LEFT JOIN chat_messages original_messages ON chat_threads.original_message_id = original_messages.id",
|
"LEFT JOIN chat_messages original_messages ON chat_threads.original_message_id = original_messages.id",
|
||||||
)
|
)
|
||||||
|
.joins(
|
||||||
|
"LEFT JOIN chat_messages last_message ON chat_threads.last_message_id = last_message.id",
|
||||||
|
)
|
||||||
.where(user_chat_thread_memberships: { user_id: guardian.user.id })
|
.where(user_chat_thread_memberships: { user_id: guardian.user.id })
|
||||||
.where(
|
.where(
|
||||||
"chat_threads.channel_id = :channel_id AND chat_messages.chat_channel_id = :channel_id",
|
"chat_threads.channel_id = :channel_id AND chat_messages.chat_channel_id = :channel_id",
|
||||||
@ -193,11 +182,10 @@ module Chat
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
"original_messages.deleted_at IS NULL AND chat_messages.deleted_at IS NULL AND original_messages.id IS NOT NULL",
|
"original_messages.deleted_at IS NULL AND chat_messages.deleted_at IS NULL AND original_messages.id IS NOT NULL AND last_message.deleted_at IS NULL",
|
||||||
)
|
)
|
||||||
.group("chat_threads.id")
|
|
||||||
.select(
|
.select(
|
||||||
"chat_threads.id AS thread_id, MAX(chat_messages.created_at) AS latest_message_created_at, MAX(chat_messages.id) AS latest_message_id",
|
"chat_threads.id AS thread_id, last_message.created_at AS latest_message_created_at, last_message.id AS latest_message_id",
|
||||||
)
|
)
|
||||||
.to_sql
|
.to_sql
|
||||||
end
|
end
|
||||||
|
@ -24,25 +24,17 @@ module Chat
|
|||||||
|
|
||||||
def update_last_read_message_ids(guardian:, **)
|
def update_last_read_message_ids(guardian:, **)
|
||||||
updated_memberships = DB.query(<<~SQL, user_id: guardian.user.id)
|
updated_memberships = DB.query(<<~SQL, user_id: guardian.user.id)
|
||||||
UPDATE user_chat_channel_memberships
|
UPDATE user_chat_channel_memberships
|
||||||
SET last_read_message_id = subquery.newest_message_id
|
SET last_read_message_id = chat_channels.last_message_id
|
||||||
FROM
|
FROM chat_channels
|
||||||
(
|
WHERE user_chat_channel_memberships.chat_channel_id = chat_channels.id AND
|
||||||
SELECT chat_messages.chat_channel_id, MAX(chat_messages.id) AS newest_message_id
|
chat_channels.last_message_id > COALESCE(user_chat_channel_memberships.last_read_message_id, 0) AND
|
||||||
FROM chat_messages
|
user_chat_channel_memberships.user_id = :user_id AND
|
||||||
LEFT JOIN chat_threads ON chat_threads.id = chat_messages.thread_id
|
user_chat_channel_memberships.following
|
||||||
WHERE chat_messages.deleted_at IS NULL
|
RETURNING user_chat_channel_memberships.id AS membership_id,
|
||||||
AND (chat_messages.thread_id IS NULL or chat_messages.id = chat_threads.original_message_id)
|
user_chat_channel_memberships.chat_channel_id AS channel_id,
|
||||||
GROUP BY chat_messages.chat_channel_id
|
user_chat_channel_memberships.last_read_message_id;
|
||||||
) AS subquery
|
SQL
|
||||||
WHERE user_chat_channel_memberships.chat_channel_id = subquery.chat_channel_id AND
|
|
||||||
subquery.newest_message_id > COALESCE(user_chat_channel_memberships.last_read_message_id, 0) AND
|
|
||||||
user_chat_channel_memberships.user_id = :user_id AND
|
|
||||||
user_chat_channel_memberships.following
|
|
||||||
RETURNING user_chat_channel_memberships.id AS membership_id,
|
|
||||||
user_chat_channel_memberships.chat_channel_id AS channel_id,
|
|
||||||
user_chat_channel_memberships.last_read_message_id;
|
|
||||||
SQL
|
|
||||||
context[:updated_memberships] = updated_memberships
|
context[:updated_memberships] = updated_memberships
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -9,6 +9,11 @@ module Chat
|
|||||||
destroyed_ids = relation.destroy_all.pluck(:id, :chat_channel_id)
|
destroyed_ids = relation.destroy_all.pluck(:id, :chat_channel_id)
|
||||||
destroyed_message_ids = destroyed_ids.map(&:first).uniq
|
destroyed_message_ids = destroyed_ids.map(&:first).uniq
|
||||||
destroyed_message_channel_ids = destroyed_ids.map(&:second).uniq
|
destroyed_message_channel_ids = destroyed_ids.map(&:second).uniq
|
||||||
|
|
||||||
|
# This needs to be done before reset_last_read so we can lean on the last_message_id
|
||||||
|
# there.
|
||||||
|
reset_last_message_ids(destroyed_message_ids, destroyed_message_channel_ids)
|
||||||
|
|
||||||
reset_last_read(destroyed_message_ids, destroyed_message_channel_ids)
|
reset_last_read(destroyed_message_ids, destroyed_message_channel_ids)
|
||||||
delete_flags(destroyed_message_ids)
|
delete_flags(destroyed_message_ids)
|
||||||
end
|
end
|
||||||
@ -16,6 +21,13 @@ module Chat
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def reset_last_message_ids(destroyed_message_ids, destroyed_message_channel_ids)
|
||||||
|
::Chat::Action::ResetChannelsLastMessageIds.call(
|
||||||
|
destroyed_message_ids,
|
||||||
|
destroyed_message_channel_ids,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def reset_last_read(destroyed_message_ids, destroyed_message_channel_ids)
|
def reset_last_read(destroyed_message_ids, destroyed_message_channel_ids)
|
||||||
::Chat::Action::ResetUserLastReadChannelMessage.call(
|
::Chat::Action::ResetUserLastReadChannelMessage.call(
|
||||||
destroyed_message_ids,
|
destroyed_message_ids,
|
||||||
|
@ -53,12 +53,13 @@ module Chat
|
|||||||
{
|
{
|
||||||
type: "channel",
|
type: "channel",
|
||||||
channel_id: chat_channel.id,
|
channel_id: chat_channel.id,
|
||||||
message_id: chat_message.id,
|
|
||||||
user_id: chat_message.user.id,
|
|
||||||
username: chat_message.user.username,
|
|
||||||
thread_id: chat_message.thread_id,
|
thread_id: chat_message.thread_id,
|
||||||
|
message:
|
||||||
|
Chat::MessageSerializer.new(
|
||||||
|
chat_message,
|
||||||
|
{ scope: anonymous_guardian, root: false },
|
||||||
|
).as_json,
|
||||||
},
|
},
|
||||||
permissions(chat_channel),
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -68,10 +69,12 @@ module Chat
|
|||||||
{
|
{
|
||||||
type: "thread",
|
type: "thread",
|
||||||
channel_id: chat_channel.id,
|
channel_id: chat_channel.id,
|
||||||
message_id: chat_message.id,
|
|
||||||
user_id: chat_message.user.id,
|
|
||||||
username: chat_message.user.username,
|
|
||||||
thread_id: chat_message.thread_id,
|
thread_id: chat_message.thread_id,
|
||||||
|
message:
|
||||||
|
Chat::MessageSerializer.new(
|
||||||
|
chat_message,
|
||||||
|
{ scope: anonymous_guardian, root: false },
|
||||||
|
).as_json,
|
||||||
},
|
},
|
||||||
permissions(chat_channel),
|
permissions(chat_channel),
|
||||||
)
|
)
|
||||||
|
@ -22,6 +22,7 @@ module Chat
|
|||||||
policy :invalid_access
|
policy :invalid_access
|
||||||
transaction do
|
transaction do
|
||||||
step :restore_message
|
step :restore_message
|
||||||
|
step :update_last_message_ids
|
||||||
step :update_thread_reply_cache
|
step :update_thread_reply_cache
|
||||||
end
|
end
|
||||||
step :publish_events
|
step :publish_events
|
||||||
@ -55,6 +56,11 @@ module Chat
|
|||||||
message.thread&.increment_replies_count_cache
|
message.thread&.increment_replies_count_cache
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_last_message_ids(message:, **)
|
||||||
|
message.thread&.update_last_message_id!
|
||||||
|
message.chat_channel.update_last_message_id!
|
||||||
|
end
|
||||||
|
|
||||||
def publish_events(guardian:, message:, **)
|
def publish_events(guardian:, message:, **)
|
||||||
DiscourseEvent.trigger(:chat_message_restored, message, message.chat_channel, guardian.user)
|
DiscourseEvent.trigger(:chat_message_restored, message, message.chat_channel, guardian.user)
|
||||||
Chat::Publisher.publish_restore!(message.chat_channel, message)
|
Chat::Publisher.publish_restore!(message.chat_channel, message)
|
||||||
|
@ -23,6 +23,7 @@ module Chat
|
|||||||
transaction do
|
transaction do
|
||||||
step :trash_message
|
step :trash_message
|
||||||
step :destroy_notifications
|
step :destroy_notifications
|
||||||
|
step :update_last_message_ids
|
||||||
step :update_tracking_state
|
step :update_tracking_state
|
||||||
step :update_thread_reply_cache
|
step :update_thread_reply_cache
|
||||||
end
|
end
|
||||||
@ -70,6 +71,11 @@ module Chat
|
|||||||
message.thread&.decrement_replies_count_cache
|
message.thread&.decrement_replies_count_cache
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_last_message_ids(message:, **)
|
||||||
|
message.thread&.update_last_message_id!
|
||||||
|
message.chat_channel.update_last_message_id!
|
||||||
|
end
|
||||||
|
|
||||||
def publish_events(guardian:, message:, **)
|
def publish_events(guardian:, message:, **)
|
||||||
DiscourseEvent.trigger(:chat_message_trashed, message, message.chat_channel, guardian.user)
|
DiscourseEvent.trigger(:chat_message_trashed, message, message.chat_channel, guardian.user)
|
||||||
Chat::Publisher.publish_delete!(message.chat_channel, message)
|
Chat::Publisher.publish_delete!(message.chat_channel, message)
|
||||||
|
@ -61,7 +61,7 @@ module Chat
|
|||||||
membership = thread.membership_for(guardian.user)
|
membership = thread.membership_for(guardian.user)
|
||||||
if !membership
|
if !membership
|
||||||
membership = thread.add(guardian.user)
|
membership = thread.add(guardian.user)
|
||||||
membership.update!(last_read_message_id: thread.last_reply.id)
|
membership.update!(last_read_message_id: thread.last_message_id)
|
||||||
end
|
end
|
||||||
membership.update!(notification_level: contract.notification_level)
|
membership.update!(notification_level: contract.notification_level)
|
||||||
context.membership = membership
|
context.membership = membership
|
||||||
|
@ -49,14 +49,9 @@ module Chat
|
|||||||
def mark_thread_read(thread:, guardian:, **)
|
def mark_thread_read(thread:, guardian:, **)
|
||||||
query = <<~SQL
|
query = <<~SQL
|
||||||
UPDATE user_chat_thread_memberships
|
UPDATE user_chat_thread_memberships
|
||||||
SET last_read_message_id = (
|
SET last_read_message_id = chat_threads.last_message_id
|
||||||
SELECT id FROM chat_messages
|
FROM chat_threads
|
||||||
WHERE thread_id = :thread_id
|
WHERE user_id = :user_id AND thread_id = :thread_id AND chat_threads.id = :thread_id
|
||||||
AND deleted_at IS NULL
|
|
||||||
ORDER BY created_at DESC, id DESC
|
|
||||||
LIMIT 1
|
|
||||||
)
|
|
||||||
WHERE user_id = :user_id AND thread_id = :thread_id
|
|
||||||
SQL
|
SQL
|
||||||
DB.exec(query, thread_id: thread.id, user_id: guardian.user.id)
|
DB.exec(query, thread_id: thread.id, user_id: guardian.user.id)
|
||||||
end
|
end
|
||||||
@ -73,7 +68,7 @@ module Chat
|
|||||||
::Chat::Publisher.publish_user_tracking_state!(
|
::Chat::Publisher.publish_user_tracking_state!(
|
||||||
guardian.user,
|
guardian.user,
|
||||||
thread.channel,
|
thread.channel,
|
||||||
thread.last_reply,
|
thread.last_message,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<div class="chat-channel-metadata">
|
<div class="chat-channel-metadata">
|
||||||
<div class="chat-channel-metadata__date">
|
{{#if @channel.lastMessage}}
|
||||||
{{this.lastMessageFormattedDate}}
|
<div class="chat-channel-metadata__date">
|
||||||
</div>
|
{{this.lastMessageFormattedDate}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.unreadIndicator}}
|
{{#if this.unreadIndicator}}
|
||||||
<ChatChannelUnreadIndicator @channel={{@channel}} />
|
<ChatChannelUnreadIndicator @channel={{@channel}} />
|
||||||
|
@ -6,7 +6,7 @@ export default class ChatChannelMetadata extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get lastMessageFormattedDate() {
|
get lastMessageFormattedDate() {
|
||||||
return moment(this.args.channel.lastMessageSentAt).calendar(null, {
|
return moment(this.args.channel.lastMessage.createdAt).calendar(null, {
|
||||||
sameDay: "LT",
|
sameDay: "LT",
|
||||||
nextDay: "[Tomorrow]",
|
nextDay: "[Tomorrow]",
|
||||||
nextWeek: "dddd",
|
nextWeek: "dddd",
|
||||||
|
@ -664,10 +664,6 @@ export default class ChatLivePane extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSentMessage(data) {
|
handleSentMessage(data) {
|
||||||
if (this.args.channel.isFollowing) {
|
|
||||||
this.args.channel.lastMessageSentAt = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
|
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
|
||||||
const stagedMessage = handleStagedMessage(
|
const stagedMessage = handleStagedMessage(
|
||||||
this.args.channel,
|
this.args.channel,
|
||||||
@ -686,12 +682,14 @@ export default class ChatLivePane extends Component {
|
|||||||
// If we are at the bottom, we append the message and scroll to it
|
// If we are at the bottom, we append the message and scroll to it
|
||||||
const message = ChatMessage.create(this.args.channel, data.chat_message);
|
const message = ChatMessage.create(this.args.channel, data.chat_message);
|
||||||
this.args.channel.addMessages([message]);
|
this.args.channel.addMessages([message]);
|
||||||
|
this.args.channel.lastMessage = message;
|
||||||
this.scrollToLatestMessage();
|
this.scrollToLatestMessage();
|
||||||
this.updateLastReadMessage();
|
this.updateLastReadMessage();
|
||||||
} else {
|
} else {
|
||||||
// If we are almost at the bottom, we append the message and notice the user
|
// If we are almost at the bottom, we append the message and notice the user
|
||||||
const message = ChatMessage.create(this.args.channel, data.chat_message);
|
const message = ChatMessage.create(this.args.channel, data.chat_message);
|
||||||
this.args.channel.addMessages([message]);
|
this.args.channel.addMessages([message]);
|
||||||
|
this.args.channel.lastMessage = message;
|
||||||
this.hasNewMessages = true;
|
this.hasNewMessages = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,10 +38,6 @@ export default class ChatComposerThread extends ChatComposer {
|
|||||||
return I18n.t("chat.placeholder_thread");
|
return I18n.t("chat.placeholder_thread");
|
||||||
}
|
}
|
||||||
|
|
||||||
get lastMessage() {
|
|
||||||
return this.args.thread.lastMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastUserMessage(user) {
|
lastUserMessage(user) {
|
||||||
return this.args.thread.lastUserMessage(user);
|
return this.args.thread.lastUserMessage(user);
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ function messageFabricator(args = {}) {
|
|||||||
function channelFabricator(args = {}) {
|
function channelFabricator(args = {}) {
|
||||||
const id = args.id || sequence++;
|
const id = args.id || sequence++;
|
||||||
|
|
||||||
return ChatChannel.create(
|
const channel = ChatChannel.create(
|
||||||
Object.assign(
|
Object.assign(
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
@ -62,7 +62,6 @@ function channelFabricator(args = {}) {
|
|||||||
args.chatable?.type ||
|
args.chatable?.type ||
|
||||||
args.chatable_type ||
|
args.chatable_type ||
|
||||||
CHATABLE_TYPES.categoryChannel,
|
CHATABLE_TYPES.categoryChannel,
|
||||||
last_message_sent_at: args.last_message_sent_at,
|
|
||||||
chatable_id: args.chatable?.id || args.chatable_id,
|
chatable_id: args.chatable?.id || args.chatable_id,
|
||||||
title: args.title || "General",
|
title: args.title || "General",
|
||||||
description: args.description,
|
description: args.description,
|
||||||
@ -73,6 +72,10 @@ function channelFabricator(args = {}) {
|
|||||||
args
|
args
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
channel.lastMessage = messageFabricator({ channel });
|
||||||
|
|
||||||
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
function categoryFabricator(args = {}) {
|
function categoryFabricator(args = {}) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import UserChatChannelMembership from "discourse/plugins/chat/discourse/models/user-chat-channel-membership";
|
import UserChatChannelMembership from "discourse/plugins/chat/discourse/models/user-chat-channel-membership";
|
||||||
|
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
||||||
import { TrackedSet } from "@ember-compat/tracked-built-ins";
|
import { TrackedSet } from "@ember-compat/tracked-built-ins";
|
||||||
import { escapeExpression } from "discourse/lib/utilities";
|
import { escapeExpression } from "discourse/lib/utilities";
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { tracked } from "@glimmer/tracking";
|
||||||
@ -61,7 +62,6 @@ export default class ChatChannel {
|
|||||||
@tracked description;
|
@tracked description;
|
||||||
@tracked status;
|
@tracked status;
|
||||||
@tracked activeThread = null;
|
@tracked activeThread = null;
|
||||||
@tracked lastMessageSentAt;
|
|
||||||
@tracked canDeleteOthers;
|
@tracked canDeleteOthers;
|
||||||
@tracked canDeleteSelf;
|
@tracked canDeleteSelf;
|
||||||
@tracked canFlag;
|
@tracked canFlag;
|
||||||
@ -82,6 +82,7 @@ export default class ChatChannel {
|
|||||||
|
|
||||||
@tracked _unreadThreadIds = new TrackedSet();
|
@tracked _unreadThreadIds = new TrackedSet();
|
||||||
@tracked _currentUserMembership;
|
@tracked _currentUserMembership;
|
||||||
|
@tracked _lastMessage;
|
||||||
|
|
||||||
constructor(args = {}) {
|
constructor(args = {}) {
|
||||||
this.id = args.id;
|
this.id = args.id;
|
||||||
@ -99,7 +100,6 @@ export default class ChatChannel {
|
|||||||
this.userSilenced = args.user_silenced;
|
this.userSilenced = args.user_silenced;
|
||||||
this.canModerate = args.can_moderate;
|
this.canModerate = args.can_moderate;
|
||||||
this.description = args.description;
|
this.description = args.description;
|
||||||
this.lastMessageSentAt = args.last_message_sent_at;
|
|
||||||
this.threadingEnabled = args.threading_enabled;
|
this.threadingEnabled = args.threading_enabled;
|
||||||
this.autoJoinUsers = args.auto_join_users;
|
this.autoJoinUsers = args.auto_join_users;
|
||||||
this.allowChannelWideMentions = args.allow_channel_wide_mentions;
|
this.allowChannelWideMentions = args.allow_channel_wide_mentions;
|
||||||
@ -116,6 +116,7 @@ export default class ChatChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.tracking = new ChatTrackingState(getOwner(this));
|
this.tracking = new ChatTrackingState(getOwner(this));
|
||||||
|
this.lastMessage = args.last_message;
|
||||||
}
|
}
|
||||||
|
|
||||||
get unreadThreadCount() {
|
get unreadThreadCount() {
|
||||||
@ -158,10 +159,6 @@ export default class ChatChannel {
|
|||||||
this.messagesManager.removeMessage(message);
|
this.messagesManager.removeMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
get lastMessage() {
|
|
||||||
return this.messagesManager.findLastMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
lastUserMessage(user) {
|
lastUserMessage(user) {
|
||||||
return this.messagesManager.findLastUserMessage(user);
|
return this.messagesManager.findLastUserMessage(user);
|
||||||
}
|
}
|
||||||
@ -312,6 +309,23 @@ export default class ChatChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get lastMessage() {
|
||||||
|
return this._lastMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
set lastMessage(message) {
|
||||||
|
if (!message) {
|
||||||
|
this._lastMessage = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message instanceof ChatMessage) {
|
||||||
|
this._lastMessage = message;
|
||||||
|
} else {
|
||||||
|
this._lastMessage = ChatMessage.create(this, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clearSelectedMessages() {
|
clearSelectedMessages() {
|
||||||
this.selectedMessages.forEach((message) => (message.selected = false));
|
this.selectedMessages.forEach((message) => (message.selected = false));
|
||||||
}
|
}
|
||||||
|
@ -151,8 +151,17 @@ export default class ChatChannelsManager extends Service {
|
|||||||
|
|
||||||
#sortDirectMessageChannels(channels) {
|
#sortDirectMessageChannels(channels) {
|
||||||
return channels.sort((a, b) => {
|
return channels.sort((a, b) => {
|
||||||
|
if (!a.lastMessage) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!b.lastMessage) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (a.tracking.unreadCount === b.tracking.unreadCount) {
|
if (a.tracking.unreadCount === b.tracking.unreadCount) {
|
||||||
return new Date(a.lastMessageSentAt) > new Date(b.lastMessageSentAt)
|
return new Date(a.lastMessage.createdAt) >
|
||||||
|
new Date(b.lastMessage.createdAt)
|
||||||
? -1
|
? -1
|
||||||
: 1;
|
: 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -186,16 +186,20 @@ export default class ChatSubscriptionsManager extends Service {
|
|||||||
|
|
||||||
_onNewChannelMessage(busData) {
|
_onNewChannelMessage(busData) {
|
||||||
this.chatChannelsManager.find(busData.channel_id).then((channel) => {
|
this.chatChannelsManager.find(busData.channel_id).then((channel) => {
|
||||||
if (busData.user_id === this.currentUser.id) {
|
channel.lastMessage = busData.message;
|
||||||
|
const user = busData.message.user;
|
||||||
|
if (user.id === this.currentUser.id) {
|
||||||
// User sent message, update tracking state to no unread
|
// User sent message, update tracking state to no unread
|
||||||
channel.currentUserMembership.lastReadMessageId = busData.message_id;
|
channel.currentUserMembership.lastReadMessageId =
|
||||||
|
channel.lastMessage.id;
|
||||||
} else {
|
} else {
|
||||||
// Ignored user sent message, update tracking state to no unread
|
// Ignored user sent message, update tracking state to no unread
|
||||||
if (this.currentUser.ignored_users.includes(busData.username)) {
|
if (this.currentUser.ignored_users.includes(user.username)) {
|
||||||
channel.currentUserMembership.lastReadMessageId = busData.message_id;
|
channel.currentUserMembership.lastReadMessageId =
|
||||||
|
channel.lastMessage.id;
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
busData.message_id >
|
channel.lastMessage.id >
|
||||||
(channel.currentUserMembership.lastReadMessageId || 0)
|
(channel.currentUserMembership.lastReadMessageId || 0)
|
||||||
) {
|
) {
|
||||||
channel.tracking.unreadCount++;
|
channel.tracking.unreadCount++;
|
||||||
@ -204,7 +208,7 @@ export default class ChatSubscriptionsManager extends Service {
|
|||||||
// Thread should be considered unread if not already.
|
// Thread should be considered unread if not already.
|
||||||
if (busData.thread_id) {
|
if (busData.thread_id) {
|
||||||
channel.threadsManager
|
channel.threadsManager
|
||||||
.find(busData.channel_id, busData.thread_id)
|
.find(channel.id, busData.thread_id)
|
||||||
.then((thread) => {
|
.then((thread) => {
|
||||||
if (thread.currentUserMembership) {
|
if (thread.currentUserMembership) {
|
||||||
channel.unreadThreadIds.add(busData.thread_id);
|
channel.unreadThreadIds.add(busData.thread_id);
|
||||||
@ -213,8 +217,6 @@ export default class ChatSubscriptionsManager extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.lastMessageSentAt = new Date();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,25 +225,29 @@ export default class ChatSubscriptionsManager extends Service {
|
|||||||
channel.threadsManager
|
channel.threadsManager
|
||||||
.find(busData.channel_id, busData.thread_id)
|
.find(busData.channel_id, busData.thread_id)
|
||||||
.then((thread) => {
|
.then((thread) => {
|
||||||
if (busData.user_id === this.currentUser.id) {
|
if (busData.message.user.id === this.currentUser.id) {
|
||||||
// Thread should no longer be considered unread.
|
// Thread should no longer be considered unread.
|
||||||
if (thread.currentUserMembership) {
|
if (thread.currentUserMembership) {
|
||||||
channel.unreadThreadIds.delete(busData.thread_id);
|
channel.unreadThreadIds.delete(busData.thread_id);
|
||||||
thread.currentUserMembership.lastReadMessageId =
|
thread.currentUserMembership.lastReadMessageId =
|
||||||
busData.message_id;
|
busData.message.id;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Ignored user sent message, update tracking state to no unread
|
// Ignored user sent message, update tracking state to no unread
|
||||||
if (this.currentUser.ignored_users.includes(busData.username)) {
|
if (
|
||||||
|
this.currentUser.ignored_users.includes(
|
||||||
|
busData.message.user.username
|
||||||
|
)
|
||||||
|
) {
|
||||||
if (thread.currentUserMembership) {
|
if (thread.currentUserMembership) {
|
||||||
thread.currentUserMembership.lastReadMessageId =
|
thread.currentUserMembership.lastReadMessageId =
|
||||||
busData.message_id;
|
busData.message.id;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Message from other user. Increment unread for thread tracking state.
|
// Message from other user. Increment unread for thread tracking state.
|
||||||
if (
|
if (
|
||||||
thread.currentUserMembership &&
|
thread.currentUserMembership &&
|
||||||
busData.message_id >
|
busData.message.id >
|
||||||
(thread.currentUserMembership.lastReadMessageId || 0) &&
|
(thread.currentUserMembership.lastReadMessageId || 0) &&
|
||||||
!thread.currentUserMembership.isQuiet
|
!thread.currentUserMembership.isQuiet
|
||||||
) {
|
) {
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddLastMessageIdToChannelAndThread < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :chat_channels, :last_message_id, :bigint, null: true
|
||||||
|
add_column :chat_threads, :last_message_id, :bigint, null: true
|
||||||
|
|
||||||
|
add_index :chat_channels, :last_message_id
|
||||||
|
add_index :chat_threads, :last_message_id
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,35 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class BackfillChatChannelAndThreadLastMessageIds < ActiveRecord::Migration[7.0]
|
||||||
|
def up
|
||||||
|
execute <<-SQL
|
||||||
|
UPDATE chat_channels
|
||||||
|
SET last_message_id = (
|
||||||
|
SELECT cm.id
|
||||||
|
FROM chat_messages cm
|
||||||
|
LEFT JOIN chat_threads ON chat_threads.original_message_id = cm.id
|
||||||
|
WHERE cm.chat_channel_id = chat_channels.id
|
||||||
|
AND cm.deleted_at IS NULL
|
||||||
|
AND (cm.thread_id IS NULL OR chat_threads.id IS NOT NULL)
|
||||||
|
ORDER BY cm.created_at DESC, cm.id DESC
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute <<-SQL
|
||||||
|
UPDATE chat_threads
|
||||||
|
SET last_message_id = (
|
||||||
|
SELECT cm.id
|
||||||
|
FROM chat_messages cm
|
||||||
|
WHERE cm.thread_id = chat_threads.id
|
||||||
|
AND cm.deleted_at IS NULL
|
||||||
|
ORDER BY cm.created_at DESC, cm.id DESC
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
raise ActiveRecord::IrreversibleMigration
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,29 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class BackfillChatChannelAndThreadLastMessageIdsPostMigrate < ActiveRecord::Migration[7.0]
|
||||||
|
execute <<-SQL
|
||||||
|
UPDATE chat_channels
|
||||||
|
SET last_message_id = (
|
||||||
|
SELECT cm.id
|
||||||
|
FROM chat_messages cm
|
||||||
|
LEFT JOIN chat_threads ON chat_threads.original_message_id = cm.id
|
||||||
|
WHERE cm.chat_channel_id = chat_channels.id
|
||||||
|
AND cm.deleted_at IS NULL
|
||||||
|
AND (cm.thread_id IS NULL OR chat_threads.id IS NOT NULL)
|
||||||
|
ORDER BY cm.created_at DESC, cm.id DESC
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute <<-SQL
|
||||||
|
UPDATE chat_threads
|
||||||
|
SET last_message_id = (
|
||||||
|
SELECT cm.id
|
||||||
|
FROM chat_messages cm
|
||||||
|
WHERE cm.thread_id = chat_threads.id
|
||||||
|
AND cm.deleted_at IS NULL
|
||||||
|
ORDER BY cm.created_at DESC, cm.id DESC
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
SQL
|
||||||
|
end
|
@ -88,7 +88,7 @@ module Chat
|
|||||||
def self.secured_public_channel_search(guardian, options = {})
|
def self.secured_public_channel_search(guardian, options = {})
|
||||||
allowed_channel_ids = generate_allowed_channel_ids_sql(guardian, exclude_dm_channels: true)
|
allowed_channel_ids = generate_allowed_channel_ids_sql(guardian, exclude_dm_channels: true)
|
||||||
|
|
||||||
channels = Chat::Channel.includes(chatable: [:topic_only_relative_url])
|
channels = Chat::Channel.includes(:last_message, chatable: [:topic_only_relative_url])
|
||||||
channels = channels.includes(:chat_channel_archive) if options[:include_archives]
|
channels = channels.includes(:chat_channel_archive) if options[:include_archives]
|
||||||
|
|
||||||
channels =
|
channels =
|
||||||
@ -179,10 +179,15 @@ module Chat
|
|||||||
def self.secured_direct_message_channels_search(user_id, guardian, options = {})
|
def self.secured_direct_message_channels_search(user_id, guardian, options = {})
|
||||||
query =
|
query =
|
||||||
Chat::Channel.strict_loading.includes(
|
Chat::Channel.strict_loading.includes(
|
||||||
|
last_message: [:uploads],
|
||||||
chatable: [{ direct_message_users: [user: :user_option] }, :users],
|
chatable: [{ direct_message_users: [user: :user_option] }, :users],
|
||||||
)
|
)
|
||||||
query = query.includes(chatable: [{ users: :user_status }]) if SiteSetting.enable_user_status
|
query = query.includes(chatable: [{ users: :user_status }]) if SiteSetting.enable_user_status
|
||||||
query = query.joins(:user_chat_channel_memberships)
|
query = query.joins(:user_chat_channel_memberships)
|
||||||
|
query =
|
||||||
|
query.joins(
|
||||||
|
"LEFT JOIN chat_messages last_message ON last_message.id = chat_channels.last_message_id",
|
||||||
|
)
|
||||||
|
|
||||||
scoped_channels =
|
scoped_channels =
|
||||||
Chat::Channel
|
Chat::Channel
|
||||||
@ -223,7 +228,7 @@ module Chat
|
|||||||
query
|
query
|
||||||
.where(chatable_type: Chat::Channel.direct_channel_chatable_types)
|
.where(chatable_type: Chat::Channel.direct_channel_chatable_types)
|
||||||
.where(chat_channels: { id: scoped_channels })
|
.where(chat_channels: { id: scoped_channels })
|
||||||
.order(last_message_sent_at: :desc)
|
.order("last_message.created_at DESC NULLS LAST")
|
||||||
|
|
||||||
channels = query.to_a
|
channels = query.to_a
|
||||||
preload_fields =
|
preload_fields =
|
||||||
|
@ -63,6 +63,7 @@ module Chat
|
|||||||
@chat_message.attach_uploads(uploads)
|
@chat_message.attach_uploads(uploads)
|
||||||
Chat::Draft.where(user_id: @user.id, chat_channel_id: @chat_channel.id).destroy_all
|
Chat::Draft.where(user_id: @user.id, chat_channel_id: @chat_channel.id).destroy_all
|
||||||
post_process_resolved_thread
|
post_process_resolved_thread
|
||||||
|
update_channel_last_message
|
||||||
Chat::Publisher.publish_new!(
|
Chat::Publisher.publish_new!(
|
||||||
@chat_channel,
|
@chat_channel,
|
||||||
@chat_message,
|
@chat_message,
|
||||||
@ -71,7 +72,6 @@ module Chat
|
|||||||
)
|
)
|
||||||
Jobs.enqueue(Jobs::Chat::ProcessMessage, { chat_message_id: @chat_message.id })
|
Jobs.enqueue(Jobs::Chat::ProcessMessage, { chat_message_id: @chat_message.id })
|
||||||
Chat::Notifier.notify_new(chat_message: @chat_message, timestamp: @chat_message.created_at)
|
Chat::Notifier.notify_new(chat_message: @chat_message, timestamp: @chat_message.created_at)
|
||||||
@chat_channel.touch(:last_message_sent_at)
|
|
||||||
DiscourseEvent.trigger(:chat_message_created, @chat_message, @chat_channel, @user)
|
DiscourseEvent.trigger(:chat_message_created, @chat_message, @chat_channel, @user)
|
||||||
rescue => error
|
rescue => error
|
||||||
@error = error
|
@error = error
|
||||||
@ -232,6 +232,7 @@ module Chat
|
|||||||
def post_process_resolved_thread
|
def post_process_resolved_thread
|
||||||
return if resolved_thread.blank?
|
return if resolved_thread.blank?
|
||||||
|
|
||||||
|
resolved_thread.update!(last_message: @chat_message)
|
||||||
resolved_thread.increment_replies_count_cache
|
resolved_thread.increment_replies_count_cache
|
||||||
current_user_thread_membership = resolved_thread.add(@user)
|
current_user_thread_membership = resolved_thread.add(@user)
|
||||||
current_user_thread_membership.update!(last_read_message_id: @chat_message.id)
|
current_user_thread_membership.update!(last_read_message_id: @chat_message.id)
|
||||||
@ -240,5 +241,10 @@ module Chat
|
|||||||
resolved_thread.add(resolved_thread.original_message_user)
|
resolved_thread.add(resolved_thread.original_message_user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_channel_last_message
|
||||||
|
return if @chat_message.thread_reply?
|
||||||
|
@chat_channel.update!(last_message: @chat_message)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -73,7 +73,7 @@ module Chat
|
|||||||
# Prioritize messages from regular channels over direct messages
|
# Prioritize messages from regular channels over direct messages
|
||||||
if channels.any?
|
if channels.any?
|
||||||
channel_notification_text(
|
channel_notification_text(
|
||||||
channels.sort_by { |channel| [channel.last_message_sent_at, channel.created_at] },
|
channels.sort_by { |channel| [channel.last_message.created_at, channel.created_at] },
|
||||||
dm_users,
|
dm_users,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
@ -120,16 +120,14 @@ describe Chat::MessageCreator do
|
|||||||
}.to change { Chat::Message.count }.by(1)
|
}.to change { Chat::Message.count }.by(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "updates the channel’s last message date" do
|
it "updates the last_message for the channel" do
|
||||||
previous_last_message_sent_at = public_chat_channel.last_message_sent_at
|
message =
|
||||||
|
described_class.create(
|
||||||
described_class.create(
|
chat_channel: public_chat_channel,
|
||||||
chat_channel: public_chat_channel,
|
user: user1,
|
||||||
user: user1,
|
content: "this is a message",
|
||||||
content: "this is a message",
|
).chat_message
|
||||||
)
|
expect(public_chat_channel.reload.last_message).to eq(message)
|
||||||
|
|
||||||
expect(previous_last_message_sent_at).to be < public_chat_channel.reload.last_message_sent_at
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sets the last_editor_id to the user who created the message" do
|
it "sets the last_editor_id to the user who created the message" do
|
||||||
@ -640,6 +638,18 @@ describe Chat::MessageCreator do
|
|||||||
expect(message.in_reply_to.thread).to eq(message.thread)
|
expect(message.in_reply_to.thread).to eq(message.thread)
|
||||||
expect(message.thread.original_message).to eq(reply_message)
|
expect(message.thread.original_message).to eq(reply_message)
|
||||||
expect(message.thread.original_message_user).to eq(reply_message.user)
|
expect(message.thread.original_message_user).to eq(reply_message.user)
|
||||||
|
expect(message.thread.last_message).to eq(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not change the last_message of the channel for a thread reply" do
|
||||||
|
original_last_message = public_chat_channel.last_message
|
||||||
|
described_class.create(
|
||||||
|
chat_channel: public_chat_channel,
|
||||||
|
user: user1,
|
||||||
|
content: "this is a message",
|
||||||
|
in_reply_to_id: reply_message.id,
|
||||||
|
)
|
||||||
|
expect(public_chat_channel.reload.last_message).to eq(original_last_message)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a user thread membership" do
|
it "creates a user thread membership" do
|
||||||
@ -756,6 +766,7 @@ describe Chat::MessageCreator do
|
|||||||
}.not_to change { Chat::Thread.count }
|
}.not_to change { Chat::Thread.count }
|
||||||
|
|
||||||
expect(message.reload.thread).to eq(existing_thread)
|
expect(message.reload.thread).to eq(existing_thread)
|
||||||
|
expect(existing_thread.reload.last_message).to eq(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a user thread membership if one does not exist" do
|
it "creates a user thread membership if one does not exist" do
|
||||||
|
@ -160,6 +160,7 @@ Fabricator(:chat_thread, class_name: "Chat::Thread") do
|
|||||||
transient :with_replies
|
transient :with_replies
|
||||||
transient :channel
|
transient :channel
|
||||||
transient :original_message_user
|
transient :original_message_user
|
||||||
|
transient :old_om
|
||||||
|
|
||||||
original_message do |attrs|
|
original_message do |attrs|
|
||||||
Fabricate(
|
Fabricate(
|
||||||
@ -170,7 +171,13 @@ Fabricator(:chat_thread, class_name: "Chat::Thread") do
|
|||||||
end
|
end
|
||||||
|
|
||||||
after_create do |thread, transients|
|
after_create do |thread, transients|
|
||||||
thread.original_message.update!(thread_id: thread.id)
|
attrs = { thread_id: thread.id }
|
||||||
|
|
||||||
|
# Sometimes we make this older via created_at so any messages fabricated for this thread
|
||||||
|
# afterwards are not created earlier in time than the OM.
|
||||||
|
attrs[:created_at] = 1.week.ago if transients[:old_om]
|
||||||
|
|
||||||
|
thread.original_message.update!(**attrs)
|
||||||
thread.add(thread.original_message_user)
|
thread.add(thread.original_message_user)
|
||||||
|
|
||||||
if transients[:with_replies]
|
if transients[:with_replies]
|
||||||
|
@ -46,7 +46,7 @@ RSpec.describe "Chat::Thread replies_count cache accuracy" do
|
|||||||
|
|
||||||
# Lose the cache intentionally.
|
# Lose the cache intentionally.
|
||||||
Chat::Thread.clear_caches!(thread.id)
|
Chat::Thread.clear_caches!(thread.id)
|
||||||
message_to_destroy = thread.last_reply
|
message_to_destroy = thread.last_message
|
||||||
Chat::TrashMessage.call(
|
Chat::TrashMessage.call(
|
||||||
message_id: message_to_destroy.id,
|
message_id: message_to_destroy.id,
|
||||||
channel_id: thread.channel_id,
|
channel_id: thread.channel_id,
|
||||||
|
@ -332,7 +332,7 @@ describe Chat::ChannelFetcher do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe ".secured_direct_message_channels" do
|
describe ".secured_direct_message_channels" do
|
||||||
it "includes direct message channels the user is a member of ordered by last_message_sent_at" do
|
it "includes direct message channels the user is a member of ordered by last_message.created_at" do
|
||||||
Fabricate(
|
Fabricate(
|
||||||
:user_chat_channel_membership_for_dm,
|
:user_chat_channel_membership_for_dm,
|
||||||
chat_channel: direct_message_channel1,
|
chat_channel: direct_message_channel1,
|
||||||
@ -350,8 +350,11 @@ describe Chat::ChannelFetcher do
|
|||||||
Chat::DirectMessageUser.create!(direct_message: dm_channel2, user: user1)
|
Chat::DirectMessageUser.create!(direct_message: dm_channel2, user: user1)
|
||||||
Chat::DirectMessageUser.create!(direct_message: dm_channel2, user: user2)
|
Chat::DirectMessageUser.create!(direct_message: dm_channel2, user: user2)
|
||||||
|
|
||||||
direct_message_channel1.update!(last_message_sent_at: 1.day.ago)
|
Fabricate(:chat_message, user: user1, chat_channel: direct_message_channel1)
|
||||||
direct_message_channel2.update!(last_message_sent_at: 1.hour.ago)
|
Fabricate(:chat_message, user: user1, chat_channel: direct_message_channel2)
|
||||||
|
|
||||||
|
direct_message_channel1.last_message.update!(created_at: 1.day.ago)
|
||||||
|
direct_message_channel2.last_message.update!(created_at: 1.hour.ago)
|
||||||
|
|
||||||
expect(described_class.secured_direct_message_channels(user1.id, guardian).map(&:id)).to eq(
|
expect(described_class.secured_direct_message_channels(user1.id, guardian).map(&:id)).to eq(
|
||||||
[direct_message_channel2.id, direct_message_channel1.id],
|
[direct_message_channel2.id, direct_message_channel1.id],
|
||||||
|
@ -186,4 +186,43 @@ RSpec.describe Chat::Channel do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#latest_not_deleted_message_id" do
|
||||||
|
fab!(:channel) { Fabricate(:category_channel) }
|
||||||
|
fab!(:old_message) { Fabricate(:chat_message, chat_channel: channel) }
|
||||||
|
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel) }
|
||||||
|
|
||||||
|
before { old_message.update!(created_at: 1.day.ago) }
|
||||||
|
|
||||||
|
it "accepts an anchor message to only get messages of a lower id" do
|
||||||
|
expect(channel.latest_not_deleted_message_id(anchor_message_id: message_1.id)).to eq(
|
||||||
|
old_message.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "gets the latest message by created_at" do
|
||||||
|
expect(channel.latest_not_deleted_message_id).to eq(message_1.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not get other channel messages" do
|
||||||
|
Fabricate(:chat_message)
|
||||||
|
expect(channel.latest_not_deleted_message_id).to eq(message_1.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not get thread replies" do
|
||||||
|
thread = Fabricate(:chat_thread, channel: channel, old_om: true)
|
||||||
|
message_1.update!(thread: thread)
|
||||||
|
expect(channel.latest_not_deleted_message_id).to eq(old_message.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does get thread original message" do
|
||||||
|
thread = Fabricate(:chat_thread, channel: channel)
|
||||||
|
expect(channel.latest_not_deleted_message_id).to eq(thread.original_message_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not get deleted messages" do
|
||||||
|
message_1.trash!
|
||||||
|
expect(channel.latest_not_deleted_message_id).to eq(old_message.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -213,4 +213,33 @@ RSpec.describe Chat::Thread do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#latest_not_deleted_message_id" do
|
||||||
|
fab!(:channel) { Fabricate(:category_channel) }
|
||||||
|
fab!(:thread) { Fabricate(:chat_thread, channel: channel, old_om: true) }
|
||||||
|
fab!(:old_message) { Fabricate(:chat_message, chat_channel: channel, thread: thread) }
|
||||||
|
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel, thread: thread) }
|
||||||
|
|
||||||
|
before { old_message.update!(created_at: 1.day.ago) }
|
||||||
|
|
||||||
|
it "accepts an anchor message to only get messages of a lower id" do
|
||||||
|
expect(thread.latest_not_deleted_message_id(anchor_message_id: message_1.id)).to eq(
|
||||||
|
old_message.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "gets the latest message by created_at" do
|
||||||
|
expect(thread.latest_not_deleted_message_id).to eq(message_1.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not get other channel messages" do
|
||||||
|
Fabricate(:chat_message)
|
||||||
|
expect(thread.latest_not_deleted_message_id).to eq(message_1.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not get deleted messages" do
|
||||||
|
message_1.trash!
|
||||||
|
expect(thread.latest_not_deleted_message_id).to eq(old_message.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -27,7 +27,9 @@ RSpec.describe Chat::Action::ResetUserLastReadChannelMessage do
|
|||||||
context "when there are non-deleted messages left in the channel" do
|
context "when there are non-deleted messages left in the channel" do
|
||||||
before do
|
before do
|
||||||
message_3.trash!
|
message_3.trash!
|
||||||
|
message_3.chat_channel.update_last_message_id!
|
||||||
message_6.trash!
|
message_6.trash!
|
||||||
|
message_6.chat_channel.update_last_message_id!
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sets the matching membership last_read_message_ids to the most recently created message ID" do
|
it "sets the matching membership last_read_message_ids to the most recently created message ID" do
|
||||||
@ -38,7 +40,11 @@ RSpec.describe Chat::Action::ResetUserLastReadChannelMessage do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "when there are no more non-deleted messages left in the channel" do
|
context "when there are no more non-deleted messages left in the channel" do
|
||||||
before { [message_1, message_2, message_4, message_5].each(&:trash!) }
|
before do
|
||||||
|
[message_1, message_2, message_4, message_5].each(&:trash!)
|
||||||
|
channel_1.update_last_message_id!
|
||||||
|
channel_2.update_last_message_id!
|
||||||
|
end
|
||||||
|
|
||||||
it "sets the matching membership last_read_message_ids to NULL" do
|
it "sets the matching membership last_read_message_ids to NULL" do
|
||||||
described_class.call([message_3.id, message_6.id], [channel_1.id, channel_2.id])
|
described_class.call([message_3.id, message_6.id], [channel_1.id, channel_2.id])
|
||||||
|
@ -186,15 +186,17 @@ RSpec.describe ::Chat::LookupChannelThreads do
|
|||||||
thread_4.membership_for(current_user).update!(
|
thread_4.membership_for(current_user).update!(
|
||||||
notification_level: ::Chat::UserChatThreadMembership.notification_levels[:muted],
|
notification_level: ::Chat::UserChatThreadMembership.notification_levels[:muted],
|
||||||
)
|
)
|
||||||
thread_5 = Fabricate(:chat_thread, channel: channel_1)
|
Fabricate(:chat_thread, channel: channel_1)
|
||||||
|
|
||||||
expect(result.threads.map(&:id)).to eq([thread_1.id, thread_2.id, thread_3.id])
|
expect(result.threads.map(&:id)).to eq([thread_1.id, thread_2.id, thread_3.id])
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not count deleted messages for sort order" do
|
it "does not count deleted messages for sort order" do
|
||||||
|
original_last_message_id = thread_3.reload.last_message_id
|
||||||
unread_message = Fabricate(:chat_message, chat_channel: channel_1, thread: thread_3)
|
unread_message = Fabricate(:chat_message, chat_channel: channel_1, thread: thread_3)
|
||||||
unread_message.update!(created_at: 2.days.ago)
|
unread_message.update!(created_at: 2.days.ago)
|
||||||
unread_message.trash!
|
unread_message.trash!
|
||||||
|
thread_3.reload.update!(last_message_id: original_last_message_id)
|
||||||
|
|
||||||
expect(result.threads.map(&:id)).to eq([thread_1.id, thread_2.id, thread_3.id])
|
expect(result.threads.map(&:id)).to eq([thread_1.id, thread_2.id, thread_3.id])
|
||||||
end
|
end
|
||||||
|
@ -109,6 +109,7 @@ RSpec.describe Chat::MarkAllUserChannelsRead do
|
|||||||
|
|
||||||
it "does not use deleted messages for the last_read_message_id" do
|
it "does not use deleted messages for the last_read_message_id" do
|
||||||
message_2.trash!
|
message_2.trash!
|
||||||
|
message_2.chat_channel.update_last_message_id!
|
||||||
result
|
result
|
||||||
expect(membership_1.reload.last_read_message_id).to eq(message_1.id)
|
expect(membership_1.reload.last_read_message_id).to eq(message_1.id)
|
||||||
end
|
end
|
||||||
|
@ -62,5 +62,11 @@ RSpec.describe Chat::MessageDestroyer do
|
|||||||
expect { message_1.reload }.to raise_exception(ActiveRecord::RecordNotFound)
|
expect { message_1.reload }.to raise_exception(ActiveRecord::RecordNotFound)
|
||||||
expect(message_2.reload).to be_present
|
expect(message_2.reload).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "sets the last_message_id for the channel if that message is deleted" do
|
||||||
|
expect(message_1.chat_channel.last_message_id).to eq(message_1.id)
|
||||||
|
described_class.new.destroy_in_batches(Chat::Message.where(id: message_1.id))
|
||||||
|
expect(message_1.chat_channel.reload.last_message_id).to eq(nil)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -281,10 +281,12 @@ describe Chat::Publisher do
|
|||||||
{
|
{
|
||||||
type: "channel",
|
type: "channel",
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
message_id: message_1.id,
|
|
||||||
user_id: message_1.user_id,
|
|
||||||
username: message_1.user.username,
|
|
||||||
thread_id: nil,
|
thread_id: nil,
|
||||||
|
message:
|
||||||
|
Chat::MessageSerializer.new(
|
||||||
|
message_1,
|
||||||
|
{ scope: Guardian.new(nil), root: false },
|
||||||
|
).as_json,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -340,10 +342,12 @@ describe Chat::Publisher do
|
|||||||
{
|
{
|
||||||
type: "thread",
|
type: "thread",
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
message_id: message_1.id,
|
|
||||||
user_id: message_1.user_id,
|
|
||||||
username: message_1.user.username,
|
|
||||||
thread_id: thread.id,
|
thread_id: thread.id,
|
||||||
|
message:
|
||||||
|
Chat::MessageSerializer.new(
|
||||||
|
message_1,
|
||||||
|
{ scope: Guardian.new(nil), root: false },
|
||||||
|
).as_json,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -5,7 +5,12 @@ RSpec.describe Chat::RestoreMessage do
|
|||||||
let!(:guardian) { Guardian.new(current_user) }
|
let!(:guardian) { Guardian.new(current_user) }
|
||||||
fab!(:message) { Fabricate(:chat_message, user: current_user) }
|
fab!(:message) { Fabricate(:chat_message, user: current_user) }
|
||||||
|
|
||||||
before { message.trash! }
|
before do
|
||||||
|
message.trash!
|
||||||
|
message.chat_channel.update!(
|
||||||
|
last_message_id: message.chat_channel.latest_not_deleted_message_id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
describe ".call" do
|
describe ".call" do
|
||||||
subject(:result) { described_class.call(params) }
|
subject(:result) { described_class.call(params) }
|
||||||
@ -45,6 +50,19 @@ RSpec.describe Chat::RestoreMessage do
|
|||||||
expect(Chat::Message.find_by(id: message.id)).not_to be_nil
|
expect(Chat::Message.find_by(id: message.id)).not_to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "updates the channel last_message_id if the message is now the last one in the channel" do
|
||||||
|
expect(message.chat_channel.reload.last_message_id).to be_nil
|
||||||
|
result
|
||||||
|
expect(message.chat_channel.reload.last_message_id).to eq(message.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not update the channel last_message_id if the message is not the last one in the channel" do
|
||||||
|
next_message = Fabricate(:chat_message, chat_channel: message.chat_channel)
|
||||||
|
expect(message.chat_channel.reload.last_message_id).to eq(next_message.id)
|
||||||
|
result
|
||||||
|
expect(message.chat_channel.reload.last_message_id).to eq(next_message.id)
|
||||||
|
end
|
||||||
|
|
||||||
it "publishes associated Discourse and MessageBus events" do
|
it "publishes associated Discourse and MessageBus events" do
|
||||||
freeze_time
|
freeze_time
|
||||||
messages = nil
|
messages = nil
|
||||||
@ -60,13 +78,30 @@ RSpec.describe Chat::RestoreMessage do
|
|||||||
context "when the message has a thread" do
|
context "when the message has a thread" do
|
||||||
fab!(:thread) { Fabricate(:chat_thread, channel: message.chat_channel) }
|
fab!(:thread) { Fabricate(:chat_thread, channel: message.chat_channel) }
|
||||||
|
|
||||||
before { message.update!(thread: thread) }
|
before do
|
||||||
|
message.update!(thread: thread)
|
||||||
|
thread.update_last_message_id!
|
||||||
|
thread.original_message.update!(created_at: message.created_at - 2.hours)
|
||||||
|
end
|
||||||
|
|
||||||
it "increments the thread reply count" do
|
it "increments the thread reply count" do
|
||||||
thread.set_replies_count_cache(1)
|
thread.set_replies_count_cache(1)
|
||||||
result
|
result
|
||||||
expect(thread.replies_count_cache).to eq(2)
|
expect(thread.replies_count_cache).to eq(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "updates the thread last_message_id if the message is now the last one in the thread" do
|
||||||
|
expect(message.thread.reload.last_message_id).to eq(thread.original_message_id)
|
||||||
|
result
|
||||||
|
expect(message.thread.reload.last_message_id).to eq(message.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not update the thread last_message_id if the message is not the last one in the channel" do
|
||||||
|
next_message = Fabricate(:chat_message, thread: message.thread)
|
||||||
|
expect(message.thread.reload.last_message_id).to eq(next_message.id)
|
||||||
|
result
|
||||||
|
expect(message.thread.reload.last_message_id).to eq(next_message.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -115,10 +115,23 @@ RSpec.describe Chat::TrashMessage do
|
|||||||
expect(membership_2.reload.last_read_message_id).to be_nil
|
expect(membership_2.reload.last_read_message_id).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "updates the channel last_message_id to the previous message in the channel" do
|
||||||
|
next_message =
|
||||||
|
Fabricate(:chat_message, chat_channel: message.chat_channel, user: current_user)
|
||||||
|
params[:message_id] = next_message.id
|
||||||
|
expect(message.chat_channel.reload.last_message).to eq(next_message)
|
||||||
|
result
|
||||||
|
expect(message.chat_channel.reload.last_message).to eq(message)
|
||||||
|
end
|
||||||
|
|
||||||
context "when the message has a thread" do
|
context "when the message has a thread" do
|
||||||
fab!(:thread) { Fabricate(:chat_thread, channel: message.chat_channel) }
|
fab!(:thread) { Fabricate(:chat_thread, channel: message.chat_channel) }
|
||||||
|
|
||||||
before { message.update!(thread: thread) }
|
before do
|
||||||
|
message.update!(thread: thread)
|
||||||
|
thread.update!(last_message: message)
|
||||||
|
thread.original_message.update!(created_at: message.created_at - 2.hours)
|
||||||
|
end
|
||||||
|
|
||||||
it "decrements the thread reply count" do
|
it "decrements the thread reply count" do
|
||||||
thread.set_replies_count_cache(5)
|
thread.set_replies_count_cache(5)
|
||||||
@ -154,6 +167,22 @@ RSpec.describe Chat::TrashMessage do
|
|||||||
expect(membership_1.reload.last_read_message_id).to be_nil
|
expect(membership_1.reload.last_read_message_id).to be_nil
|
||||||
expect(membership_2.reload.last_read_message_id).to be_nil
|
expect(membership_2.reload.last_read_message_id).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "updates the thread last_message_id to the previous message in the thread" do
|
||||||
|
next_message = Fabricate(:chat_message, thread: thread, user: current_user)
|
||||||
|
params[:message_id] = next_message.id
|
||||||
|
expect(thread.reload.last_message).to eq(next_message)
|
||||||
|
result
|
||||||
|
expect(thread.reload.last_message).to eq(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are no other messages left in the thread except the original message" do
|
||||||
|
it "updates the thread last_message_id to the original message" do
|
||||||
|
expect(thread.last_message).to eq(message)
|
||||||
|
result
|
||||||
|
expect(thread.reload.last_message).to eq(thread.original_message)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when message is already deleted" do
|
context "when message is already deleted" do
|
||||||
|
@ -11,7 +11,7 @@ RSpec.describe Chat::UpdateUserThreadLastRead do
|
|||||||
|
|
||||||
fab!(:current_user) { Fabricate(:user) }
|
fab!(:current_user) { Fabricate(:user) }
|
||||||
fab!(:channel) { Fabricate(:chat_channel) }
|
fab!(:channel) { Fabricate(:chat_channel) }
|
||||||
fab!(:thread) { Fabricate(:chat_thread, channel: channel) }
|
fab!(:thread) { Fabricate(:chat_thread, channel: channel, old_om: true) }
|
||||||
fab!(:thread_reply_1) { Fabricate(:chat_message, chat_channel: channel, thread: thread) }
|
fab!(:thread_reply_1) { Fabricate(:chat_message, chat_channel: channel, thread: thread) }
|
||||||
fab!(:thread_reply_2) { Fabricate(:chat_message, chat_channel: channel, thread: thread) }
|
fab!(:thread_reply_2) { Fabricate(:chat_message, chat_channel: channel, thread: thread) }
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ RSpec.describe Chat::UpdateUserThreadLastRead do
|
|||||||
|
|
||||||
it "updates the last_read_message_id of the thread" do
|
it "updates the last_read_message_id of the thread" do
|
||||||
result
|
result
|
||||||
expect(membership.reload.last_read_message_id).to eq(thread.last_reply.id)
|
expect(membership.reload.last_read_message_id).to eq(thread.reload.last_message.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"chatable_url": { "type": "string" },
|
"chatable_url": { "type": "string" },
|
||||||
"title": { "type": "string" },
|
"title": { "type": "string" },
|
||||||
"chatable_id": { "type": "number" },
|
"chatable_id": { "type": "number" },
|
||||||
"last_message_sent_at": { "type": "string" },
|
"last_message": { "type": ["object", "null"] },
|
||||||
"status": { "type": "string" },
|
"status": { "type": "string" },
|
||||||
"chatable": {
|
"chatable": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -79,10 +79,10 @@ describe "Channel thread message echoing", type: :system do
|
|||||||
current_user
|
current_user
|
||||||
.user_chat_channel_memberships
|
.user_chat_channel_memberships
|
||||||
.find_by(chat_channel: channel)
|
.find_by(chat_channel: channel)
|
||||||
.update!(last_read_message_id: thread.last_reply.id)
|
.update!(last_read_message_id: thread.last_message_id)
|
||||||
chat_page.visit_channel(channel)
|
chat_page.visit_channel(channel)
|
||||||
expect(channel_page).not_to have_css(
|
expect(channel_page).not_to have_css(
|
||||||
channel_page.message_by_id_selector(thread.last_reply.id),
|
channel_page.message_by_id_selector(thread.last_message_id),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -59,7 +59,10 @@ RSpec.describe "Chat | composer | shortcuts | thread", type: :system do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "when last message is deleted" do
|
context "when last message is deleted" do
|
||||||
before { last_thread_message.trash! }
|
before do
|
||||||
|
last_thread_message.trash!
|
||||||
|
thread_1.update_last_message_id!
|
||||||
|
end
|
||||||
|
|
||||||
it "does not edit a message" do
|
it "does not edit a message" do
|
||||||
chat_page.visit_thread(thread_1)
|
chat_page.visit_thread(thread_1)
|
||||||
|
@ -141,19 +141,19 @@ describe "Thread indicator for chat messages", type: :system do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "shows an excerpt of the last reply in the thread" do
|
it "shows an excerpt of the last reply in the thread" do
|
||||||
thread_1.last_reply.update!(message: "test for excerpt")
|
thread_1.last_message.update!(message: "test for excerpt")
|
||||||
thread_1.last_reply.rebake!
|
thread_1.last_message.rebake!
|
||||||
|
|
||||||
chat_page.visit_channel(channel)
|
chat_page.visit_channel(channel)
|
||||||
expect(
|
expect(
|
||||||
channel_page.message_thread_indicator(thread_1.original_message).excerpt,
|
channel_page.message_thread_indicator(thread_1.original_message).excerpt,
|
||||||
).to have_content(thread_excerpt(thread_1.last_reply))
|
).to have_content(thread_excerpt(thread_1.last_message))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "updates the last reply excerpt and participants when a new message is added to the thread" do
|
it "updates the last reply excerpt and participants when a new message is added to the thread" do
|
||||||
new_user = Fabricate(:user)
|
new_user = Fabricate(:user)
|
||||||
chat_system_user_bootstrap(user: new_user, channel: channel)
|
chat_system_user_bootstrap(user: new_user, channel: channel)
|
||||||
original_last_reply = thread_1.replies.last
|
original_last_reply = thread_1.last_message
|
||||||
original_last_reply.update!(message: "test for excerpt")
|
original_last_reply.update!(message: "test for excerpt")
|
||||||
original_last_reply.rebake!
|
original_last_reply.rebake!
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ describe "Single thread in side panel", type: :system do
|
|||||||
expect(side_panel).to have_open_thread(thread)
|
expect(side_panel).to have_open_thread(thread)
|
||||||
thread_page.send_message("new thread message")
|
thread_page.send_message("new thread message")
|
||||||
expect(thread_page).to have_message(thread_id: thread.id, text: "new thread message")
|
expect(thread_page).to have_message(thread_id: thread.id, text: "new thread message")
|
||||||
thread_message = thread.last_reply
|
thread_message = thread.last_message
|
||||||
expect(thread_message.chat_channel_id).to eq(channel.id)
|
expect(thread_message.chat_channel_id).to eq(channel.id)
|
||||||
expect(thread_message.thread.channel_id).to eq(channel.id)
|
expect(thread_message.thread.channel_id).to eq(channel.id)
|
||||||
end
|
end
|
||||||
|
@ -31,7 +31,7 @@ export const directMessageChannels = [
|
|||||||
following: true,
|
following: true,
|
||||||
},
|
},
|
||||||
allow_channel_wide_mentions: true,
|
allow_channel_wide_mentions: true,
|
||||||
last_message_sent_at: "2021-07-20T08:14:16.950Z",
|
last_message: { id: 333, created_at: "2021-07-02T08:14:16.950Z" },
|
||||||
message_bus_last_ids: {
|
message_bus_last_ids: {
|
||||||
new_mentions: 0,
|
new_mentions: 0,
|
||||||
new_messages: 0,
|
new_messages: 0,
|
||||||
@ -66,7 +66,7 @@ export const directMessageChannels = [
|
|||||||
following: true,
|
following: true,
|
||||||
},
|
},
|
||||||
allow_channel_wide_mentions: true,
|
allow_channel_wide_mentions: true,
|
||||||
last_message_sent_at: "2021-07-05T12:04:00.850Z",
|
last_message: { id: 333, created_at: "2021-07-02T08:14:16.950Z" },
|
||||||
message_bus_last_ids: {
|
message_bus_last_ids: {
|
||||||
new_mentions: 0,
|
new_mentions: 0,
|
||||||
new_messages: 0,
|
new_messages: 0,
|
||||||
@ -108,7 +108,7 @@ export const chatChannels = {
|
|||||||
status: "open",
|
status: "open",
|
||||||
chatable: chatables[1],
|
chatable: chatables[1],
|
||||||
allow_channel_wide_mentions: true,
|
allow_channel_wide_mentions: true,
|
||||||
last_message_sent_at: "2021-07-24T08:14:16.950Z",
|
last_message: { id: 333, created_at: "2021-07-02T08:14:16.950Z" },
|
||||||
current_user_membership: {
|
current_user_membership: {
|
||||||
muted: false,
|
muted: false,
|
||||||
following: true,
|
following: true,
|
||||||
@ -127,7 +127,7 @@ export const chatChannels = {
|
|||||||
status: "open",
|
status: "open",
|
||||||
chatable: chatables[1],
|
chatable: chatables[1],
|
||||||
allow_channel_wide_mentions: true,
|
allow_channel_wide_mentions: true,
|
||||||
last_message_sent_at: "2021-07-15T08:14:16.950Z",
|
last_message: { id: 333, created_at: "2021-07-02T08:14:16.950Z" },
|
||||||
current_user_membership: {
|
current_user_membership: {
|
||||||
muted: false,
|
muted: false,
|
||||||
following: true,
|
following: true,
|
||||||
@ -146,7 +146,7 @@ export const chatChannels = {
|
|||||||
status: "open",
|
status: "open",
|
||||||
chatable: chatables[8],
|
chatable: chatables[8],
|
||||||
allow_channel_wide_mentions: true,
|
allow_channel_wide_mentions: true,
|
||||||
last_message_sent_at: "2021-07-14T08:14:16.950Z",
|
last_message: { id: 333, created_at: "2021-07-02T08:14:16.950Z" },
|
||||||
current_user_membership: {
|
current_user_membership: {
|
||||||
muted: false,
|
muted: false,
|
||||||
following: true,
|
following: true,
|
||||||
@ -165,7 +165,7 @@ export const chatChannels = {
|
|||||||
status: "read_only",
|
status: "read_only",
|
||||||
chatable: chatables[8],
|
chatable: chatables[8],
|
||||||
allow_channel_wide_mentions: true,
|
allow_channel_wide_mentions: true,
|
||||||
last_message_sent_at: "2021-07-10T08:14:16.950Z",
|
last_message: { id: 333, created_at: "2021-07-02T08:14:16.950Z" },
|
||||||
current_user_membership: {
|
current_user_membership: {
|
||||||
muted: false,
|
muted: false,
|
||||||
following: true,
|
following: true,
|
||||||
@ -184,7 +184,7 @@ export const chatChannels = {
|
|||||||
status: "closed",
|
status: "closed",
|
||||||
chatable: chatables[8],
|
chatable: chatables[8],
|
||||||
allow_channel_wide_mentions: true,
|
allow_channel_wide_mentions: true,
|
||||||
last_message_sent_at: "2021-07-21T08:14:16.950Z",
|
last_message: { id: 333, created_at: "2021-07-02T08:14:16.950Z" },
|
||||||
current_user_membership: {
|
current_user_membership: {
|
||||||
muted: false,
|
muted: false,
|
||||||
following: true,
|
following: true,
|
||||||
@ -203,7 +203,7 @@ export const chatChannels = {
|
|||||||
status: "archived",
|
status: "archived",
|
||||||
chatable: chatables[8],
|
chatable: chatables[8],
|
||||||
allow_channel_wide_mentions: true,
|
allow_channel_wide_mentions: true,
|
||||||
last_message_sent_at: "2021-07-25T08:14:16.950Z",
|
last_message: { id: 333, created_at: "2021-07-02T08:14:16.950Z" },
|
||||||
current_user_membership: {
|
current_user_membership: {
|
||||||
muted: false,
|
muted: false,
|
||||||
following: true,
|
following: true,
|
||||||
@ -222,7 +222,7 @@ export const chatChannels = {
|
|||||||
status: "open",
|
status: "open",
|
||||||
chatable: chatables[12],
|
chatable: chatables[12],
|
||||||
allow_channel_wide_mentions: true,
|
allow_channel_wide_mentions: true,
|
||||||
last_message_sent_at: "2021-07-02T08:14:16.950Z",
|
last_message: { id: 333, created_at: "2021-07-02T08:14:16.950Z" },
|
||||||
current_user_membership: {
|
current_user_membership: {
|
||||||
muted: false,
|
muted: false,
|
||||||
following: true,
|
following: true,
|
||||||
|
@ -8,10 +8,12 @@ import { render } from "@ember/test-helpers";
|
|||||||
module("Discourse Chat | Component | chat-channel-metadata", function (hooks) {
|
module("Discourse Chat | Component | chat-channel-metadata", function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
test("displays last message sent at", async function (assert) {
|
test("displays last message created at", async function (assert) {
|
||||||
let lastMessageSentAt = moment().subtract(1, "day").format();
|
let lastMessageSentAt = moment().subtract(1, "day").format();
|
||||||
this.channel = fabricators.directMessageChannel({
|
this.channel = fabricators.directMessageChannel();
|
||||||
last_message_sent_at: lastMessageSentAt,
|
this.channel.lastMessage = fabricators.message({
|
||||||
|
channel: this.channel,
|
||||||
|
created_at: lastMessageSentAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<ChatChannelMetadata @channel={{this.channel}} />`);
|
await render(hbs`<ChatChannelMetadata @channel={{this.channel}} />`);
|
||||||
@ -19,7 +21,7 @@ module("Discourse Chat | Component | chat-channel-metadata", function (hooks) {
|
|||||||
assert.dom(".chat-channel-metadata__date").hasText("Yesterday");
|
assert.dom(".chat-channel-metadata__date").hasText("Yesterday");
|
||||||
|
|
||||||
lastMessageSentAt = moment();
|
lastMessageSentAt = moment();
|
||||||
this.channel.lastMessageSentAt = lastMessageSentAt;
|
this.channel.lastMessage.createdAt = lastMessageSentAt;
|
||||||
await render(hbs`<ChatChannelMetadata @channel={{this.channel}} />`);
|
await render(hbs`<ChatChannelMetadata @channel={{this.channel}} />`);
|
||||||
|
|
||||||
assert
|
assert
|
||||||
|
@ -47,13 +47,15 @@ module("Discourse Chat | Component | chat-channel-row", function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("renders correct channel metadata", async function (assert) {
|
test("renders correct channel metadata", async function (assert) {
|
||||||
this.categoryChatChannel.lastMessageSentAt = moment().toISOString();
|
this.categoryChatChannel.lastMessage = fabricators.message({
|
||||||
|
created_at: moment().toISOString(),
|
||||||
|
});
|
||||||
await render(hbs`<ChatChannelRow @channel={{this.categoryChatChannel}} />`);
|
await render(hbs`<ChatChannelRow @channel={{this.categoryChatChannel}} />`);
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom(".chat-channel-metadata")
|
.dom(".chat-channel-metadata")
|
||||||
.hasText(
|
.hasText(
|
||||||
moment(this.categoryChatChannel.lastMessageSentAt).format("h:mm A")
|
moment(this.categoryChatChannel.lastMessage.createdAt).format("h:mm A")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user