FIX: improve chat channel sorting for DMs (#30124)

This change sorts unread channels in descending order based on last message date, so channels with the latest activity will always appear at the top. It also adds some improvements for sorting channels with unread threads, now when multiple channels have unread threads, they will be sorted by last thread reply date to ensure more active channels rise to the top.

For DM channels, the order is now:

- Urgent (green badge) - unread messages, mentions and unread watched threads (most recent activity at top)
- Unread (blue badge) - unread tracked threads (most recent thread reply at top)
- Everything else (most recent message at top)
This commit is contained in:
David Battersby 2024-12-05 13:33:55 +04:00 committed by GitHub
parent 1ca90c3070
commit b3c94839ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 106 additions and 44 deletions

View File

@ -3,7 +3,11 @@ import { i18n } from "discourse-i18n";
export default class ChatChannelMetadata extends Component { export default class ChatChannelMetadata extends Component {
get lastMessageFormattedDate() { get lastMessageFormattedDate() {
return moment(this.args.channel.lastMessage.createdAt).calendar(null, { const lastMessageDate = this.showThreadUnreadDate
? this.args.channel.lastUnreadThreadDate
: this.args.channel.lastMessage.createdAt;
return moment(lastMessageDate).calendar(null, {
sameDay: "LT", sameDay: "LT",
lastDay: `[${i18n("chat.dates.yesterday")}]`, lastDay: `[${i18n("chat.dates.yesterday")}]`,
lastWeek: "dddd", lastWeek: "dddd",
@ -11,6 +15,13 @@ export default class ChatChannelMetadata extends Component {
}); });
} }
get showThreadUnreadDate() {
return (
this.args.channel.lastUnreadThreadDate >
this.args.channel.lastMessage.createdAt
);
}
<template> <template>
<div class="chat-channel__metadata"> <div class="chat-channel__metadata">
{{#if @channel.lastMessage}} {{#if @channel.lastMessage}}

View File

@ -119,6 +119,16 @@ export default class ChatChannel {
return this.threadsManager.unreadThreadCount; return this.threadsManager.unreadThreadCount;
} }
get lastUnreadThreadDate() {
if (this.unreadThreadsCount === 0) {
return this.lastMessage.createdAt;
}
return Array.from(this.threadsManager.unreadThreadOverview.values())
.sort((a, b) => b - a)
.pop();
}
get watchedThreadsUnreadCount() { get watchedThreadsUnreadCount() {
return this.threadsManager.threads.reduce((unreadCount, thread) => { return this.threadsManager.threads.reduce((unreadCount, thread) => {
return unreadCount + thread.tracking.watchedThreadsUnreadCount; return unreadCount + thread.tracking.watchedThreadsUnreadCount;

View File

@ -263,20 +263,33 @@ export default class ChatChannelsManager extends Service {
b.tracking.mentionCount + b.tracking.mentionCount +
b.tracking.watchedThreadsUnreadCount; b.tracking.watchedThreadsUnreadCount;
if (aUrgent > 0 || bUrgent > 0) { const aUnread = a.unreadThreadsCountSinceLastViewed;
return aUrgent > bUrgent ? -1 : 1; const bUnread = b.unreadThreadsCountSinceLastViewed;
}
if ( // if both channels have urgent count, sort by last message date
a.unreadThreadsCountSinceLastViewed > 0 || if (aUrgent > 0 && bUrgent > 0) {
b.unreadThreadsCountSinceLastViewed > 0 return new Date(a.lastMessage.createdAt) >
) { new Date(b.lastMessage.createdAt)
return a.unreadThreadsCountSinceLastViewed >
b.unreadThreadsCountSinceLastViewed
? -1 ? -1
: 1; : 1;
} }
// otherwise prioritize channel with urgent count
if (aUrgent > 0 || bUrgent > 0) {
return aUrgent > bUrgent ? -1 : 1;
}
// if both channels have unread threads, sort by last thread reply date
if (aUnread > 0 && bUnread > 0) {
return a.lastUnreadThreadDate > b.lastUnreadThreadDate ? -1 : 1;
}
// otherwise prioritize channel with unread thread count
if (aUnread > 0 || bUnread > 0) {
return aUnread > bUnread ? -1 : 1;
}
// read channels are sorted by last message date
return new Date(a.lastMessage.createdAt) > return new Date(a.lastMessage.createdAt) >
new Date(b.lastMessage.createdAt) new Date(b.lastMessage.createdAt)
? -1 ? -1

View File

@ -145,45 +145,73 @@ RSpec.describe "List channels | Drawer", type: :system do
expect(drawer_page).to have_channel_at_position(dm_channel_3, 4) expect(drawer_page).to have_channel_at_position(dm_channel_3, 4)
end end
it "sorts channels with threads by urgency" do context "with unread threads" do
drawer_page.visit_index fab!(:message_1) do
drawer_page.click_direct_messages Fabricate(
:chat_message,
chat_channel: dm_channel_3,
user: current_user,
use_service: true,
)
end
fab!(:thread_1) do
Fabricate(
:chat_thread,
channel: dm_channel_3,
original_message: message_1,
use_service: true,
)
end
fab!(:message_2) do
Fabricate(
:chat_message,
chat_channel: dm_channel_4,
user: current_user,
use_service: true,
)
end
fab!(:thread_2) do
Fabricate(
:chat_thread,
channel: dm_channel_4,
original_message: message_2,
use_service: true,
)
end
Fabricate( before do
:chat_thread, dm_channel_3.membership_for(current_user).mark_read!(message_1.id)
notification_level: :watching, dm_channel_4.membership_for(current_user).mark_read!(message_2.id)
original_message:
Fabricate(
:chat_message,
chat_channel: dm_channel_4,
user: current_user,
use_service: true,
),
with_replies: 2,
use_service: true,
)
Fabricate( drawer_page.visit_index
:chat_thread, drawer_page.click_direct_messages
original_message: end
Fabricate(
:chat_message,
chat_channel: dm_channel_3,
user: current_user,
use_service: true,
),
with_replies: 2,
use_service: true,
)
expect(drawer_page).to have_channel_at_position(dm_channel_4, 1) it "sorts channels with unread threads by last reply" do
expect(drawer_page).to have_urgent_channel(dm_channel_4) Fabricate(:chat_message, thread: thread_1, user: user_2, use_service: true)
Fabricate(:chat_message, thread: thread_2, user: user_3, use_service: true)
expect(drawer_page).to have_channel_at_position(dm_channel_3, 2) expect(drawer_page).to have_channel_at_position(dm_channel_4, 1)
expect(drawer_page).to have_unread_channel(dm_channel_3) expect(drawer_page).to have_unread_channel(dm_channel_4)
expect(drawer_page).to have_channel_at_position(dm_channel_1, 3) expect(drawer_page).to have_channel_at_position(dm_channel_3, 2)
expect(drawer_page).to have_channel_at_position(dm_channel_2, 4) expect(drawer_page).to have_unread_channel(dm_channel_3)
end
it "sorts channels with unread threads by importance" do
thread_1.membership_for(current_user).update!(
notification_level: ::Chat::NotificationLevels.all[:watching],
)
Fabricate(:chat_message, thread: thread_1, user: user_2, use_service: true)
Fabricate(:chat_message, thread: thread_2, user: user_3, use_service: true)
expect(drawer_page).to have_channel_at_position(dm_channel_3, 1)
expect(drawer_page).to have_urgent_channel(dm_channel_3)
expect(drawer_page).to have_channel_at_position(dm_channel_4, 2)
expect(drawer_page).to have_unread_channel(dm_channel_4)
end
end end
end end
end end