FEATURE: don't display new/unread notification for muted topics (#9482)
* FEATURE: don't display new/unread notification for muted topics Currently, even if user mute topic, when a new reply to that topic arrives, the user will get "See 1 new or updated topic" message. After clicking on that link, nothing is visible (because the topic is muted) To solve that problem, we will send background message to all users who recently muted that topic that update is coming and they can ignore the next message about that topic.
This commit is contained in:
parent
54ec9f7009
commit
52c1d7337e
|
@ -53,6 +53,17 @@ const TopicTrackingState = EmberObject.extend({
|
||||||
const tracker = this;
|
const tracker = this;
|
||||||
|
|
||||||
const process = data => {
|
const process = data => {
|
||||||
|
if (data.message_type === "muted") {
|
||||||
|
tracker.trackMutedTopic(data.topic_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracker.pruneOldMutedTopics();
|
||||||
|
|
||||||
|
if (tracker.isMutedTopic(data.topic_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.message_type === "delete") {
|
if (data.message_type === "delete") {
|
||||||
tracker.removeTopic(data.topic_id);
|
tracker.removeTopic(data.topic_id);
|
||||||
tracker.incrementMessageCount();
|
tracker.incrementMessageCount();
|
||||||
|
@ -132,6 +143,30 @@ const TopicTrackingState = EmberObject.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mutedTopics() {
|
||||||
|
return (this.currentUser && this.currentUser.muted_topics) || [];
|
||||||
|
},
|
||||||
|
|
||||||
|
trackMutedTopic(topicId) {
|
||||||
|
let mutedTopics = this.mutedTopics().concat({
|
||||||
|
topicId: topicId,
|
||||||
|
createdAt: Date.now()
|
||||||
|
});
|
||||||
|
this.currentUser && this.currentUser.set("muted_topics", mutedTopics);
|
||||||
|
},
|
||||||
|
|
||||||
|
pruneOldMutedTopics() {
|
||||||
|
const now = Date.now();
|
||||||
|
let mutedTopics = this.mutedTopics().filter(
|
||||||
|
mutedTopic => now - mutedTopic.createdAt < 60000
|
||||||
|
);
|
||||||
|
this.currentUser && this.currentUser.set("muted_topics", mutedTopics);
|
||||||
|
},
|
||||||
|
|
||||||
|
isMutedTopic(topicId) {
|
||||||
|
return !!this.mutedTopics().findBy("topicId", topicId);
|
||||||
|
},
|
||||||
|
|
||||||
updateSeen(topicId, highestSeen) {
|
updateSeen(topicId, highestSeen) {
|
||||||
if (!topicId || !highestSeen) {
|
if (!topicId || !highestSeen) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -12,6 +12,7 @@ class TopicTrackingState
|
||||||
CHANNEL = "/user-tracking"
|
CHANNEL = "/user-tracking"
|
||||||
UNREAD_MESSAGE_TYPE = "unread".freeze
|
UNREAD_MESSAGE_TYPE = "unread".freeze
|
||||||
LATEST_MESSAGE_TYPE = "latest".freeze
|
LATEST_MESSAGE_TYPE = "latest".freeze
|
||||||
|
MUTED_MESSAGE_TYPE = "muted".freeze
|
||||||
|
|
||||||
attr_accessor :user_id,
|
attr_accessor :user_id,
|
||||||
:topic_id,
|
:topic_id,
|
||||||
|
@ -71,6 +72,22 @@ class TopicTrackingState
|
||||||
"/unread/#{user_id}"
|
"/unread/#{user_id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.publish_muted(post)
|
||||||
|
user_ids = post.topic.topic_users
|
||||||
|
.where(notification_level: NotificationLevels.all[:muted])
|
||||||
|
.joins(:user)
|
||||||
|
.where("users.last_seen_at > ?", 7.days.ago)
|
||||||
|
.order("users.last_seen_at DESC")
|
||||||
|
.limit(100)
|
||||||
|
.pluck(:user_id)
|
||||||
|
return if user_ids.blank?
|
||||||
|
message = {
|
||||||
|
topic_id: post.topic_id,
|
||||||
|
message_type: MUTED_MESSAGE_TYPE,
|
||||||
|
}
|
||||||
|
MessageBus.publish("/latest", message.as_json, user_ids: user_ids)
|
||||||
|
end
|
||||||
|
|
||||||
def self.publish_unread(post)
|
def self.publish_unread(post)
|
||||||
return unless post.topic.regular?
|
return unless post.topic.regular?
|
||||||
# TODO at high scale we are going to have to defer this,
|
# TODO at high scale we are going to have to defer this,
|
||||||
|
|
|
@ -57,7 +57,10 @@ class PostJobsEnqueuer
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_post_create
|
def after_post_create
|
||||||
TopicTrackingState.publish_unread(@post) if @post.post_number > 1
|
if @post.post_number > 1
|
||||||
|
TopicTrackingState.publish_muted(@post)
|
||||||
|
TopicTrackingState.publish_unread(@post)
|
||||||
|
end
|
||||||
TopicTrackingState.publish_latest(@topic, @post.whisper?)
|
TopicTrackingState.publish_latest(@topic, @post.whisper?)
|
||||||
|
|
||||||
Jobs.enqueue_in(SiteSetting.email_time_window_mins.minutes,
|
Jobs.enqueue_in(SiteSetting.email_time_window_mins.minutes,
|
||||||
|
|
|
@ -72,6 +72,46 @@ describe TopicTrackingState do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#publish_muted' do
|
||||||
|
let(:user) do
|
||||||
|
Fabricate(:user, last_seen_at: Date.today)
|
||||||
|
end
|
||||||
|
let(:post) do
|
||||||
|
create_post(user: user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can correctly publish muted' do
|
||||||
|
TopicUser.find_by(topic: post.topic, user: post.user).update(notification_level: 0)
|
||||||
|
messages = MessageBus.track_publish("/latest") do
|
||||||
|
TopicTrackingState.publish_muted(post)
|
||||||
|
end
|
||||||
|
|
||||||
|
muted_message = messages.find { |message| message.data["message_type"] == "muted" }
|
||||||
|
|
||||||
|
expect(muted_message.data["topic_id"]).to eq(topic.id)
|
||||||
|
expect(muted_message.data["message_type"]).to eq(described_class::MUTED_MESSAGE_TYPE)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not publish any message when notification level is not muted' do
|
||||||
|
messages = MessageBus.track_publish("/latest") do
|
||||||
|
TopicTrackingState.publish_muted(post)
|
||||||
|
end
|
||||||
|
muted_messages = messages.select { |message| message.data["message_type"] == "muted" }
|
||||||
|
|
||||||
|
expect(muted_messages).to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not publish any message when the user was not seen in the last 7 days' do
|
||||||
|
TopicUser.find_by(topic: post.topic, user: post.user).update(notification_level: 0)
|
||||||
|
post.user.update(last_seen_at: 8.days.ago)
|
||||||
|
messages = MessageBus.track_publish("/latest") do
|
||||||
|
TopicTrackingState.publish_muted(post)
|
||||||
|
end
|
||||||
|
muted_messages = messages.select { |message| message.data["message_type"] == "muted" }
|
||||||
|
expect(muted_messages).to eq([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#publish_private_message' do
|
describe '#publish_private_message' do
|
||||||
fab!(:admin) { Fabricate(:admin) }
|
fab!(:admin) { Fabricate(:admin) }
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,17 @@ import TopicTrackingState from "discourse/models/topic-tracking-state";
|
||||||
import createStore from "helpers/create-store";
|
import createStore from "helpers/create-store";
|
||||||
import Category from "discourse/models/category";
|
import Category from "discourse/models/category";
|
||||||
import { NotificationLevels } from "discourse/lib/notification-levels";
|
import { NotificationLevels } from "discourse/lib/notification-levels";
|
||||||
|
import User from "discourse/models/user";
|
||||||
|
|
||||||
QUnit.module("model:topic-tracking-state");
|
QUnit.module("model:topic-tracking-state", {
|
||||||
|
beforeEach() {
|
||||||
|
this.clock = sinon.useFakeTimers(new Date(2012, 11, 31, 12, 0).getTime());
|
||||||
|
},
|
||||||
|
|
||||||
|
afterEach() {
|
||||||
|
this.clock.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test("sync", function(assert) {
|
QUnit.test("sync", function(assert) {
|
||||||
const state = TopicTrackingState.create();
|
const state = TopicTrackingState.create();
|
||||||
|
@ -168,3 +177,22 @@ QUnit.test("countNew", assert => {
|
||||||
assert.equal(state.countNew(2), 2);
|
assert.equal(state.countNew(2), 2);
|
||||||
assert.equal(state.countNew(3), 1);
|
assert.equal(state.countNew(3), 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test("mute topic", function(assert) {
|
||||||
|
let currentUser = User.create({
|
||||||
|
username: "chuck",
|
||||||
|
muted_category_ids: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = TopicTrackingState.create({ currentUser });
|
||||||
|
|
||||||
|
state.trackMutedTopic(1);
|
||||||
|
assert.equal(currentUser.muted_topics[0].topicId, 1);
|
||||||
|
|
||||||
|
state.pruneOldMutedTopics();
|
||||||
|
assert.equal(state.isMutedTopic(1), true);
|
||||||
|
|
||||||
|
this.clock.tick(60000);
|
||||||
|
state.pruneOldMutedTopics();
|
||||||
|
assert.equal(state.isMutedTopic(1), false);
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue