FEATURE: Allow "move to inbox" and "move to archive" for private messages using new bulk topic dropdown (#27236)

This commit re-introduces the "Move to Inbox" and "Move to Archive"
bulk topic actions, which we had in the old modal but had not yet added
to the new "experimental" dropdown, which isn't really experimental at
this point.

Once this is merged we can remove the old modal and only
rely on the new dropdown.
This commit is contained in:
Martin Brennan 2024-06-03 14:37:28 +10:00 committed by GitHub
parent a8f0a927a0
commit 4b2bd4d682
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 398 additions and 30 deletions

View File

@ -46,6 +46,9 @@ export default class BulkSelectTopicsDropdown extends Component {
id: "update-category",
icon: "pencil-alt",
name: i18n("topic_bulk_actions.update_category.name"),
visible: ({ topics }) => {
return !topics.some((t) => t.isPrivateMessage);
},
},
{
id: "update-notifications",
@ -72,6 +75,19 @@ export default class BulkSelectTopicsDropdown extends Component {
id: "archive-topics",
icon: "folder",
name: i18n("topic_bulk_actions.archive_topics.name"),
visible: ({ topics }) => !topics.some((t) => t.isPrivateMessage),
},
{
id: "archive-messages",
icon: "archive",
name: i18n("topic_bulk_actions.archive_messages.name"),
visible: ({ topics }) => topics.every((t) => t.isPrivateMessage),
},
{
id: "move-messages-to-inbox",
icon: "envelope",
name: i18n("topic_bulk_actions.move_messages_to_inbox.name"),
visible: ({ topics }) => topics.every((t) => t.isPrivateMessage),
},
{
id: "unlist-topics",
@ -167,17 +183,17 @@ export default class BulkSelectTopicsDropdown extends Component {
}
@action
async onSelect(id) {
async onSelect(actionId) {
await this.dMenu.close();
switch (id) {
switch (actionId) {
case "update-category":
this.showBulkTopicActionsModal(id, "change_category", {
this.showBulkTopicActionsModal(actionId, "change_category", {
description: i18n(`topic_bulk_actions.update_category.description`),
});
break;
case "update-notifications":
this.showBulkTopicActionsModal(id, "notification_level", {
this.showBulkTopicActionsModal(actionId, "notification_level", {
description: i18n(
`topic_bulk_actions.update_notifications.description`
),
@ -191,6 +207,15 @@ export default class BulkSelectTopicsDropdown extends Component {
case "archive-topics":
this.showBulkTopicActionsModal("archive", "archive_topics");
break;
case "archive-messages":
this.showBulkTopicActionsModal("archive_messages", "archive_messages");
break;
case "move-messages-to-inbox":
this.showBulkTopicActionsModal(
"move_messages_to_inbox",
"move_messages_to_inbox"
);
break;
case "unlist-topics":
this.showBulkTopicActionsModal("unlist", "unlist_topics");
break;
@ -198,33 +223,37 @@ export default class BulkSelectTopicsDropdown extends Component {
this.showBulkTopicActionsModal("relist", "relist_topics");
break;
case "append-tags":
this.showBulkTopicActionsModal(id, "choose_append_tags");
this.showBulkTopicActionsModal(actionId, "choose_append_tags");
break;
case "replace-tags":
this.showBulkTopicActionsModal(id, "change_tags");
this.showBulkTopicActionsModal(actionId, "change_tags");
break;
case "remove-tags":
this.showBulkTopicActionsModal(id, "remove_tags");
this.showBulkTopicActionsModal(actionId, "remove_tags");
break;
case "delete-topics":
this.showBulkTopicActionsModal("delete", "delete");
break;
case "reset-bump-dates":
this.showBulkTopicActionsModal(id, "reset_bump_dates", {
this.showBulkTopicActionsModal(actionId, "reset_bump_dates", {
description: i18n(`topic_bulk_actions.reset_bump_dates.description`),
});
break;
case "defer":
this.showBulkTopicActionsModal(id, "defer", {
this.showBulkTopicActionsModal(actionId, "defer", {
description: i18n(`topic_bulk_actions.defer.description`),
});
break;
default:
if (_customOnSelection[id]) {
this.showBulkTopicActionsModal(id, _customOnSelection[id].label, {
custom: true,
setComponent: _customOnSelection[id].setComponent,
});
if (_customOnSelection[actionId]) {
this.showBulkTopicActionsModal(
actionId,
_customOnSelection[actionId].label,
{
custom: true,
setComponent: _customOnSelection[actionId].setComponent,
}
);
}
}
}

View File

@ -1,5 +1,6 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { getOwner } from "@ember/application";
import { Input } from "@ember/component";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
@ -145,6 +146,20 @@ export default class BulkTopicActions extends Component {
t.set("archived", true)
);
break;
case "archive_messages":
case "move_messages_to_inbox":
let userPrivateMessages = getOwner(this).lookup(
"controller:user-private-messages"
);
let params = { type: this.model.action };
if (userPrivateMessages.isGroup) {
params.group = userPrivateMessages.groupFilter;
}
this.performAndRefresh(params);
break;
case "unlist":
this.forEachPerformed({ type: "unlist" }, (t) =>
t.set("unlisted", true)

View File

@ -0,0 +1,225 @@
import { getOwner } from "@ember/application";
import { click, render } from "@ember/test-helpers";
import { hbs } from "ember-cli-htmlbars";
import { module, test } from "qunit";
import BulkSelectHelper from "discourse/lib/bulk-select-helper";
import { TOPIC_VISIBILITY_REASONS } from "discourse/lib/constants";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
const REGULAR_TOPIC_ID = 123;
const PM_TOPIC_ID = 124;
const UNLISTED_TOPIC_ID = 125;
function createBulkSelectHelper(testThis, opts = {}) {
const store = getOwner(testThis).lookup("service:store");
const regularTopic = store.createRecord("topic", {
id: REGULAR_TOPIC_ID,
visible: true,
});
const pmTopic = store.createRecord("topic", {
id: PM_TOPIC_ID,
visible: true,
archetype: "private_message",
});
const unlistedTopic = store.createRecord("topic", {
id: UNLISTED_TOPIC_ID,
visibility_reason_id: TOPIC_VISIBILITY_REASONS.manually_unlisted,
visible: false,
});
const topics = [regularTopic, pmTopic, unlistedTopic].filter((t) => {
if (opts.topicIds) {
return opts.topicIds.includes(t.id);
} else {
return true;
}
});
const bulkSelectHelper = new BulkSelectHelper(testThis);
topics.forEach((t) => {
bulkSelectHelper.selected.addObject(t);
});
return bulkSelectHelper;
}
module("Integration | Component | BulkSelectTopicsDropdown", function (hooks) {
setupRenderingTest(hooks);
test("actions all topics can perform", async function (assert) {
this.currentUser.admin = true;
this.bulkSelectHelper = createBulkSelectHelper(this);
await render(
hbs`<BulkSelectTopicsDropdown @bulkSelectHelper={{this.bulkSelectHelper}} />`
);
await click(".bulk-select-topics-dropdown-trigger");
assert
.dom(".fk-d-menu__inner-content .dropdown-menu__item")
.exists({ count: 7 });
[
"update-notifications",
"reset-bump-dates",
"close-topics",
"append-tags",
"replace-tags",
"remove-tags",
"delete-topics",
].forEach((action) => {
assert
.dom(`.fk-d-menu__inner-content .dropdown-menu__item .${action}`)
.exists();
});
});
test("does not allow unlisting topics that are already unlisted", async function (assert) {
this.currentUser.admin = true;
this.bulkSelectHelper = createBulkSelectHelper(this, {
topicIds: [UNLISTED_TOPIC_ID],
});
await render(
hbs`<BulkSelectTopicsDropdown @bulkSelectHelper={{this.bulkSelectHelper}} />`
);
await click(".bulk-select-topics-dropdown-trigger");
assert
.dom(".fk-d-menu__inner-content .dropdown-menu__item .unlist-topics")
.doesNotExist();
});
test("does not allow relisting topics that are already visible", async function (assert) {
this.currentUser.admin = true;
this.bulkSelectHelper = createBulkSelectHelper(this, {
topicIds: [REGULAR_TOPIC_ID],
});
await render(
hbs`<BulkSelectTopicsDropdown @bulkSelectHelper={{this.bulkSelectHelper}} />`
);
await click(".bulk-select-topics-dropdown-trigger");
assert
.dom(".fk-d-menu__inner-content .dropdown-menu__item .relist-topics")
.doesNotExist();
});
test("allows deferring topics if the user has the preference enabled", async function (assert) {
this.currentUser.admin = true;
this.currentUser.user_option.enable_defer = true;
this.bulkSelectHelper = createBulkSelectHelper(this);
await render(
hbs`<BulkSelectTopicsDropdown @bulkSelectHelper={{this.bulkSelectHelper}} />`
);
await click(".bulk-select-topics-dropdown-trigger");
assert
.dom(".fk-d-menu__inner-content .dropdown-menu__item .defer")
.exists();
});
test("does not allow tagging actions if tagging_enabled is false", async function (assert) {
this.currentUser.admin = true;
this.siteSettings.tagging_enabled = false;
this.bulkSelectHelper = createBulkSelectHelper(this);
await render(
hbs`<BulkSelectTopicsDropdown @bulkSelectHelper={{this.bulkSelectHelper}} />`
);
await click(".bulk-select-topics-dropdown-trigger");
["append-tags", "replace-tags", "remove-tags"].forEach((action) => {
assert
.dom(`.fk-d-menu__inner-content .dropdown-menu__item .${action}`)
.doesNotExist();
});
});
test("does not allow tagging actions if user cannot manage topic", async function (assert) {
this.bulkSelectHelper = createBulkSelectHelper(this);
await render(
hbs`<BulkSelectTopicsDropdown @bulkSelectHelper={{this.bulkSelectHelper}} />`
);
await click(".bulk-select-topics-dropdown-trigger");
["append-tags", "replace-tags", "remove-tags"].forEach((action) => {
assert
.dom(`.fk-d-menu__inner-content .dropdown-menu__item .${action}`)
.doesNotExist();
});
});
test("does not allow deleting topics if user is not staff", async function (assert) {
this.bulkSelectHelper = createBulkSelectHelper(this);
await render(
hbs`<BulkSelectTopicsDropdown @bulkSelectHelper={{this.bulkSelectHelper}} />`
);
await click(".bulk-select-topics-dropdown-trigger");
assert
.dom(".fk-d-menu__inner-content .dropdown-menu__item .delete-topics")
.doesNotExist();
});
test("does not allow unlisting or relisting PM topics", async function (assert) {
this.currentUser.admin = true;
this.bulkSelectHelper = createBulkSelectHelper(this, {
topicIds: [PM_TOPIC_ID],
});
await render(
hbs`<BulkSelectTopicsDropdown @bulkSelectHelper={{this.bulkSelectHelper}} />`
);
await click(".bulk-select-topics-dropdown-trigger");
assert
.dom(".fk-d-menu__inner-content .dropdown-menu__item .relist-topics")
.doesNotExist();
assert
.dom(".fk-d-menu__inner-content .dropdown-menu__item .unlist-topics")
.doesNotExist();
});
test("does not allow updating category for PMs", async function (assert) {
this.currentUser.admin = true;
this.bulkSelectHelper = createBulkSelectHelper(this, {
topicIds: [PM_TOPIC_ID],
});
await render(
hbs`<BulkSelectTopicsDropdown @bulkSelectHelper={{this.bulkSelectHelper}} />`
);
await click(".bulk-select-topics-dropdown-trigger");
assert
.dom(".fk-d-menu__inner-content .dropdown-menu__item .update-category")
.doesNotExist();
});
test("allows moving to archive and moving to inbox for PMs", async function (assert) {
this.currentUser.admin = true;
this.bulkSelectHelper = createBulkSelectHelper(this, {
topicIds: [PM_TOPIC_ID],
});
await render(
hbs`<BulkSelectTopicsDropdown @bulkSelectHelper={{this.bulkSelectHelper}} />`
);
await click(".bulk-select-topics-dropdown-trigger");
assert
.dom(".fk-d-menu__inner-content .dropdown-menu__item .archive-messages")
.exists();
assert
.dom(
".fk-d-menu__inner-content .dropdown-menu__item .move-messages-to-inbox"
)
.exists();
assert
.dom(".fk-d-menu__inner-content .dropdown-menu__item .archive-topics")
.doesNotExist();
});
});

View File

@ -3018,6 +3018,7 @@ en:
close_topics: "Close Topics"
archive_topics: "Archive Topics"
move_messages_to_inbox: "Move to Inbox"
archive_messages: "Move to Archive"
notification_level: "Notifications…"
change_notification_level: "Change Notification Level"
choose_new_category: "Choose the new category for the topics:"
@ -3078,15 +3079,19 @@ en:
topic_bulk_actions:
close_topics:
name: "Close Topics"
name: "Close"
note: "Note"
optional: (optional)
archive_topics:
name: "Archive Topics"
name: "Archive"
archive_messages:
name: "Move to Archive"
move_messages_to_inbox:
name: "Move to Inbox"
unlist_topics:
name: "Unlist Topics"
name: "Unlist"
relist_topics:
name: "Relist Topics"
name: "Relist"
remove_tags:
name: "Remove Tags"
append_tags:
@ -3094,7 +3099,7 @@ en:
replace_tags:
name: "Replace Tags"
delete_topics:
name: "Delete Topics"
name: "Delete"
update_category:
name: "Update Category"
description: "Choose the new category for the selected topics"

View File

@ -26,3 +26,12 @@ Fabricator(:private_message_topic, from: :topic) do
]
end
end
Fabricator(:group_private_message_topic, from: :topic) do
transient :recipient_group
category_id { nil }
title { sequence(:title) { |i| "This is a private message #{i} to a group" } }
archetype "private_message"
topic_allowed_users { |t| [Fabricate.build(:topic_allowed_user, user: t[:user])] }
topic_allowed_groups { |t| [Fabricate.build(:topic_allowed_group, group: t[:recipient_group])] }
end

View File

@ -11,6 +11,20 @@ describe "Topic bulk select", type: :system do
let(:topic_page) { PageObjects::Pages::Topic.new }
let(:topic_bulk_actions_modal) { PageObjects::Modals::TopicBulkActions.new }
def open_bulk_actions_modal(topics_to_select = nil, action)
topic_list_header.click_bulk_select_button
if !topics_to_select
topic_list.click_topic_checkbox(topics.last)
else
topics_to_select.each { |topic| topic_list.click_topic_checkbox(topic) }
end
topic_list_header.click_bulk_select_topics_dropdown
topic_list_header.click_bulk_button(action)
expect(topic_bulk_actions_modal).to be_open
end
context "when appending tags" do
fab!(:tag1) { Fabricate(:tag) }
fab!(:tag2) { Fabricate(:tag) }
@ -22,17 +36,7 @@ describe "Topic bulk select", type: :system do
sign_in(admin)
visit("/latest")
topic_list_header.click_bulk_select_button
if !topics_to_select
topic_list.click_topic_checkbox(topics.last)
else
topics_to_select.each { |topic| topic_list.click_topic_checkbox(topic) }
end
topic_list_header.click_bulk_select_topics_dropdown
topic_list_header.click_bulk_button("append-tags")
expect(topic_bulk_actions_modal).to be_open
open_bulk_actions_modal(topics_to_select, "append-tags")
end
context "when in mobile", mobile: true do
@ -241,4 +245,85 @@ describe "Topic bulk select", type: :system do
expect(topic_list).to have_no_topics
end
end
context "when working with private messages" do
fab!(:private_message_1) do
Fabricate(:private_message_topic, user: admin, recipient: user, participant_count: 2)
end
fab!(:private_message_post_1) { Fabricate(:post, topic: private_message_1, user: admin) }
fab!(:private_message_post_2) { Fabricate(:post, topic: private_message_1, user: user) }
fab!(:group)
fab!(:group_private_message) do
Fabricate(:group_private_message_topic, user: admin, recipient_group: group)
end
before do
TopicUser.change(
admin.id,
private_message_1,
notification_level: TopicUser.notification_levels[:tracking],
)
TopicUser.update_last_read(admin, private_message_1.id, 1, 1, 1)
GroupUser.create!(user: admin, group: group)
end
it "allows moving private messages to the Archive" do
sign_in(admin)
visit("/u/#{admin.username}/messages")
expect(page).to have_content(private_message_1.title)
open_bulk_actions_modal([private_message_1], "archive-messages")
topic_bulk_actions_modal.click_bulk_topics_confirm
expect(page).to have_content(I18n.t("js.topics.bulk.completed"))
visit("/u/#{admin.username}/messages/archive")
expect(page).to have_content(private_message_1.title)
expect(UserArchivedMessage.exists?(user_id: admin.id, topic_id: private_message_1.id)).to eq(
true,
)
end
it "allows moving private messages to the Inbox" do
UserArchivedMessage.create!(user: admin, topic: private_message_1)
sign_in(admin)
visit("/u/#{admin.username}/messages/archive")
expect(page).to have_content(private_message_1.title)
open_bulk_actions_modal([private_message_1], "move-messages-to-inbox")
topic_bulk_actions_modal.click_bulk_topics_confirm
expect(page).to have_content(I18n.t("js.topics.bulk.completed"))
visit("/u/#{admin.username}/messages")
expect(page).to have_content(private_message_1.title)
end
it "allows moving group private messages to the scoped group Archive" do
sign_in(admin)
visit("/u/#{admin.username}/messages/group/#{group.name}")
expect(page).to have_content(group_private_message.title)
open_bulk_actions_modal([group_private_message], "archive-messages")
topic_bulk_actions_modal.click_bulk_topics_confirm
expect(page).to have_content(I18n.t("js.topics.bulk.completed"))
visit("/u/#{admin.username}/messages/group/#{group.name}/archive")
expect(page).to have_content(group_private_message.title)
end
it "allows moving group private messages to the scoped group Inbox" do
GroupArchivedMessage.create!(group: group, topic: group_private_message)
sign_in(admin)
visit("/u/#{admin.username}/messages/group/#{group.name}/archive")
expect(page).to have_content(group_private_message.title)
open_bulk_actions_modal([group_private_message], "move-messages-to-inbox")
topic_bulk_actions_modal.click_bulk_topics_confirm
expect(page).to have_content(I18n.t("js.topics.bulk.completed"))
visit("/u/#{admin.username}/messages/group/#{group.name}")
expect(page).to have_content(group_private_message.title)
end
context "when in mobile" do
it "is working", mobile: true do
# behavior is already tested on desktop, we simply ensure
# the general workflow is working on mobile
sign_in(admin)
visit("/u/#{admin.username}/messages")
open_bulk_actions_modal([private_message_1], "archive-messages")
end
end
end
end