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 {
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",
lastDay: `[${i18n("chat.dates.yesterday")}]`,
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>
<div class="chat-channel__metadata">
{{#if @channel.lastMessage}}

View File

@ -119,6 +119,16 @@ export default class ChatChannel {
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() {
return this.threadsManager.threads.reduce((unreadCount, thread) => {
return unreadCount + thread.tracking.watchedThreadsUnreadCount;

View File

@ -263,20 +263,33 @@ export default class ChatChannelsManager extends Service {
b.tracking.mentionCount +
b.tracking.watchedThreadsUnreadCount;
if (aUrgent > 0 || bUrgent > 0) {
return aUrgent > bUrgent ? -1 : 1;
}
const aUnread = a.unreadThreadsCountSinceLastViewed;
const bUnread = b.unreadThreadsCountSinceLastViewed;
if (
a.unreadThreadsCountSinceLastViewed > 0 ||
b.unreadThreadsCountSinceLastViewed > 0
) {
return a.unreadThreadsCountSinceLastViewed >
b.unreadThreadsCountSinceLastViewed
// if both channels have urgent count, sort by last message date
if (aUrgent > 0 && bUrgent > 0) {
return new Date(a.lastMessage.createdAt) >
new Date(b.lastMessage.createdAt)
? -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) >
new Date(b.lastMessage.createdAt)
? -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)
end
it "sorts channels with threads by urgency" do
drawer_page.visit_index
drawer_page.click_direct_messages
context "with unread threads" do
fab!(:message_1) do
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(
:chat_thread,
notification_level: :watching,
original_message:
Fabricate(
:chat_message,
chat_channel: dm_channel_4,
user: current_user,
use_service: true,
),
with_replies: 2,
use_service: true,
)
before do
dm_channel_3.membership_for(current_user).mark_read!(message_1.id)
dm_channel_4.membership_for(current_user).mark_read!(message_2.id)
Fabricate(
:chat_thread,
original_message:
Fabricate(
:chat_message,
chat_channel: dm_channel_3,
user: current_user,
use_service: true,
),
with_replies: 2,
use_service: true,
)
drawer_page.visit_index
drawer_page.click_direct_messages
end
expect(drawer_page).to have_channel_at_position(dm_channel_4, 1)
expect(drawer_page).to have_urgent_channel(dm_channel_4)
it "sorts channels with unread threads by last reply" do
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_unread_channel(dm_channel_3)
expect(drawer_page).to have_channel_at_position(dm_channel_4, 1)
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_2, 4)
expect(drawer_page).to have_channel_at_position(dm_channel_3, 2)
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