FEATURE: Ability to dismiss new topics in a specific tag (#11968)
* FEATURE: Ability to dismiss new topics in a specific tag Follow up of https://github.com/discourse/discourse/pull/11927 Using the same mechanism to disable new topics in a tag. * FIX: respect when category and tag is selected
This commit is contained in:
parent
f9de2f9d23
commit
354ec6694a
|
@ -4,6 +4,7 @@ import BulkTopicSelection from "discourse/mixins/bulk-topic-selection";
|
||||||
import FilterModeMixin from "discourse/mixins/filter-mode";
|
import FilterModeMixin from "discourse/mixins/filter-mode";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import NavItem from "discourse/models/nav-item";
|
import NavItem from "discourse/models/nav-item";
|
||||||
|
import Topic from "discourse/models/topic";
|
||||||
import { alias } from "@ember/object/computed";
|
import { alias } from "@ember/object/computed";
|
||||||
import bootbox from "bootbox";
|
import bootbox from "bootbox";
|
||||||
import { queryParams } from "discourse/controllers/discovery-sortable";
|
import { queryParams } from "discourse/controllers/discovery-sortable";
|
||||||
|
@ -109,11 +110,34 @@ export default Controller.extend(BulkTopicSelection, FilterModeMixin, {
|
||||||
return this.isFilterPage(filter, "unread") && topicsLength > 0;
|
return this.isFilterPage(filter, "unread") && topicsLength > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@discourseComputed("list.filter", "list.topics.length")
|
||||||
|
showResetNew(filter, topicsLength) {
|
||||||
|
return this.isFilterPage(filter, "new") && topicsLength > 0;
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
dismissReadPosts() {
|
dismissReadPosts() {
|
||||||
showModal("dismiss-read", { title: "topics.bulk.dismiss_read" });
|
showModal("dismiss-read", { title: "topics.bulk.dismiss_read" });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resetNew() {
|
||||||
|
const tracked =
|
||||||
|
(this.router.currentRoute.queryParams["f"] ||
|
||||||
|
this.router.currentRoute.queryParams["filter"]) === "tracked";
|
||||||
|
|
||||||
|
Topic.resetNew(
|
||||||
|
this.category,
|
||||||
|
!this.noSubcategories,
|
||||||
|
tracked,
|
||||||
|
this.tag
|
||||||
|
).then(() =>
|
||||||
|
this.send(
|
||||||
|
"refresh",
|
||||||
|
tracked ? { skipResettingParams: ["filter", "f"] } : {}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
changeSort(order) {
|
changeSort(order) {
|
||||||
if (order === this.order) {
|
if (order === this.order) {
|
||||||
this.toggleProperty("ascending");
|
this.toggleProperty("ascending");
|
||||||
|
|
|
@ -106,19 +106,7 @@ const TopicTrackingState = EmberObject.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.message_type === "dismiss_new") {
|
if (data.message_type === "dismiss_new") {
|
||||||
Object.keys(tracker.states).forEach((k) => {
|
tracker.dismissNewTopic(data);
|
||||||
const topic = tracker.states[k];
|
|
||||||
if (
|
|
||||||
!data.payload.category_id ||
|
|
||||||
topic.category_id === parseInt(data.payload.category_id, 0)
|
|
||||||
) {
|
|
||||||
tracker.states[k] = Object.assign({}, topic, {
|
|
||||||
is_seen: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tracker.notifyPropertyChange("states");
|
|
||||||
tracker.incrementMessageCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (["new_topic", "unread", "read"].includes(data.message_type)) {
|
if (["new_topic", "unread", "read"].includes(data.message_type)) {
|
||||||
|
@ -193,6 +181,23 @@ const TopicTrackingState = EmberObject.extend({
|
||||||
this.currentUser && this.currentUser.set(key, topics);
|
this.currentUser && this.currentUser.set(key, topics);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dismissNewTopic(data) {
|
||||||
|
Object.keys(this.states).forEach((k) => {
|
||||||
|
const topic = this.states[k];
|
||||||
|
if (
|
||||||
|
(!data.payload.category_id ||
|
||||||
|
topic.category_id === parseInt(data.payload.category_id, 10)) &&
|
||||||
|
(!data.payload.tag_id || topic.tags.includes(data.payload.tag_id))
|
||||||
|
) {
|
||||||
|
this.states[k] = Object.assign({}, topic, {
|
||||||
|
is_seen: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.notifyPropertyChange("states");
|
||||||
|
this.incrementMessageCount();
|
||||||
|
},
|
||||||
|
|
||||||
pruneOldMutedAndUnmutedTopics() {
|
pruneOldMutedAndUnmutedTopics() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
let mutedTopics = this.mutedTopics().filter(
|
let mutedTopics = this.mutedTopics().filter(
|
||||||
|
|
|
@ -766,12 +766,16 @@ Topic.reopenClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
resetNew(category, include_subcategories, tracked = false) {
|
resetNew(category, include_subcategories, tracked = false, tag = false) {
|
||||||
const data = { tracked };
|
const data = { tracked };
|
||||||
if (category) {
|
if (category) {
|
||||||
data.category_id = category.id;
|
data.category_id = category.id;
|
||||||
data.include_subcategories = include_subcategories;
|
data.include_subcategories = include_subcategories;
|
||||||
}
|
}
|
||||||
|
if (tag) {
|
||||||
|
data.tag_id = tag.id;
|
||||||
|
}
|
||||||
|
|
||||||
return ajax("/topics/reset-new", { type: "PUT", data });
|
return ajax("/topics/reset-new", { type: "PUT", data });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,15 @@
|
||||||
label="topics.bulk.dismiss_button"}}
|
label="topics.bulk.dismiss_button"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if showResetNew}}
|
||||||
|
{{d-button
|
||||||
|
class="btn-default dismiss-read"
|
||||||
|
action=(action "resetNew")
|
||||||
|
id="dismiss-new"
|
||||||
|
icon="check"
|
||||||
|
label="topics.bulk.dismiss_new"}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#footer-message education=footerEducation message=footerMessage}}
|
{{#footer-message education=footerEducation message=footerMessage}}
|
||||||
{{#link-to "tags"}} {{i18n "topic.browse_all_tags"}}{{/link-to}} {{i18n "or"}} {{#link-to "discovery.latest"}}{{i18n "topic.view_latest_topics"}}{{/link-to}}.
|
{{#link-to "tags"}} {{i18n "topic.browse_all_tags"}}{{/link-to}} {{i18n "or"}} {{#link-to "discovery.latest"}}{{i18n "topic.view_latest_topics"}}{{/link-to}}.
|
||||||
{{/footer-message}}
|
{{/footer-message}}
|
||||||
|
|
|
@ -328,6 +328,58 @@ module("Unit | Model | topic-tracking-state", function (hooks) {
|
||||||
assert.equal(state.countNew(4), 0);
|
assert.equal(state.countNew(4), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("dismissNew", function (assert) {
|
||||||
|
let currentUser = User.create({
|
||||||
|
username: "chuck",
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = TopicTrackingState.create({ currentUser });
|
||||||
|
|
||||||
|
state.states["t112"] = {
|
||||||
|
last_read_post_number: null,
|
||||||
|
id: 112,
|
||||||
|
notification_level: NotificationLevels.TRACKING,
|
||||||
|
category_id: 1,
|
||||||
|
is_seen: false,
|
||||||
|
tags: ["foo"],
|
||||||
|
};
|
||||||
|
|
||||||
|
state.dismissNewTopic({
|
||||||
|
message_type: "dismiss_new",
|
||||||
|
topic_id: 112,
|
||||||
|
payload: { category_id: 2 },
|
||||||
|
});
|
||||||
|
assert.equal(state.states["t112"].is_seen, false);
|
||||||
|
state.dismissNewTopic({
|
||||||
|
message_type: "dismiss_new",
|
||||||
|
topic_id: 112,
|
||||||
|
payload: { category_id: 1 },
|
||||||
|
});
|
||||||
|
assert.equal(state.states["t112"].is_seen, true);
|
||||||
|
|
||||||
|
state.states["t112"].is_seen = false;
|
||||||
|
state.dismissNewTopic({
|
||||||
|
message_type: "dismiss_new",
|
||||||
|
topic_id: 112,
|
||||||
|
payload: { tag_id: "bar" },
|
||||||
|
});
|
||||||
|
assert.equal(state.states["t112"].is_seen, false);
|
||||||
|
state.dismissNewTopic({
|
||||||
|
message_type: "dismiss_new",
|
||||||
|
topic_id: 112,
|
||||||
|
payload: { tag_id: "foo" },
|
||||||
|
});
|
||||||
|
assert.equal(state.states["t112"].is_seen, true);
|
||||||
|
|
||||||
|
state.states["t112"].is_seen = false;
|
||||||
|
state.dismissNewTopic({
|
||||||
|
message_type: "dismiss_new",
|
||||||
|
topic_id: 112,
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
assert.equal(state.states["t112"].is_seen, true);
|
||||||
|
});
|
||||||
|
|
||||||
test("mute and unmute topic", function (assert) {
|
test("mute and unmute topic", function (assert) {
|
||||||
let currentUser = User.create({
|
let currentUser = User.create({
|
||||||
username: "chuck",
|
username: "chuck",
|
||||||
|
|
|
@ -898,10 +898,21 @@ class TopicsController < ApplicationController
|
||||||
if params[:include_subcategories] == 'true'
|
if params[:include_subcategories] == 'true'
|
||||||
category_ids = category_ids.concat(Category.where(parent_category_id: params[:category_id]).pluck(:id))
|
category_ids = category_ids.concat(Category.where(parent_category_id: params[:category_id]).pluck(:id))
|
||||||
end
|
end
|
||||||
DismissTopics.new(current_user, Topic.where(category_id: category_ids)).perform!
|
|
||||||
|
topic_scope =
|
||||||
|
if params[:tag_id]
|
||||||
|
Topic.where(category_id: category_ids).joins(:tags).where(tags: { name: params[:tag_id] })
|
||||||
|
else
|
||||||
|
Topic.where(category_id: category_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
DismissTopics.new(current_user, topic_scope).perform!
|
||||||
category_ids.each do |category_id|
|
category_ids.each do |category_id|
|
||||||
TopicTrackingState.publish_dismiss_new(current_user.id, category_id)
|
TopicTrackingState.publish_dismiss_new(current_user.id, category_id: category_id, tag_id: params[:tag_id])
|
||||||
end
|
end
|
||||||
|
elsif params[:tag_id].present?
|
||||||
|
DismissTopics.new(current_user, Topic.joins(:tags).where(tags: { name: params[:tag_id] })).perform!
|
||||||
|
TopicTrackingState.publish_dismiss_new(current_user.id, tag_id: params[:tag_id])
|
||||||
else
|
else
|
||||||
if params[:tracked].to_s == "true"
|
if params[:tracked].to_s == "true"
|
||||||
topics = TopicQuery.tracked_filter(TopicQuery.new(current_user).new_results, current_user.id)
|
topics = TopicQuery.tracked_filter(TopicQuery.new(current_user).new_results, current_user.id)
|
||||||
|
|
|
@ -215,8 +215,10 @@ 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(user_id, category_id = nil)
|
def self.publish_dismiss_new(user_id, category_id: nil, tag_id: nil)
|
||||||
payload = category_id ? { category_id: category_id } : {}
|
payload = {}
|
||||||
|
payload[:category_id] = category_id if category_id
|
||||||
|
payload[:tag_id] = tag_id if tag_id
|
||||||
message = {
|
message = {
|
||||||
message_type: "dismiss_new",
|
message_type: "dismiss_new",
|
||||||
payload: payload
|
payload: payload
|
||||||
|
|
|
@ -13,7 +13,12 @@ class DismissTopics
|
||||||
private
|
private
|
||||||
|
|
||||||
def rows
|
def rows
|
||||||
@rows ||= @topics_scope.where("created_at >= ?", since_date).order(created_at: :desc).limit(SiteSetting.max_new_topics).map do |topic|
|
@rows ||= @topics_scope
|
||||||
|
.joins("LEFT JOIN topic_users ON topic_users.topic_id = topics.id AND topic_users.user_id = #{@user.id}")
|
||||||
|
.where("topics.created_at >= ?", since_date)
|
||||||
|
.where("topic_users.id IS NULL")
|
||||||
|
.order("topics.created_at DESC")
|
||||||
|
.limit(SiteSetting.max_new_topics).map do |topic|
|
||||||
{
|
{
|
||||||
topic_id: topic.id,
|
topic_id: topic.id,
|
||||||
user_id: @user.id,
|
user_id: @user.id,
|
||||||
|
|
|
@ -2873,7 +2873,7 @@ RSpec.describe TopicsController do
|
||||||
it 'dismisses topics for main category' do
|
it 'dismisses topics for main category' do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
|
|
||||||
TopicTrackingState.expects(:publish_dismiss_new).with(user.id, category.id.to_s)
|
TopicTrackingState.expects(:publish_dismiss_new).with(user.id, category_id: category.id.to_s, tag_id: nil)
|
||||||
|
|
||||||
put "/topics/reset-new.json?category_id=#{category.id}"
|
put "/topics/reset-new.json?category_id=#{category.id}"
|
||||||
|
|
||||||
|
@ -2887,6 +2887,36 @@ RSpec.describe TopicsController do
|
||||||
expect(DismissedTopicUser.where(user_id: user.id).pluck(:topic_id).sort).to eq([category_topic.id, subcategory_topic.id].sort)
|
expect(DismissedTopicUser.where(user_id: user.id).pluck(:topic_id).sort).to eq([category_topic.id, subcategory_topic.id].sort)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'tag' do
|
||||||
|
fab!(:tag) { Fabricate(:tag) }
|
||||||
|
fab!(:tag_topic) { Fabricate(:topic) }
|
||||||
|
fab!(:topic_tag) { Fabricate(:topic_tag, topic: tag_topic, tag: tag) }
|
||||||
|
fab!(:topic) { Fabricate(:topic) }
|
||||||
|
|
||||||
|
it 'dismisses topics for tag' do
|
||||||
|
sign_in(user)
|
||||||
|
TopicTrackingState.expects(:publish_dismiss_new).with(user.id, tag_id: tag.name)
|
||||||
|
put "/topics/reset-new.json?tag_id=#{tag.name}"
|
||||||
|
expect(DismissedTopicUser.where(user_id: user.id).pluck(:topic_id)).to eq([tag_topic.id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'tag and category' do
|
||||||
|
fab!(:tag) { Fabricate(:tag) }
|
||||||
|
fab!(:tag_topic) { Fabricate(:topic) }
|
||||||
|
fab!(:category) { Fabricate(:category) }
|
||||||
|
fab!(:tag_and_category_topic) { Fabricate(:topic, category: category) }
|
||||||
|
fab!(:topic_tag) { Fabricate(:topic_tag, topic: tag_topic, tag: tag) }
|
||||||
|
fab!(:topic_tag2) { Fabricate(:topic_tag, topic: tag_and_category_topic, tag: tag) }
|
||||||
|
|
||||||
|
it 'dismisses topics for tag' do
|
||||||
|
sign_in(user)
|
||||||
|
TopicTrackingState.expects(:publish_dismiss_new).with(user.id, tag_id: tag.name, category_id: category.id.to_s)
|
||||||
|
put "/topics/reset-new.json?tag_id=#{tag.name}&category_id=#{category.id}"
|
||||||
|
expect(DismissedTopicUser.where(user_id: user.id).pluck(:topic_id)).to eq([tag_and_category_topic.id])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#feature_stats' do
|
describe '#feature_stats' do
|
||||||
|
|
|
@ -28,6 +28,12 @@ describe DismissTopics do
|
||||||
expect(dismissed_topic_user.created_at).not_to be_nil
|
expect(dismissed_topic_user.created_at).not_to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'respects seen topics' do
|
||||||
|
Fabricate(:topic_user, user: user, topic: topic1)
|
||||||
|
Fabricate(:topic_user, user: user, topic: topic2)
|
||||||
|
expect { described_class.new(user, Topic.all).perform! }.to change { DismissedTopicUser.count }.by(0)
|
||||||
|
end
|
||||||
|
|
||||||
it 'respects new_topic_duration_minutes' do
|
it 'respects new_topic_duration_minutes' do
|
||||||
user.user_option.update!(new_topic_duration_minutes: 70)
|
user.user_option.update!(new_topic_duration_minutes: 70)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue