FIX: show chat thread notifications for direct message channels (#29414)

When adding threads to DM channels in #29170 we intentionally didn't add them to the My Threads section. However this makes it easy to miss notifications as we don't get the new thread badge on the sidebar and footer tabs (drawer/mobile). However they were also missing from the chat header and sidebar too, which is fixed with this PR.

When a new thread or a reply to an existing thread is created within a DM channel (either 1:1 or group), we now show the standard badges like we do for public channels.

We now also show the green dot in the sidebar for My Threads and public channels when they contain an unread watched thread.
This commit is contained in:
David Battersby 2024-10-31 10:50:11 +04:00 committed by GitHub
parent 1a3b9a7352
commit ec480e99ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 89 additions and 44 deletions

View File

@ -46,7 +46,7 @@ module Chat
INNER JOIN user_chat_thread_memberships ON user_chat_thread_memberships.thread_id = chat_threads.id
INNER JOIN user_chat_channel_memberships ON user_chat_channel_memberships.chat_channel_id = chat_messages.chat_channel_id
INNER JOIN chat_messages AS original_message ON original_message.id = chat_threads.original_message_id
AND chat_messages.thread_id = memberships.thread_id
WHERE chat_messages.thread_id = memberships.thread_id
AND chat_messages.user_id != :user_id
AND user_chat_thread_memberships.user_id = :user_id
AND chat_messages.id > COALESCE(user_chat_thread_memberships.last_read_message_id, 0)

View File

@ -24,9 +24,11 @@ module Chat
end
def inject_unread_thread_overview(structured:, guardian:)
channel_ids =
structured[:public_channels].map(&:id) + structured[:direct_message_channels].map(&:id)
structured[:unread_thread_overview] = ::Chat::TrackingStateReportQuery.call(
guardian: guardian,
channel_ids: structured[:public_channels].map(&:id),
channel_ids: channel_ids,
include_threads: true,
include_read: false,
include_last_reply_details: true,

View File

@ -77,13 +77,7 @@ module Chat
::Chat::Channel
.joins(:user_chat_channel_memberships)
.where(user_chat_channel_memberships: { user_id: guardian.user.id, following: true })
.where(
{
chatable_type: "Category",
threading_enabled: true,
status: ::Chat::Channel.statuses[:open],
},
)
.where({ threading_enabled: true, status: ::Chat::Channel.statuses[:open] })
.select(:id),
)
.where("original_message.chat_channel_id = chat_threads.channel_id")

View File

@ -13,7 +13,8 @@ export default class ChatChannelUnreadIndicator extends Component {
this.args.channel.tracking.unreadCount > 0 ||
// We want to do this so we don't show a blue dot if the user is inside
// the channel and a new unread thread comes in.
(this.chat.activeChannel?.id !== this.args.channel.id &&
(this.args.channel.isCategoryChannel &&
this.chat.activeChannel?.id !== this.args.channel.id &&
this.args.channel.unreadThreadsCountSinceLastViewed > 0)
);
}

View File

@ -63,7 +63,6 @@ export default {
prefixType = "icon";
prefixValue = "discourse-threads";
suffixType = "icon";
suffixCSSClass = "unread";
constructor() {
super(...arguments);
@ -74,12 +73,20 @@ export default {
}
get suffixValue() {
return chatChannelsManager.publicMessageChannels.some(
return chatChannelsManager.allChannels.some(
(channel) => channel.unreadThreadsCount > 0
)
? "circle"
: "";
}
get suffixCSSClass() {
return chatChannelsManager.allChannels.some(
(channel) => channel.tracking.watchedThreadsUnreadCount > 0
)
? "urgent"
: "unread";
}
};
const SidebarChatMyThreadsSection = class extends BaseCustomSidebarSection {
@ -196,7 +203,8 @@ export default {
}
get suffixCSSClass() {
return this.channel.tracking.mentionCount > 0
return this.channel.tracking.mentionCount > 0 ||
this.channel.tracking.watchedThreadsUnreadCount > 0
? "urgent"
: "unread";
}

View File

@ -111,7 +111,7 @@ export default class ChatChannel {
}
get unreadThreadsCount() {
return Array.from(this.threadsManager.unreadThreadOverview.values()).length;
return this.threadsManager.unreadThreadCount;
}
get watchedThreadsUnreadCount() {

View File

@ -108,9 +108,7 @@ export default class ChatChannelsManager extends Service {
@cached
get hasThreadedChannels() {
return this.publicMessageChannels?.some(
(channel) => channel.threadingEnabled
);
return this.allChannels?.some((channel) => channel.threadingEnabled);
}
get allChannels() {

View File

@ -78,13 +78,11 @@ export default class ChatTrackingStateManager extends Service {
}
get hasUnreadThreads() {
return this.#publicChannels.some(
(channel) => channel.unreadThreadsCount > 0
);
return this.#allChannels.some((channel) => channel.unreadThreadsCount > 0);
}
get watchedThreadsUnreadCount() {
return this.#publicChannels.reduce((unreadCount, channel) => {
return this.#allChannels.reduce((unreadCount, channel) => {
return unreadCount + channel.tracking.watchedThreadsUnreadCount;
}, 0);
}
@ -129,4 +127,8 @@ export default class ChatTrackingStateManager extends Service {
get #directMessageChannels() {
return this.chatChannelsManager.directMessageChannels;
}
get #allChannels() {
return this.chatChannelsManager.allChannels;
}
}

View File

@ -11,8 +11,11 @@ RSpec.describe Chat::CreateThread do
subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) }
fab!(:another_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel, threading_enabled: true) }
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1) }
fab!(:dm_channel) { Fabricate(:direct_message_channel, users: [current_user, another_user]) }
fab!(:dm_message) { Fabricate(:chat_message, chat_channel: dm_channel) }
let(:guardian) { Guardian.new(current_user) }
let(:title) { nil }
@ -39,11 +42,20 @@ RSpec.describe Chat::CreateThread do
expect(result.membership).to eq(result.thread.membership_for(current_user))
end
it "publishes a `thread_created` MessageBus event" do
it "publishes a `thread_created` MessageBus event for public channels" do
message = MessageBus.track_publish("/chat/#{channel_1.id}") { result }.first
expect(message.data["type"]).to eq("thread_created")
end
it "publishes a `thread_created` MessageBus event for DM channels" do
params[:channel_id] = dm_channel.id
params[:original_message_id] = dm_message.id
params[:guardian] = Guardian.new(another_user)
message = MessageBus.track_publish("/chat/#{dm_channel.id}") { result }.first
expect(message.data["type"]).to eq("thread_created")
end
it "triggers a discourse event `chat_thread_created`" do
event = DiscourseEvent.track_events(:chat_thread_created) { result }.first

View File

@ -133,36 +133,64 @@ RSpec.describe "Mobile Chat footer", type: :system, mobile: true do
end
context "for my threads" do
fab!(:thread) { Fabricate(:chat_thread, channel: channel, original_message: message) }
fab!(:thread_message) { Fabricate(:chat_message, chat_channel: channel, thread: thread) }
context "with public channels" do
fab!(:thread) { Fabricate(:chat_thread, channel: channel, original_message: message) }
fab!(:thread_message) { Fabricate(:chat_message, chat_channel: channel, thread: thread) }
before { SiteSetting.chat_threads_enabled = true }
before { SiteSetting.chat_threads_enabled = true }
it "is unread" do
visit("/")
chat_page.open_from_header
it "is unread" do
visit("/")
chat_page.open_from_header
expect(page).to have_css("#c-footer-threads .c-unread-indicator")
expect(page).to have_css("#c-footer-threads .c-unread-indicator")
end
it "is not unread when thread is from a muted channel" do
channel.membership_for(current_user).update!(muted: true)
visit("/")
chat_page.open_from_header
expect(page).to have_no_css("#c-footer-threads .c-unread-indicator")
end
it "is urgent for watched thread messages" do
thread.membership_for(current_user).update!(
notification_level: ::Chat::NotificationLevels.all[:watching],
)
visit("/")
chat_page.open_from_header
expect(page).to have_css("#c-footer-threads .c-unread-indicator.-urgent")
end
end
it "is not unread when thread is from a muted channel" do
channel.membership_for(current_user).update!(muted: true)
context "with direct messages" do
fab!(:dm_channel) do
Fabricate(
:direct_message_channel,
threading_enabled: true,
users: [current_user, other_user],
)
end
fab!(:dm_message) { Fabricate(:chat_message, chat_channel: dm_channel, user: current_user) }
fab!(:dm_thread) do
Fabricate(:chat_thread, channel: dm_channel, original_message: dm_message)
end
fab!(:dm_thread_message) do
Fabricate(:chat_message, chat_channel: dm_channel, thread: dm_thread, user: other_user)
end
visit("/")
chat_page.open_from_header
before { SiteSetting.chat_threads_enabled = true }
expect(page).to have_no_css("#c-footer-threads .c-unread-indicator")
end
it "is unread" do
visit("/")
chat_page.open_from_header
it "is urgent for watched thread messages" do
thread.membership_for(current_user).update!(
notification_level: ::Chat::NotificationLevels.all[:watching],
)
visit("/")
chat_page.open_from_header
expect(page).to have_css("#c-footer-threads .c-unread-indicator.-urgent")
expect(page).to have_css("#c-footer-threads .c-unread-indicator")
end
end
end
end