FIX: Improve chat membership update on deleted message (#21716)

Followup to c908eeacc96e947025176c1744ee4071ee252b91

Instead of using the latest message ID in the channel, which
could cause issues if you have an earlier last read message ID
that matches the deleted one, instead we use the first non-deleted
message that comes before the deleted message by ID.
This commit is contained in:
Martin Brennan
2023-05-24 10:31:15 +02:00
committed by GitHub
parent b2e3084205
commit 177d8dfcd1
5 changed files with 65 additions and 44 deletions

View File

@ -127,11 +127,12 @@ module Chat
SQL SQL
end end
def latest_not_deleted_message_id def latest_not_deleted_message_id(anchor_message_id: nil)
DB.query_single(<<~SQL, channel_id: self.id).first DB.query_single(<<~SQL, channel_id: self.id, anchor_message_id: anchor_message_id).first
SELECT id FROM chat_messages SELECT id FROM chat_messages
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" : ""}
ORDER BY created_at DESC, id DESC ORDER BY created_at DESC, id DESC
LIMIT 1 LIMIT 1
SQL SQL

View File

@ -52,15 +52,21 @@ module Chat
original_message.rich_excerpt(max_length: EXCERPT_LENGTH) original_message.rich_excerpt(max_length: EXCERPT_LENGTH)
end end
def latest_not_deleted_message_id def latest_not_deleted_message_id(anchor_message_id: nil)
DB.query_single(<<~SQL, channel_id: self.channel_id, thread_id: self.id).first DB.query_single(
<<~SQL,
SELECT id FROM chat_messages SELECT id FROM chat_messages
WHERE chat_channel_id = :channel_id WHERE chat_channel_id = :channel_id
AND thread_id = :thread_id AND thread_id = :thread_id
AND deleted_at IS NULL AND deleted_at IS NULL
#{anchor_message_id ? "AND id < :anchor_message_id" : ""}
ORDER BY created_at DESC, id DESC ORDER BY created_at DESC, id DESC
LIMIT 1 LIMIT 1
SQL SQL
channel_id: self.channel_id,
thread_id: self.id,
anchor_message_id: anchor_message_id,
).first
end end
def self.grouped_messages(thread_ids: nil, message_ids: nil, include_original_message: true) def self.grouped_messages(thread_ids: nil, message_ids: nil, include_original_message: true)

View File

@ -140,9 +140,9 @@ module Chat
latest_not_deleted_message_id = latest_not_deleted_message_id =
if chat_message.thread_reply? && chat_channel.threading_enabled && if chat_message.thread_reply? && chat_channel.threading_enabled &&
SiteSetting.enable_experimental_chat_threaded_discussions SiteSetting.enable_experimental_chat_threaded_discussions
chat_message.thread.latest_not_deleted_message_id chat_message.thread.latest_not_deleted_message_id(anchor_message_id: chat_message.id)
else else
chat_channel.latest_not_deleted_message_id chat_channel.latest_not_deleted_message_id(anchor_message_id: chat_message.id)
end end
publish_to_targets!( publish_to_targets!(
message_bus_targets, message_bus_targets,

View File

@ -4,36 +4,47 @@ require "rails_helper"
describe Chat::Publisher do describe Chat::Publisher do
fab!(:channel) { Fabricate(:category_channel) } fab!(:channel) { Fabricate(:category_channel) }
fab!(:message) { Fabricate(:chat_message, chat_channel: channel) } fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel) }
describe ".publish_delete!" do describe ".publish_delete!" do
fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel) } fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel) }
before { message.trash! } before { message_2.trash! }
it "publishes the correct data" do it "publishes the correct data" do
data = MessageBus.track_publish { described_class.publish_delete!(channel, message) }[0].data data =
MessageBus.track_publish { described_class.publish_delete!(channel, message_2) }[0].data
expect(data["deleted_at"]).to eq(message.deleted_at.iso8601(3)) expect(data["deleted_at"]).to eq(message_2.deleted_at.iso8601(3))
expect(data["deleted_id"]).to eq(message.id) expect(data["deleted_id"]).to eq(message_2.id)
expect(data["latest_not_deleted_message_id"]).to eq(message_2.id) expect(data["latest_not_deleted_message_id"]).to eq(message_1.id)
expect(data["type"]).to eq("delete") expect(data["type"]).to eq("delete")
end end
context "when there are no earlier messages in the channel to send as latest_not_deleted_message_id" do
it "publishes nil" do
data =
MessageBus.track_publish { described_class.publish_delete!(channel, message_1) }[0].data
expect(data["latest_not_deleted_message_id"]).to eq(nil)
end
end
context "when the message is in a thread and the channel has threading_enabled" do context "when the message is in a thread and the channel has threading_enabled" do
before do before do
SiteSetting.enable_experimental_chat_threaded_discussions = true SiteSetting.enable_experimental_chat_threaded_discussions = true
thread = Fabricate(:chat_thread, channel: channel) thread = Fabricate(:chat_thread, channel: channel)
message.update!(thread: thread) message_1.update!(thread: thread)
message_2.update!(thread: thread)
channel.update!(threading_enabled: true) channel.update!(threading_enabled: true)
end end
it "publishes the correct latest not deleted message id" do it "publishes the correct latest not deleted message id" do
data = data =
MessageBus.track_publish { described_class.publish_delete!(channel, message) }[0].data MessageBus.track_publish { described_class.publish_delete!(channel, message_2) }[0].data
expect(data["deleted_at"]).to eq(message.deleted_at.iso8601(3)) expect(data["deleted_at"]).to eq(message_2.deleted_at.iso8601(3))
expect(data["deleted_id"]).to eq(message.id) expect(data["deleted_id"]).to eq(message_2.id)
expect(data["latest_not_deleted_message_id"]).to eq(message.thread.original_message_id) expect(data["latest_not_deleted_message_id"]).to eq(message_1.id)
expect(data["type"]).to eq("delete") expect(data["type"]).to eq("delete")
end end
end end
@ -41,9 +52,10 @@ describe Chat::Publisher do
describe ".publish_refresh!" do describe ".publish_refresh!" do
it "publishes the message" do it "publishes the message" do
data = MessageBus.track_publish { described_class.publish_refresh!(channel, message) }[0].data data =
MessageBus.track_publish { described_class.publish_refresh!(channel, message_1) }[0].data
expect(data["chat_message"]["id"]).to eq(message.id) expect(data["chat_message"]["id"]).to eq(message_1.id)
expect(data["type"]).to eq("refresh") expect(data["type"]).to eq("refresh")
end end
end end
@ -53,10 +65,10 @@ describe Chat::Publisher do
before { SiteSetting.enable_experimental_chat_threaded_discussions = false } before { SiteSetting.enable_experimental_chat_threaded_discussions = false }
context "when the message is the original message of a thread" do context "when the message is the original message of a thread" do
fab!(:thread) { Fabricate(:chat_thread, original_message: message, channel: channel) } fab!(:thread) { Fabricate(:chat_thread, original_message: message_1, channel: channel) }
it "generates the correct targets" do it "generates the correct targets" do
targets = described_class.calculate_publish_targets(channel, message) targets = described_class.calculate_publish_targets(channel, message_1)
expect(targets).to contain_exactly("/chat/#{channel.id}") expect(targets).to contain_exactly("/chat/#{channel.id}")
end end
end end
@ -70,17 +82,17 @@ describe Chat::Publisher do
) )
end end
before { message.update!(thread: thread) } before { message_1.update!(thread: thread) }
it "generates the correct targets" do it "generates the correct targets" do
targets = described_class.calculate_publish_targets(channel, message) targets = described_class.calculate_publish_targets(channel, message_1)
expect(targets).to contain_exactly("/chat/#{channel.id}") expect(targets).to contain_exactly("/chat/#{channel.id}")
end end
end end
context "when the message is not part of a thread" do context "when the message is not part of a thread" do
it "generates the correct targets" do it "generates the correct targets" do
targets = described_class.calculate_publish_targets(channel, message) targets = described_class.calculate_publish_targets(channel, message_1)
expect(targets).to contain_exactly("/chat/#{channel.id}") expect(targets).to contain_exactly("/chat/#{channel.id}")
end end
end end
@ -93,10 +105,10 @@ describe Chat::Publisher do
end end
context "when the message is the original message of a thread" do context "when the message is the original message of a thread" do
fab!(:thread) { Fabricate(:chat_thread, original_message: message, channel: channel) } fab!(:thread) { Fabricate(:chat_thread, original_message: message_1, channel: channel) }
it "generates the correct targets" do it "generates the correct targets" do
targets = described_class.calculate_publish_targets(channel, message) targets = described_class.calculate_publish_targets(channel, message_1)
expect(targets).to contain_exactly("/chat/#{channel.id}") expect(targets).to contain_exactly("/chat/#{channel.id}")
end end
end end
@ -110,17 +122,17 @@ describe Chat::Publisher do
) )
end end
before { message.update!(thread: thread) } before { message_1.update!(thread: thread) }
it "generates the correct targets" do it "generates the correct targets" do
targets = described_class.calculate_publish_targets(channel, message) targets = described_class.calculate_publish_targets(channel, message_1)
expect(targets).to contain_exactly("/chat/#{channel.id}") expect(targets).to contain_exactly("/chat/#{channel.id}")
end end
end end
context "when the message is not part of a thread" do context "when the message is not part of a thread" do
it "generates the correct targets" do it "generates the correct targets" do
targets = described_class.calculate_publish_targets(channel, message) targets = described_class.calculate_publish_targets(channel, message_1)
expect(targets).to contain_exactly("/chat/#{channel.id}") expect(targets).to contain_exactly("/chat/#{channel.id}")
end end
end end
@ -133,10 +145,10 @@ describe Chat::Publisher do
end end
context "when the message is the original message of a thread" do context "when the message is the original message of a thread" do
fab!(:thread) { Fabricate(:chat_thread, original_message: message, channel: channel) } fab!(:thread) { Fabricate(:chat_thread, original_message: message_1, channel: channel) }
it "generates the correct targets" do it "generates the correct targets" do
targets = described_class.calculate_publish_targets(channel, message) targets = described_class.calculate_publish_targets(channel, message_1)
expect(targets).to contain_exactly( expect(targets).to contain_exactly(
"/chat/#{channel.id}", "/chat/#{channel.id}",
"/chat/#{channel.id}/thread/#{thread.id}", "/chat/#{channel.id}/thread/#{thread.id}",
@ -153,13 +165,13 @@ describe Chat::Publisher do
) )
end end
before { message.update!(thread: thread) } before { message_1.update!(thread: thread) }
it "generates the correct targets" do it "generates the correct targets" do
targets = targets =
described_class.calculate_publish_targets( described_class.calculate_publish_targets(
channel, channel,
message, message_1,
staged_thread_id: "stagedthreadid", staged_thread_id: "stagedthreadid",
) )
@ -179,17 +191,17 @@ describe Chat::Publisher do
) )
end end
before { message.update!(thread: thread) } before { message_1.update!(thread: thread) }
it "generates the correct targets" do it "generates the correct targets" do
targets = described_class.calculate_publish_targets(channel, message) targets = described_class.calculate_publish_targets(channel, message_1)
expect(targets).to contain_exactly("/chat/#{channel.id}/thread/#{thread.id}") expect(targets).to contain_exactly("/chat/#{channel.id}/thread/#{thread.id}")
end end
end end
context "when the message is not part of a thread" do context "when the message is not part of a thread" do
it "generates the correct targets" do it "generates the correct targets" do
targets = described_class.calculate_publish_targets(channel, message) targets = described_class.calculate_publish_targets(channel, message_1)
expect(targets).to contain_exactly("/chat/#{channel.id}") expect(targets).to contain_exactly("/chat/#{channel.id}")
end end
end end
@ -203,14 +215,14 @@ describe Chat::Publisher do
it "publishes to the new_messages_message_bus_channel" do it "publishes to the new_messages_message_bus_channel" do
messages = messages =
MessageBus.track_publish(described_class.new_messages_message_bus_channel(channel.id)) do MessageBus.track_publish(described_class.new_messages_message_bus_channel(channel.id)) do
described_class.publish_new!(channel, message, staged_id) described_class.publish_new!(channel, message_1, staged_id)
end end
expect(messages.first.data).to eq( expect(messages.first.data).to eq(
{ {
channel_id: channel.id, channel_id: channel.id,
message_id: message.id, message_id: message_1.id,
user_id: message.user_id, user_id: message_1.user_id,
username: message.user.username, username: message_1.user.username,
thread_id: nil, thread_id: nil,
}, },
) )
@ -226,7 +238,7 @@ describe Chat::Publisher do
) )
end end
before { message.update!(thread: thread) } before { message_1.update!(thread: thread) }
context "if enable_experimental_chat_threaded_discussions is false" do context "if enable_experimental_chat_threaded_discussions is false" do
before { SiteSetting.enable_experimental_chat_threaded_discussions = false } before { SiteSetting.enable_experimental_chat_threaded_discussions = false }
@ -235,7 +247,7 @@ describe Chat::Publisher do
messages = messages =
MessageBus.track_publish( MessageBus.track_publish(
described_class.new_messages_message_bus_channel(channel.id), described_class.new_messages_message_bus_channel(channel.id),
) { described_class.publish_new!(channel, message, staged_id) } ) { described_class.publish_new!(channel, message_1, staged_id) }
expect(messages).not_to be_empty expect(messages).not_to be_empty
end end
end end
@ -250,7 +262,7 @@ describe Chat::Publisher do
messages = messages =
MessageBus.track_publish( MessageBus.track_publish(
described_class.new_messages_message_bus_channel(channel.id), described_class.new_messages_message_bus_channel(channel.id),
) { described_class.publish_new!(channel, message, staged_id) } ) { described_class.publish_new!(channel, message_1, staged_id) }
expect(messages).not_to be_empty expect(messages).not_to be_empty
end end
end end
@ -262,7 +274,7 @@ describe Chat::Publisher do
messages = messages =
MessageBus.track_publish( MessageBus.track_publish(
described_class.new_messages_message_bus_channel(channel.id), described_class.new_messages_message_bus_channel(channel.id),
) { described_class.publish_new!(channel, message, staged_id) } ) { described_class.publish_new!(channel, message_1, staged_id) }
expect(messages).to be_empty expect(messages).to be_empty
end end
end end

View File

@ -42,6 +42,8 @@ RSpec.describe "Deleted message", type: :system, js: true do
channel_page.delete_message(message) channel_page.delete_message(message)
expect(channel_page).to have_deleted_message(message, count: 1) expect(channel_page).to have_deleted_message(message, count: 1)
sidebar_component.click_link(channel_2.name) sidebar_component.click_link(channel_2.name)
expect(channel_page).to have_no_loading_skeleton
sidebar_component.click_link(channel_1.name) sidebar_component.click_link(channel_1.name)
expect(channel_page).to have_deleted_message(message, count: 1) expect(channel_page).to have_deleted_message(message, count: 1)
end end