FEATURE: new dismiss button for combined new and unread view (#21817)

Display modal for combined new and unread view with options:
- [x] Dismiss new topics
- [x] Dismiss new posts
- [ ] Stop tracking these topics so they stop appearing in my new list
This commit is contained in:
Krzysztof Kotlarek 2023-06-07 10:06:57 +10:00 committed by GitHub
parent 899969fd5d
commit af74cf5c77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 374 additions and 31 deletions

View File

@ -1,5 +1,6 @@
import { alias, empty, equal, gt, not, readOnly } from "@ember/object/computed";
import BulkTopicSelection from "discourse/mixins/bulk-topic-selection";
import DismissTopics from "discourse/mixins/dismiss-topics";
import DiscoveryController from "discourse/controllers/discovery";
import I18n from "I18n";
import Topic from "discourse/models/topic";
@ -56,6 +57,32 @@ const controllerOpts = {
return this._isFilterPage(filter, "new") && topicsLength > 0;
},
callResetNew(dismissPosts = false, dismissTopics = false, untrack = false) {
const tracked =
(this.router.currentRoute.queryParams["f"] ||
this.router.currentRoute.queryParams["filter"]) === "tracked";
let topicIds = this.selected
? this.selected.map((topic) => topic.id)
: null;
Topic.resetNew(this.category, !this.noSubcategories, {
tracked,
topicIds,
dismissPosts,
dismissTopics,
untrack,
}).then((result) => {
if (result.topic_ids) {
this.topicTrackingState.removeTopics(result.topic_ids);
}
this.send(
"refresh",
tracked ? { skipResettingParams: ["filter", "f"] } : {}
);
});
},
// Show newly inserted topics
@action
showInserted(event) {
@ -114,26 +141,6 @@ const controllerOpts = {
}
});
},
resetNew() {
const tracked =
(this.router.currentRoute.queryParams["f"] ||
this.router.currentRoute.queryParams["filter"]) === "tracked";
let topicIds = this.selected
? this.selected.map((topic) => topic.id)
: null;
Topic.resetNew(this.category, !this.noSubcategories, {
tracked,
topicIds,
}).then(() =>
this.send(
"refresh",
tracked ? { skipResettingParams: ["filter", "f"] } : {}
)
);
},
},
afterRefresh(filter, list, listModel = list) {
@ -213,4 +220,8 @@ const controllerOpts = {
},
};
export default DiscoveryController.extend(controllerOpts, BulkTopicSelection);
export default DiscoveryController.extend(
controllerOpts,
BulkTopicSelection,
DismissTopics
);

View File

@ -0,0 +1,13 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object";
export default class DismissNewController extends Controller.extend(
ModalFunctionality
) {
@action
dismiss() {
this.dismissCallback();
this.send("closeModal");
}
}

View File

@ -2,6 +2,7 @@ import DiscoverySortableController from "discourse/controllers/discovery-sortabl
import { inject as controller } from "@ember/controller";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import BulkTopicSelection from "discourse/mixins/bulk-topic-selection";
import DismissTopics from "discourse/mixins/dismiss-topics";
import FilterModeMixin from "discourse/mixins/filter-mode";
import I18n from "I18n";
import NavItem from "discourse/models/nav-item";
@ -13,6 +14,7 @@ import { inject as service } from "@ember/service";
export default DiscoverySortableController.extend(
BulkTopicSelection,
DismissTopics,
FilterModeMixin,
{
application: controller(),
@ -91,8 +93,7 @@ export default DiscoverySortableController.extend(
return this._isFilterPage(filter, "new") && topicsLength > 0;
},
@action
resetNew() {
callResetNew(dismissPosts = false, dismissTopics = false, untrack = false) {
const tracked =
(this.router.currentRoute.queryParams["f"] ||
this.router.currentRoute.queryParams["filter"]) === "tracked";
@ -103,9 +104,15 @@ export default DiscoverySortableController.extend(
tracked,
tag: this.tag,
topicIds,
}).then(() =>
this.refresh(tracked ? { skipResettingParams: ["filter", "f"] } : {})
);
dismissPosts,
dismissTopics,
untrack,
}).then((result) => {
if (result.topic_ids) {
this.topicTrackingState.removeTopics(result.topic_ids);
}
this.refresh(tracked ? { skipResettingParams: ["filter", "f"] } : {});
});
},
@action

View File

@ -0,0 +1,30 @@
import Mixin from "@ember/object/mixin";
import User from "discourse/models/user";
import showModal from "discourse/lib/show-modal";
import I18n from "I18n";
export default Mixin.create({
actions: {
resetNew() {
const user = User.current();
if (!user.new_new_view_enabled) {
return this.callResetNew();
}
const controller = showModal("dismiss-new", {
model: {
dismissTopics: true,
dismissPosts: true,
},
titleTranslated: I18n.t("topics.bulk.dismiss_new_modal.title"),
});
controller.set("dismissCallback", () => {
this.callResetNew(
controller.model.dismissPosts,
controller.model.dismissTopics,
controller.model.untrack
);
});
},
},
});

View File

