mirror of
https://github.com/discourse/discourse.git
synced 2026-08-17 16:35:03 -05:00
FEATURE: Show unread in sidebar for unread channel threads (#22342)
This commit makes it so that when the user has unread threads for a channel we show a blue dot in the sidebar (or channel index for mobile/drawer). This blue dot is slightly different from the channel unread messages: 1. It will only show if the new thread messages were created since the user last viewed the channel 2. It will be cleared when the user views the channel, but the threads are still considered unread because we want the user to click into the thread list to view them This necessitates a change to the current user serializer to also include the unread thread overview, which is all unread threads across all channels and their last reply date + time.
This commit is contained in:
@@ -9,6 +9,7 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
||||
include_missing_memberships: include_missing_memberships,
|
||||
include_threads: include_threads,
|
||||
include_read: include_read,
|
||||
include_last_reply_details: include_last_reply_details,
|
||||
)
|
||||
end
|
||||
|
||||
@@ -20,6 +21,7 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
||||
let(:include_missing_memberships) { false }
|
||||
let(:include_threads) { false }
|
||||
let(:include_read) { true }
|
||||
let(:include_last_reply_details) { false }
|
||||
context "when channel_ids empty" do
|
||||
it "returns empty object for channel_tracking" do
|
||||
expect(query.channel_tracking).to eq({})
|
||||
@@ -129,6 +131,56 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
||||
)
|
||||
end
|
||||
|
||||
context "when include_last_reply_details is true" do
|
||||
let(:include_last_reply_details) { true }
|
||||
|
||||
before do
|
||||
thread_1.add(current_user)
|
||||
thread_2.add(current_user)
|
||||
Fabricate(:chat_message, chat_channel: channel_1, thread: thread_1)
|
||||
Fabricate(:chat_message, chat_channel: channel_2, thread: thread_2)
|
||||
end
|
||||
|
||||
it "gets the last_reply_created_at for each thread based on the last_message" do
|
||||
expect(query.thread_tracking).to eq(
|
||||
{
|
||||
thread_1.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
last_reply_created_at: thread_1.reload.last_message.created_at,
|
||||
},
|
||||
thread_2.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
channel_id: channel_2.id,
|
||||
last_reply_created_at: thread_2.reload.last_message.created_at,
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not get the last_reply_created_at for threads where the last_message is deleted" do
|
||||
thread_1.reload.last_message.trash!
|
||||
expect(query.thread_tracking).to eq(
|
||||
{
|
||||
thread_1.id => {
|
||||
unread_count: 0,
|
||||
mention_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
last_reply_created_at: nil,
|
||||
},
|
||||
thread_2.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
channel_id: channel_2.id,
|
||||
last_reply_created_at: thread_2.reload.last_message.created_at,
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when thread_ids and channel_ids is empty" do
|
||||
let(:thread_ids) { [] }
|
||||
let(:channel_ids) { [] }
|
||||
|
||||
@@ -173,6 +173,7 @@ RSpec.describe Chat::ChannelViewBuilder do
|
||||
channel_ids: [channel.id],
|
||||
include_threads: true,
|
||||
include_read: false,
|
||||
include_last_reply_details: true,
|
||||
)
|
||||
.returns(Chat::TrackingStateReport.new)
|
||||
.once
|
||||
@@ -188,7 +189,7 @@ RSpec.describe Chat::ChannelViewBuilder do
|
||||
thread = Fabricate(:chat_thread, channel: channel)
|
||||
thread.add(current_user)
|
||||
message_1 = Fabricate(:chat_message, chat_channel: channel, thread: thread)
|
||||
expect(result.view.unread_thread_ids).to eq([message_1.thread.id])
|
||||
expect(result.view.unread_thread_overview).to eq({ thread.id => message_1.created_at })
|
||||
end
|
||||
|
||||
it "fetches the tracking state of threads in the channel" do
|
||||
|
||||
@@ -137,18 +137,21 @@ RSpec.describe Chat::MarkAllUserChannelsRead do
|
||||
expect(message.data).to eq(
|
||||
channel_1.id.to_s => {
|
||||
"last_read_message_id" => message_2.id,
|
||||
"last_reply_created_at" => nil,
|
||||
"membership_id" => membership_1.id,
|
||||
"mention_count" => 0,
|
||||
"unread_count" => 0,
|
||||
},
|
||||
channel_2.id.to_s => {
|
||||
"last_read_message_id" => message_4.id,
|
||||
"last_reply_created_at" => nil,
|
||||
"membership_id" => membership_2.id,
|
||||
"mention_count" => 0,
|
||||
"unread_count" => 0,
|
||||
},
|
||||
channel_3.id.to_s => {
|
||||
"last_read_message_id" => message_6.id,
|
||||
"last_reply_created_at" => nil,
|
||||
"membership_id" => membership_3.id,
|
||||
"mention_count" => 0,
|
||||
"unread_count" => 0,
|
||||
|
||||
@@ -96,8 +96,10 @@ describe Chat::Publisher do
|
||||
|
||||
context "when the channel has threading enabled and the message is a thread reply" do
|
||||
fab!(:thread) { Fabricate(:chat_thread, channel: channel) }
|
||||
|
||||
before do
|
||||
message_1.update!(thread: thread)
|
||||
thread.update_last_message_id!
|
||||
channel.update!(threading_enabled: true)
|
||||
end
|
||||
|
||||
@@ -106,16 +108,22 @@ describe Chat::Publisher do
|
||||
|
||||
it "publishes the tracking state with correct counts" do
|
||||
expect(data["thread_id"]).to eq(thread.id)
|
||||
expect(data["unread_thread_ids"]).to eq([thread.id])
|
||||
expect(data["thread_tracking"]).to eq({ "unread_count" => 1, "mention_count" => 0 })
|
||||
expect(data["unread_thread_overview"]).to eq(
|
||||
{ thread.id.to_s => thread.reload.last_message.created_at.iso8601(3) },
|
||||
)
|
||||
expect(data["thread_tracking"]).to eq(
|
||||
{ "unread_count" => 1, "mention_count" => 0, "last_reply_created_at" => nil },
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the user has no thread membership" do
|
||||
it "publishes the tracking state with zeroed out counts" do
|
||||
expect(data["thread_id"]).to eq(thread.id)
|
||||
expect(data["unread_thread_ids"]).to eq([])
|
||||
expect(data["thread_tracking"]).to eq({ "unread_count" => 0, "mention_count" => 0 })
|
||||
expect(data["unread_thread_overview"]).to eq({})
|
||||
expect(data["thread_tracking"]).to eq(
|
||||
{ "unread_count" => 0, "mention_count" => 0, "last_reply_created_at" => nil },
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"avatar_template": { "type": "string" },
|
||||
"username": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"last_viewed_at": { "type": "datetime" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,6 @@ RSpec.describe "Message notifications - mobile", type: :system, mobile: true do
|
||||
expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator", text: "")
|
||||
expect(page).to have_css(
|
||||
".chat-channel-row[data-chat-channel-id=\"#{channel_1.id}\"] .chat-channel-unread-indicator",
|
||||
text: 1,
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -130,7 +129,6 @@ RSpec.describe "Message notifications - mobile", type: :system, mobile: true do
|
||||
expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator")
|
||||
expect(page).to have_css(
|
||||
".chat-channel-row[data-chat-channel-id=\"#{channel_1.id}\"] .chat-channel-unread-indicator",
|
||||
text: 1,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,12 +20,22 @@ module PageObjects
|
||||
end
|
||||
|
||||
def open_channel(channel)
|
||||
find(
|
||||
"#{VISIBLE_DRAWER} .channels-list .chat-channel-row[data-chat-channel-id='#{channel.id}']",
|
||||
).click
|
||||
find("#{VISIBLE_DRAWER} .channels-list #{channel_row_selector(channel)}").click
|
||||
has_no_css?(".chat-skeleton")
|
||||
end
|
||||
|
||||
def channel_row_selector(channel)
|
||||
".chat-channel-row[data-chat-channel-id='#{channel.id}']"
|
||||
end
|
||||
|
||||
def has_unread_channel?(channel)
|
||||
has_css?("#{channel_row_selector(channel)} .chat-channel-unread-indicator")
|
||||
end
|
||||
|
||||
def has_no_unread_channel?(channel)
|
||||
has_no_css?("#{channel_row_selector(channel)} .chat-channel-unread-indicator")
|
||||
end
|
||||
|
||||
def maximize
|
||||
mouseout
|
||||
find("#{VISIBLE_DRAWER} .chat-drawer-header__full-screen-btn").click
|
||||
|
||||
@@ -40,6 +40,16 @@ module PageObjects
|
||||
find(".sidebar-section-link.channel-#{channel.id}")
|
||||
self
|
||||
end
|
||||
|
||||
def has_unread_channel?(channel)
|
||||
has_css?(".sidebar-section-link.channel-#{channel.id} .sidebar-section-link-suffix.unread")
|
||||
end
|
||||
|
||||
def has_no_unread_channel?(channel)
|
||||
has_no_css?(
|
||||
".sidebar-section-link.channel-#{channel.id} .sidebar-section-link-suffix.unread",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -64,5 +64,53 @@ describe "Thread tracking state | drawer", type: :system do
|
||||
expect(drawer_page).to have_unread_thread_indicator(count: 1)
|
||||
expect(thread_list_page).to have_unread_item(thread.id)
|
||||
end
|
||||
|
||||
describe "channel index unread indicators" do
|
||||
fab!(:other_channel) { Fabricate(:chat_channel) }
|
||||
|
||||
before { other_channel.add(current_user) }
|
||||
|
||||
it "shows an unread indicator for the channel with unread threads in the index" do
|
||||
visit("/")
|
||||
chat_page.open_from_header
|
||||
expect(drawer_page).to have_unread_channel(channel)
|
||||
end
|
||||
|
||||
it "does not show an unread indicator for the channel if the user has visited the channel since the unread thread message arrived" do
|
||||
channel.membership_for(current_user).update!(last_viewed_at: Time.zone.now)
|
||||
visit("/")
|
||||
chat_page.open_from_header
|
||||
expect(drawer_page).to have_no_unread_channel(channel)
|
||||
end
|
||||
|
||||
it "clears the index unread indicator for the channel when opening it but keeps the thread list unread indicator" do
|
||||
visit("/")
|
||||
chat_page.open_from_header
|
||||
drawer_page.open_channel(channel)
|
||||
expect(channel_page).to have_unread_thread_indicator(count: 1)
|
||||
drawer_page.back
|
||||
expect(drawer_page).to have_no_unread_channel(channel)
|
||||
end
|
||||
|
||||
it "does not show an unread indicator for the channel index if a new thread message arrives while the user is looking at the channel" do
|
||||
visit("/")
|
||||
chat_page.open_from_header
|
||||
expect(drawer_page).to have_unread_channel(channel)
|
||||
drawer_page.open_channel(channel)
|
||||
Fabricate(:chat_message, thread: thread)
|
||||
drawer_page.back
|
||||
expect(drawer_page).to have_no_unread_channel(channel)
|
||||
end
|
||||
|
||||
it "shows an unread indicator for the channel index if a new thread message arrives while the user is not looking at the channel" do
|
||||
visit("/")
|
||||
chat_page.open_from_header
|
||||
drawer_page.open_channel(channel)
|
||||
drawer_page.back
|
||||
expect(drawer_page).to have_no_unread_channel(channel)
|
||||
Fabricate(:chat_message, thread: thread)
|
||||
expect(drawer_page).to have_unread_channel(channel)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ describe "Thread tracking state | full page", type: :system do
|
||||
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
|
||||
let(:thread_page) { PageObjects::Pages::ChatThread.new }
|
||||
let(:thread_list_page) { PageObjects::Components::Chat::ThreadList.new }
|
||||
let(:sidebar_page) { PageObjects::Pages::Sidebar.new }
|
||||
|
||||
before do
|
||||
SiteSetting.enable_experimental_chat_threaded_discussions = true
|
||||
@@ -86,6 +87,47 @@ describe "Thread tracking state | full page", type: :system do
|
||||
expect(thread_list_page).to have_thread(new_thread)
|
||||
end
|
||||
|
||||
describe "sidebar unread indicators" do
|
||||
fab!(:other_channel) { Fabricate(:chat_channel) }
|
||||
|
||||
before do
|
||||
other_channel.add(current_user)
|
||||
SiteSetting.navigation_menu = "sidebar"
|
||||
end
|
||||
|
||||
it "shows an unread indicator for the channel with unread threads in the sidebar" do
|
||||
chat_page.visit_channel(other_channel)
|
||||
expect(sidebar_page).to have_unread_channel(channel)
|
||||
end
|
||||
|
||||
it "does not show an unread indicator for the channel if the user has visited the channel since the unread thread message arrived" do
|
||||
channel.membership_for(current_user).update!(last_viewed_at: Time.zone.now)
|
||||
chat_page.visit_channel(other_channel)
|
||||
expect(sidebar_page).to have_no_unread_channel(channel)
|
||||
end
|
||||
|
||||
it "clears the sidebar unread indicator for the channel when opening it but keeps the thread list unread indicator" do
|
||||
chat_page.visit_channel(channel)
|
||||
expect(sidebar_page).to have_no_unread_channel(channel)
|
||||
expect(channel_page).to have_unread_thread_indicator(count: 1)
|
||||
end
|
||||
|
||||
it "does not show an unread indicator for the channel sidebar if a new thread message arrives while the user is looking at the channel" do
|
||||
chat_page.visit_channel(channel)
|
||||
expect(sidebar_page).to have_no_unread_channel(channel)
|
||||
Fabricate(:chat_message, thread: thread)
|
||||
expect(sidebar_page).to have_no_unread_channel(channel)
|
||||
end
|
||||
|
||||
it "shows an unread indicator for the channel sidebar if a new thread message arrives while the user is not looking at the channel" do
|
||||
chat_page.visit_channel(channel)
|
||||
expect(sidebar_page).to have_no_unread_channel(channel)
|
||||
chat_page.visit_channel(other_channel)
|
||||
Fabricate(:chat_message, thread: thread)
|
||||
expect(sidebar_page).to have_unread_channel(channel)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the user's notification level for the thread is set to normal" do
|
||||
before { thread.membership_for(current_user).update!(notification_level: :normal) }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user