FIX: Dismissing unread posts did not publish changes to other clients (#22584)

Why this change?

Prior to this change, dismissing unreads posts did not publish the
changes across clients for the same user. As a result, users can end up
seeing an unread count being present but saw no topics being loaded when
visiting the `/unread` route.
This commit is contained in:
Alan Guo Xiang Tan 2023-07-13 18:05:56 +08:00 committed by GitHub
parent ab6c638da2
commit 48c8ed49d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 208 additions and 40 deletions

View File

@ -1016,6 +1016,10 @@ const TopicTrackingState = EmberObject.extend({
this._dismissNewTopics(data.payload.topic_ids); this._dismissNewTopics(data.payload.topic_ids);
} }
if (data.message_type === "dismiss_new_posts") {
this._dismissNewPosts(data.payload.topic_ids);
}
if (["new_topic", "unread", "read"].includes(data.message_type)) { if (["new_topic", "unread", "read"].includes(data.message_type)) {
this.notifyIncoming(data); this.notifyIncoming(data);
if (!deepEqual(old, data.payload)) { if (!deepEqual(old, data.payload)) {
@ -1055,6 +1059,23 @@ const TopicTrackingState = EmberObject.extend({
topicIds.forEach((topicId) => { topicIds.forEach((topicId) => {
this.modifyStateProp(topicId, "is_seen", true); this.modifyStateProp(topicId, "is_seen", true);
}); });
this.incrementMessageCount();
},
_dismissNewPosts(topicIds) {
topicIds.forEach((topicId) => {
const state = this.findState(topicId);
if (state) {
this.modifyStateProp(
topicId,
"last_read_post_number",
state.highest_post_number
);
}
});
this.incrementMessageCount(); this.incrementMessageCount();
}, },

View File

@ -1100,11 +1100,12 @@ class TopicsController < ApplicationController
dismissed_topic_ids = [] dismissed_topic_ids = []
dismissed_post_topic_ids = [] dismissed_post_topic_ids = []
if !current_user.new_new_view_enabled? || params[:dismiss_topics] if !current_user.new_new_view_enabled? || params[:dismiss_topics]
dismissed_topic_ids = dismissed_topic_ids =
TopicsBulkAction.new(current_user, topic_scope.pluck(:id), type: "dismiss_topics").perform! TopicsBulkAction.new(current_user, topic_scope.pluck(:id), type: "dismiss_topics").perform!
TopicTrackingState.publish_dismiss_new(current_user.id, topic_ids: dismissed_topic_ids)
end end
if params[:dismiss_posts] if params[:dismiss_posts]
if params[:untrack] if params[:untrack]
dismissed_post_topic_ids = dismissed_post_topic_ids =

View File

@ -29,6 +29,7 @@ class TopicTrackingState
DESTROY_MESSAGE_TYPE = "destroy" DESTROY_MESSAGE_TYPE = "destroy"
READ_MESSAGE_TYPE = "read" READ_MESSAGE_TYPE = "read"
DISMISS_NEW_MESSAGE_TYPE = "dismiss_new" DISMISS_NEW_MESSAGE_TYPE = "dismiss_new"
DISMISS_NEW_POSTS_MESSAGE_TYPE = "dismiss_new_posts"
MAX_TOPICS = 5000 MAX_TOPICS = 5000
NEW_MESSAGE_BUS_CHANNEL = "/new" NEW_MESSAGE_BUS_CHANNEL = "/new"
@ -231,6 +232,11 @@ class TopicTrackingState
MessageBus.publish(self.unread_channel_key(user_id), message.as_json, user_ids: [user_id]) MessageBus.publish(self.unread_channel_key(user_id), message.as_json, user_ids: [user_id])
end end
def self.publish_dismiss_new_posts(user_id, topic_ids: [])
message = { message_type: DISMISS_NEW_POSTS_MESSAGE_TYPE, payload: { topic_ids: topic_ids } }
MessageBus.publish(self.unread_channel_key(user_id), message.as_json, user_ids: [user_id])
end
def self.new_filter_sql def self.new_filter_sql
TopicQuery TopicQuery
.new_filter(Topic, treat_as_new_topic_clause_sql: treat_as_new_topic_clause) .new_filter(Topic, treat_as_new_topic_clause_sql: treat_as_new_topic_clause)

View File

@ -86,6 +86,7 @@ class TopicsBulkAction
def dismiss_posts def dismiss_posts
highest_number_source_column = highest_number_source_column =
@user.whisperer? ? "highest_staff_post_number" : "highest_post_number" @user.whisperer? ? "highest_staff_post_number" : "highest_post_number"
sql = <<~SQL sql = <<~SQL
UPDATE topic_users tu UPDATE topic_users tu
SET last_read_post_number = t.#{highest_number_source_column} SET last_read_post_number = t.#{highest_number_source_column}
@ -94,6 +95,8 @@ class TopicsBulkAction
SQL SQL
DB.exec(sql, user_id: @user.id, topic_ids: @topic_ids) DB.exec(sql, user_id: @user.id, topic_ids: @topic_ids)
TopicTrackingState.publish_dismiss_new_posts(@user.id, topic_ids: @topic_ids.sort)
@changed_ids.concat @topic_ids @changed_ids.concat @topic_ids
end end
@ -115,7 +118,9 @@ class TopicsBulkAction
now = Time.zone.now now = Time.zone.now
rows = ids.map { |id| { topic_id: id, user_id: @user.id, created_at: now } } rows = ids.map { |id| { topic_id: id, user_id: @user.id, created_at: now } }
DismissedTopicUser.insert_all(rows) DismissedTopicUser.insert_all(rows)
TopicTrackingState.publish_dismiss_new(@user.id, topic_ids: ids.sort)
end end
@changed_ids = ids @changed_ids = ids
end end

View File

@ -788,4 +788,20 @@ RSpec.describe TopicTrackingState do
include_examples("publishes message to right groups and users", "/destroy", :publish_destroy) include_examples("publishes message to right groups and users", "/destroy", :publish_destroy)
include_examples("does not publish message for private topics", :publish_destroy) include_examples("does not publish message for private topics", :publish_destroy)
end end
describe "#publish_dismiss_new_posts" do
it "publishes the right message to the right users" do
messages =
MessageBus.track_publish(TopicTrackingState.unread_channel_key(user.id)) do
TopicTrackingState.publish_dismiss_new_posts(user.id, topic_ids: [topic.id])
end
expect(messages.size).to eq(1)
message = messages.first
expect(message.data["payload"]["topic_ids"]).to contain_exactly(topic.id)
expect(message.user_ids).to eq([user.id])
end
end
end end

View File

@ -3854,7 +3854,7 @@ RSpec.describe TopicsController do
context "when tracked is unset" do context "when tracked is unset" do
it "updates the `new_since` date" do it "updates the `new_since` date" do
TopicTrackingState.expects(:publish_dismiss_new) TopicTrackingState.expects(:publish_dismiss_new).never
put "/topics/reset-new.json" put "/topics/reset-new.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
@ -4052,15 +4052,10 @@ RSpec.describe TopicsController do
messages = messages =
MessageBus.track_publish do MessageBus.track_publish do
put "/topics/reset-new.json", params: { category_id: private_category.id } put "/topics/reset-new.json", params: { category_id: private_category.id }
expect(response.status).to eq(200)
end end
expect(response.status).to eq(200)
expect(messages.size).to eq(1) expect(messages.size).to eq(0)
expect(messages[0].channel).to eq(TopicTrackingState.unread_channel_key(user.id))
expect(messages[0].user_ids).to eq([user.id])
expect(messages[0].data["message_type"]).to eq(
TopicTrackingState::DISMISS_NEW_MESSAGE_TYPE,
)
expect(messages[0].data["payload"]["topic_ids"]).to eq([])
expect(DismissedTopicUser.where(user_id: user.id).count).to eq(0) expect(DismissedTopicUser.where(user_id: user.id).count).to eq(0)
end end

View File

@ -1,30 +0,0 @@
# frozen_string_literal: true
describe "Filtering topics", type: :system, js: true do
fab!(:user) { Fabricate(:user) }
fab!(:group) { Fabricate(:group).tap { |g| g.add(user) } }
fab!(:topic) { Fabricate(:topic) }
let(:topic_list) { PageObjects::Components::TopicList.new }
let(:dismiss_new_modal) { PageObjects::Modals::DismissNew.new }
before { SiteSetting.experimental_new_new_view_groups = group.id }
it "displays confirmation modal with preselected options" do
sign_in(user)
visit("/new")
expect(topic_list).to have_topic(topic)
find(".dismiss-read", text: "Dismiss…").click
expect(dismiss_new_modal).to have_dismiss_topics_checked
expect(dismiss_new_modal).to have_dismiss_posts_checked
expect(dismiss_new_modal).to have_untrack_unchecked
dismiss_new_modal.click_dismiss
expect(topic_list).to have_no_topics
end
end

View File

@ -0,0 +1,114 @@
# frozen_string_literal: true
RSpec.describe "Dismissing New", type: :system do
fab!(:user) { Fabricate(:user) }
let(:topic_list_controls) { PageObjects::Components::TopicListControls.new }
let(:topic_list) { PageObjects::Components::TopicList.new }
let(:dismiss_new_modal) { PageObjects::Modals::DismissNew.new }
describe "when a user has an unread post" do
fab!(:topic) { Fabricate(:topic, user: user) }
fab!(:post1) { create_post(user: user, topic: topic) }
fab!(:post2) { create_post(topic: topic) }
it "should remove the unread post across sessions after the user dismisses it" do
sign_in(user)
visit("/unread")
expect(topic_list_controls).to have_unread(count: 1)
using_session(:tab_1) do
sign_in(user)
visit("/unread")
expect(topic_list_controls).to have_unread(count: 1)
end
topic_list_controls.dismiss_unread
expect(topic_list_controls).to have_unread(count: 0)
using_session(:tab_1) { expect(topic_list_controls).to have_unread(count: 0) }
end
end
describe "when a user has a new topic" do
fab!(:topic) { Fabricate(:topic) }
it "should remove the new topic across sessions after the user dismisses it" do
sign_in(user)
visit("/new")
expect(topic_list_controls).to have_new(count: 1)
using_session(:tab_1) do
sign_in(user)
visit("/new")
expect(topic_list_controls).to have_new(count: 1)
end
topic_list_controls.dismiss_new
expect(topic_list_controls).to have_new(count: 0)
using_session(:tab_1) { expect(topic_list_controls).to have_new(count: 0) }
end
end
describe "when the `experimental_new_new_view_groups` site setting is enabled" do
fab!(:group) { Fabricate(:group).tap { |g| g.add(user) } }
fab!(:new_topic) { Fabricate(:topic, user: user) }
fab!(:post1) { create_post(user: user) }
fab!(:post2) { create_post(topic: post1.topic) }
before { SiteSetting.experimental_new_new_view_groups = group.name }
it "should remove the new topic and post across sessions after the user dismisses it" do
sign_in(user)
visit("/new")
expect(topic_list_controls).to have_new(count: 2)
using_session(:tab_1) do
sign_in(user)
visit("/new")
expect(topic_list_controls).to have_new(count: 2)
end
topic_list_controls.dismiss_new
dismiss_new_modal.click_dismiss
expect(topic_list_controls).to have_new(count: 0)
using_session(:tab_1) { expect(topic_list_controls).to have_new(count: 0) }
end
it "displays confirmation modal with preselected options" do
sign_in(user)
visit("/new")
expect(topic_list).to have_topic(new_topic)
expect(topic_list).to have_topic(post1.topic)
topic_list_controls.dismiss_new
expect(dismiss_new_modal).to have_dismiss_topics_checked
expect(dismiss_new_modal).to have_dismiss_posts_checked
expect(dismiss_new_modal).to have_untrack_unchecked
dismiss_new_modal.click_dismiss
expect(topic_list).to have_no_topics
end
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
module PageObjects
module Components
class TopicListControls < PageObjects::Components::Base
def has_new?(count:)
text =
if count == 0
I18n.t("js.filters.new.title")
else
I18n.t("js.filters.new.title_with_count", count: count)
end
has_css?(".nav-item_new", text: text)
end
def has_unread?(count:)
text =
if count == 0
I18n.t("js.filters.unread.title")
else
I18n.t("js.filters.unread.title_with_count", count: count)
end
has_css?(".nav-item_unread", text: text)
end
def dismiss_unread
click_button("dismiss-topics-bottom")
click_button("dismiss-read-confirm")
self
end
def dismiss_new
click_button("dismiss-new-bottom")
self
end
end
end
end