@ -851,6 +851,18 @@ Topic.reopenClass({
data.topic_ids = topicIds;
}
if (opts.dismissPosts) {
data.dismiss_posts = opts.dismissPosts;
}
if (opts.dismissTopics) {
data.dismiss_topics = opts.dismissTopics;
}
if (opts.untrack) {
data.untrack = opts.untrack;
}
return ajax("/topics/reset-new", { type: "PUT", data });
},

View File

@ -0,0 +1,29 @@
<DModalBody>
<p>
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.topics"
@checked={{this.model.dismissTopics}}
@class="dismiss-topics"
/>
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.posts"
@checked={{this.model.dismissPosts}}
@class="dismiss-posts"
/>
<PreferenceCheckbox
@labelKey="topics.bulk.dismiss_new_modal.untrack"
@checked={{this.model.untrack}}
@class="untrack"
/>
</p>
</DModalBody>
<div class="modal-footer">
<DButton
@class="btn-primary"
@action="dismiss"
@icon="check"
@id="dismiss-read-confirm"
@label="topics.bulk.dismiss"
/>
</div>

View File

@ -1059,7 +1059,20 @@ class TopicsController < ApplicationController
elsif params[:tag_id].present?
Topic.joins(:tags).where(tags: { name: params[:tag_id] })
else
new_results = TopicQuery.new(current_user).new_results(limit: false)
new_results =
if current_user.new_new_view_enabled?
if (params[:dismiss_topics] && params[:dismiss_posts])
TopicQuery.new(current_user).new_and_unread_results(limit: false)
elsif params[:dismiss_topics]
TopicQuery.new(current_user).new_results(limit: false)
elsif params[:dismiss_posts]
TopicQuery.new(current_user).unread_results(limit: false)
else
Topic.none
end
else
TopicQuery.new(current_user).new_results(limit: false)
end
if params[:tracked].to_s == "true"
TopicQuery.tracked_filter(new_results, current_user.id)
else
@ -1077,11 +1090,30 @@ class TopicsController < ApplicationController
topic_scope = topic_scope.where(id: topic_ids)
end
dismissed_topic_ids =
TopicsBulkAction.new(current_user, topic_scope.pluck(:id), type: "dismiss_topics").perform!
TopicTrackingState.publish_dismiss_new(current_user.id, topic_ids: dismissed_topic_ids)
dismissed_topic_ids = []
dismissed_post_topic_ids = []
if !current_user.new_new_view_enabled? || params[:dismiss_topics]
dismissed_topic_ids =
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
render body: nil
if params[:dismiss_posts]
if params[:untrack]
dismissed_post_topic_ids =
TopicsBulkAction.new(
current_user,
topic_scope.pluck(:id),
type: "change_notification_level",
notification_level_id: NotificationLevels.topic_levels[:regular],
).perform!
else
dismissed_post_topic_ids =
TopicsBulkAction.new(current_user, topic_scope.pluck(:id), type: "dismiss_posts").perform!
end
end
render_json_dump topic_ids: dismissed_topic_ids.concat(dismissed_post_topic_ids).uniq
end
def convert_topic

View File

@ -2866,6 +2866,11 @@ en:
dismiss_tooltip: "Dismiss just new posts or stop tracking topics"
also_dismiss_topics: "Stop tracking these topics so they never show up as unread for me again"
dismiss_new: "Dismiss New"
dismiss_new_modal:
title: "Dismiss new"
topics: "Dismiss new topics"
posts: "Dismiss new posts"
untrack: "Stop tracking these topics so they stop appearing in my new list"
dismiss_new_with_selected:
one: "Dismiss New (%{count})"
other: "Dismiss New (%{count})"

View File

@ -4061,6 +4061,168 @@ RSpec.describe TopicsController do
end
end
end
describe "new and unread" do
fab!(:group) { Fabricate(:group) }
fab!(:new_topic) { Fabricate(:topic) }
fab!(:unread_topic) { Fabricate(:topic, highest_post_number: 3) }
fab!(:topic_user) do
Fabricate(
:topic_user,
topic: unread_topic,
user: user,
notification_level: NotificationLevels.topic_levels[:tracking],
last_read_post_number: 1,
)
end
before do
create_post(topic: unread_topic)
create_post(topic: unread_topic)
user.groups << group
SiteSetting.experimental_new_new_view_groups = group.id
sign_in(user)
end
it "dismisses new topics" do
put "/topics/reset-new.json"
topics = TopicQuery.new(user).new_and_unread_results(limit: false)
expect(topics).to eq([unread_topic, new_topic])
expect(response.status).to eq(200)
expect(response.parsed_body["topic_ids"]).to eq([])
put "/topics/reset-new.json", params: { dismiss_topics: true }
expect(response.status).to eq(200)
expect(response.parsed_body["topic_ids"]).to eq([new_topic.id])
topics = TopicQuery.new(user).new_and_unread_results(limit: false)
expect(topics).to eq([unread_topic])
expect(DismissedTopicUser.where(user: user).count).to eq(1)
expect(DismissedTopicUser.where(user: user).first.topic_id).to eq(new_topic.id)
expect(topic_user.reload.notification_level).to eq(
NotificationLevels.topic_levels[:tracking],
)
end
it "dismisses unread topics" do
put "/topics/reset-new.json"
expect(response.status).to eq(200)
expect(response.parsed_body["topic_ids"]).to eq([])
topics = TopicQuery.new(user).new_and_unread_results(limit: false)
expect(topics).to eq([unread_topic, new_topic])
put "/topics/reset-new.json", params: { dismiss_posts: true }
expect(response.status).to eq(200)
expect(response.parsed_body["topic_ids"]).to eq([unread_topic.id])
topics = TopicQuery.new(user).new_and_unread_results(limit: false)
expect(topics).to eq([new_topic])
expect(DismissedTopicUser.count).to eq(0)
expect(topic_user.reload.notification_level).to eq(
NotificationLevels.topic_levels[:tracking],
)
end
it "untrack topics" do
expect(topic_user.notification_level).to eq(NotificationLevels.topic_levels[:tracking])
put "/topics/reset-new.json", params: { dismiss_posts: true, untrack: true }
expect(response.status).to eq(200)
expect(response.parsed_body["topic_ids"]).to eq([unread_topic.id])
expect(topic_user.reload.notification_level).to eq(
NotificationLevels.topic_levels[:regular],
)
end
it "dismisses new topics, unread posts and untrack" do
put "/topics/reset-new.json",
params: {
dismiss_topics: true,
dismiss_posts: true,
untrack: true,
}
expect(response.status).to eq(200)
expect(response.parsed_body["topic_ids"]).to eq([new_topic.id, unread_topic.id])
topics = TopicQuery.new(user).new_and_unread_results(limit: false)
expect(topics).to be_empty
expect(DismissedTopicUser.where(user: user).count).to eq(1)
expect(DismissedTopicUser.where(user: user).first.topic_id).to eq(new_topic.id)
expect(user.topic_users.map(&:notification_level).uniq).to eq(
[NotificationLevels.topic_levels[:regular]],
)
end
context "when category" do
fab!(:category) { Fabricate(:category) }
fab!(:new_topic_2) { Fabricate(:topic, category: category) }
fab!(:unread_topic_2) { Fabricate(:topic, category: category, highest_post_number: 3) }
fab!(:topic_user) do
Fabricate(
:topic_user,
topic: unread_topic_2,
user: user,
notification_level: NotificationLevels.topic_levels[:tracking],
last_read_post_number: 1,
)
end
it "dismisses new topics, unread posts and untrack for specific category" do
topics = TopicQuery.new(user).new_and_unread_results(limit: false)
expect(topics).to match_array([new_topic, new_topic_2, unread_topic, unread_topic_2])
put "/topics/reset-new.json",
params: {
dismiss_topics: true,
dismiss_posts: true,
untrack: true,
category_id: category.id,
}
expect(response.status).to eq(200)
expect(response.parsed_body["topic_ids"]).to eq([new_topic_2.id, unread_topic_2.id])
topics = TopicQuery.new(user).new_and_unread_results(limit: false)
expect(topics).to match_array([new_topic, unread_topic])
end
end
context "when tag" do
fab!(:tag) { Fabricate(:tag) }
fab!(:new_topic_2) { Fabricate(:topic) }
fab!(:unread_topic_2) { Fabricate(:topic, highest_post_number: 3) }
fab!(:topic_user) do
Fabricate(
:topic_user,
topic: unread_topic_2,
user: user,
notification_level: NotificationLevels.topic_levels[:tracking],
last_read_post_number: 1,
)
end
fab!(:topic_tag) { Fabricate(:topic_tag, topic: new_topic_2, tag: tag) }
fab!(:topic_tag_2) { Fabricate(:topic_tag, topic: unread_topic_2, tag: tag) }
it "dismisses new topics, unread posts and untrack for specific tag" do
topics = TopicQuery.new(user).new_and_unread_results(limit: false)
expect(topics).to match_array([new_topic, new_topic_2, unread_topic, unread_topic_2])
put "/topics/reset-new.json",
params: {
dismiss_topics: true,
dismiss_posts: true,
untrack: true,
tag_id: tag.name,
}
expect(response.status).to eq(200)
expect(response.parsed_body["topic_ids"]).to eq([new_topic_2.id, unread_topic_2.id])
topics = TopicQuery.new(user).new_and_unread_results(limit: false)
expect(topics).to match_array([new_topic, unread_topic])
end
end
end
end
describe "#feature_stats" do

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
describe "Filtering topics", type: :system, js: true do
fab!(:user) { Fabricate(:user) }
let(:topic_list) { PageObjects::Components::TopicList.new }
let(:dismiss_new_modal) { PageObjects::Modals::DismissNew.new }
fab!(:group) { Fabricate(:group).tap { |g| g.add(user) } }
fab!(:topic) { Fabricate(:topic) }
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").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
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module PageObjects
module Modals
class DismissNew < PageObjects::Modals::Base
def has_dismiss_topics_checked?
find(".dismiss-topics label").has_checked_field?
end
def has_dismiss_posts_checked?
find(".dismiss-posts label").has_checked_field?
end
def has_untrack_unchecked?
find(".untrack label").has_no_checked_field?
end
end
end
end