diff --git a/assets/javascripts/discourse/components/assign-button.gjs b/assets/javascripts/discourse/components/assign-button.gjs new file mode 100644 index 00000000..63c0915a --- /dev/null +++ b/assets/javascripts/discourse/components/assign-button.gjs @@ -0,0 +1,66 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import DButton from "discourse/components/d-button"; + +export default class AssignButton extends Component { + static shouldRender(args) { + return !args.post.firstPost; + } + + static hidden(args) { + return args.post.assigned_to_user?.id !== args.state.currentUser.id; + } + + @service taskActions; + + get icon() { + return this.isAssigned ? "user-times" : "user-plus"; + } + + get isAssigned() { + return this.args.post.assigned_to_user || this.args.post.assigned_to_group; + } + + get title() { + return this.isAssigned + ? "discourse_assign.unassign_post.title" + : "discourse_assign.assign_post.title"; + } + + @action + acceptAnswer() { + if (this.isAssigned) { + unassignPost(this.args.post, this.taskActions); + } else { + assignPost(this.args.post, this.taskActions); + } + } + + +} + +// TODO (glimmer-post-menu): Remove these exported functions and move the code into the button action after the widget code is removed +export function assignPost(post, taskActions) { + taskActions.showAssignModal(post, { + isAssigned: false, + targetType: "Post", + }); +} + +export async function unassignPost(post, taskActions) { + await taskActions.unassign(post.id, "Post"); + delete post.topic.indirectly_assigned_to[post.id]; +} diff --git a/assets/javascripts/discourse/initializers/extend-for-assigns.js b/assets/javascripts/discourse/initializers/extend-for-assigns.js index 2341fba5..35b62daf 100644 --- a/assets/javascripts/discourse/initializers/extend-for-assigns.js +++ b/assets/javascripts/discourse/initializers/extend-for-assigns.js @@ -10,10 +10,15 @@ import { registerTopicFooterDropdown } from "discourse/lib/register-topic-footer import { escapeExpression } from "discourse/lib/utilities"; import RawHtml from "discourse/widgets/raw-html"; import RenderGlimmer from "discourse/widgets/render-glimmer"; +import { withSilencedDeprecations } from "discourse-common/lib/deprecated"; import getURL from "discourse-common/lib/get-url"; import { iconHTML, iconNode } from "discourse-common/lib/icon-library"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "I18n"; +import AssignButton, { + assignPost, + unassignPost, +} from "../components/assign-button"; import BulkActionsAssignUser from "../components/bulk-actions/bulk-assign-user"; import EditTopicAssignments from "../components/modal/edit-topic-assignments"; import TopicLevelAssignMenu from "../components/topic-level-assign-menu"; @@ -314,47 +319,9 @@ function initialize(api) { }, before: "top", }); - if (api.getCurrentUser()?.can_assign) { - api.addPostMenuButton("assign", (post) => { - if (post.firstPost) { - return; - } - if (post.assigned_to_user || post.assigned_to_group) { - return { - action: "unassignPost", - icon: "user-times", - className: "unassign-post", - title: "discourse_assign.unassign_post.title", - position: - post.assigned_to_user?.id === api.getCurrentUser().id - ? "first" - : "second-last-hidden", - }; - } else { - return { - action: "assignPost", - icon: "user-plus", - className: "assign-post", - title: "discourse_assign.assign_post.title", - position: "second-last-hidden", - }; - } - }); - api.attachWidgetAction("post", "assignPost", function () { - const taskActions = getOwner(this).lookup("service:task-actions"); - taskActions.showAssignModal(this.model, { - isAssigned: false, - targetType: "Post", - }); - }); - - api.attachWidgetAction("post", "unassignPost", function () { - const taskActions = getOwner(this).lookup("service:task-actions"); - taskActions.unassign(this.model.id, "Post").then(() => { - delete this.model.topic.indirectly_assigned_to[this.model.id]; - }); - }); + if (api.getCurrentUser()?.can_assign) { + customizePostMenu(api); } } @@ -528,7 +495,9 @@ function initialize(api) { return new RenderGlimmer( this, "p.assigned-to", - hbs``, + hbs` + `, { assignedToUser: attrs.post.assigned_to_user, assignedToGroup: attrs.post.assigned_to_group, @@ -755,6 +724,76 @@ function initialize(api) { api.addKeyboardShortcut("g a", "", { path: "/my/activity/assigned" }); } +function customizePostMenu(api) { + const transformerRegistered = api.registerValueTransformer( + "post-menu-buttons", + ({ + value: dag, + context: { + post, + state, + firstButtonKey, + lastHiddenButtonKey, + secondLastHiddenButtonKey, + }, + }) => { + dag.add( + "assign", + AssignButton, + post.assigned_to_user?.id === state.currentUser.id + ? { + before: firstButtonKey, + } + : { + before: lastHiddenButtonKey, + after: secondLastHiddenButtonKey, + } + ); + } + ); + + const silencedKey = + transformerRegistered && "discourse.post-menu-widget-overrides"; + + withSilencedDeprecations(silencedKey, () => customizeWidgetPostMenu(api)); +} + +function customizeWidgetPostMenu(api) { + api.addPostMenuButton("assign", (post) => { + if (post.firstPost) { + return; + } + if (post.assigned_to_user || post.assigned_to_group) { + return { + action: "unassignPost", + icon: "user-times", + className: "unassign-post", + title: "discourse_assign.unassign_post.title", + position: + post.assigned_to_user?.id === api.getCurrentUser().id + ? "first" + : "second-last-hidden", + }; + } else { + return { + action: "assignPost", + icon: "user-plus", + className: "assign-post", + title: "discourse_assign.assign_post.title", + position: "second-last-hidden", + }; + } + }); + + api.attachWidgetAction("post", "assignPost", function () { + assignPost(this.model, getOwner(this).lookup("service:task-actions")); + }); + + api.attachWidgetAction("post", "unassignPost", function () { + unassignPost(this.model, getOwner(this).lookup("service:task-actions")); + }); +} + const REGEXP_USERNAME_PREFIX = /^(assigned:)/gi; export default { @@ -794,7 +833,7 @@ export default { }); } - withPluginApi("0.13.0", (api) => { + withPluginApi("1.34.0", (api) => { extendTopicModel(api, PLUGIN_ID); initialize(api); registerTopicFooterButtons(api); diff --git a/test/javascripts/acceptance/assign-enabled-test.js b/test/javascripts/acceptance/assign-enabled-test.js index 9c0d211b..9cdfed58 100644 --- a/test/javascripts/acceptance/assign-enabled-test.js +++ b/test/javascripts/acceptance/assign-enabled-test.js @@ -15,7 +15,7 @@ import { cloneJSON } from "discourse-common/lib/object"; acceptance("Discourse Assign | Assign mobile", function (needs) { needs.user(); needs.mobileView(); - needs.settings({ assign_enabled: true }); + needs.settings({ glimmer_post_menu_mode: "enabled", assign_enabled: true }); needs.pretender((server, helper) => { server.get("/assign/suggestions", () => { @@ -52,7 +52,7 @@ acceptance("Discourse Assign | Assign desktop", function (needs) { needs.user({ can_assign: true, }); - needs.settings({ assign_enabled: true }); + needs.settings({ glimmer_post_menu_mode: "enabled", assign_enabled: true }); needs.pretender((server, helper) => { server.get("/assign/suggestions", () => { @@ -77,15 +77,15 @@ acceptance("Discourse Assign | Assign desktop", function (needs) { await visit("/t/internationalization-localization/280"); assert - .dom("#post_2 .extra-buttons .d-icon-user-plus") + .dom("#post_2 .post-action-menu__assign-post") .doesNotExist("assign to post button is hidden"); await click("#post_2 button.show-more-actions"); assert - .dom("#post_2 .extra-buttons .d-icon-user-plus") + .dom("#post_2 .post-action-menu__assign-post") .exists("assign to post button exists"); - await click("#post_2 .extra-buttons .d-icon-user-plus"); + await click("#post_2 .post-action-menu__assign-post"); assert.dom(".assign.d-modal").exists("assign modal opens"); const menu = selectKit(".assign.d-modal .user-chooser"); @@ -126,6 +126,7 @@ acceptance("Discourse Assign | Assign Status enabled", function (needs) { can_assign: true, }); needs.settings({ + glimmer_post_menu_mode: "enabled", assign_enabled: true, enable_assign_status: true, assign_statuses: "New|In Progress|Done", @@ -187,7 +188,11 @@ acceptance("Discourse Assign | Assign Status disabled", function (needs) { needs.user({ can_assign: true, }); - needs.settings({ assign_enabled: true, enable_assign_status: false }); + needs.settings({ + glimmer_post_menu_mode: "enabled", + assign_enabled: true, + enable_assign_status: false, + }); needs.pretender((server, helper) => { server.get("/assign/suggestions", () => { @@ -245,6 +250,7 @@ const remindersFrequency = [ acceptance("Discourse Assign | User preferences", function (needs) { needs.user({ can_assign: true, reminders_frequency: remindersFrequency }); needs.settings({ + glimmer_post_menu_mode: "enabled", assign_enabled: true, remind_assigns_frequency: 43200, }); @@ -291,6 +297,7 @@ acceptance( function (needs) { needs.user({ can_assign: true, reminders_frequency: remindersFrequency }); needs.settings({ + glimmer_post_menu_mode: "enabled", assign_enabled: true, remind_assigns_frequency: 43200, }); diff --git a/test/javascripts/acceptance/assign-enabled-widget-post-menu-test.js b/test/javascripts/acceptance/assign-enabled-widget-post-menu-test.js new file mode 100644 index 00000000..e9eb76b4 --- /dev/null +++ b/test/javascripts/acceptance/assign-enabled-widget-post-menu-test.js @@ -0,0 +1,351 @@ +import { click, fillIn, visit } from "@ember/test-helpers"; +import { test } from "qunit"; +import userFixtures from "discourse/tests/fixtures/user-fixtures"; +import pretender, { + parsePostData, + response, +} from "discourse/tests/helpers/create-pretender"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { cloneJSON } from "discourse-common/lib/object"; + +// TODO (glimmer-post-menu): Remove this file when the post menu widget code is removed from core +acceptance( + "Discourse Assign | Widget Post Menu | Assign mobile", + function (needs) { + needs.user(); + needs.mobileView(); + needs.settings({ + glimmer_post_menu_mode: "disabled", + assign_enabled: true, + }); + + needs.pretender((server, helper) => { + server.get("/assign/suggestions", () => { + return helper.response({ + success: true, + assign_allowed_groups: false, + assign_allowed_for_groups: [], + suggestions: [ + { + id: 19, + username: "eviltrout", + name: "Robin Ward", + avatar_template: + "/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png", + }, + ], + }); + }); + }); + + test("Footer dropdown contains button", async function (assert) { + updateCurrentUser({ can_assign: true }); + await visit("/t/internationalization-localization/280"); + const menu = selectKit(".topic-footer-mobile-dropdown"); + await menu.expand(); + + assert.true(menu.rowByValue("assign").exists()); + await menu.selectRowByValue("assign"); + assert.dom(".assign.d-modal").exists("assign modal opens"); + }); + } +); + +acceptance( + "Discourse Assign | Widget Post Menu | Assign desktop", + function (needs) { + needs.user({ + can_assign: true, + }); + needs.settings({ + glimmer_post_menu_mode: "disabled", + assign_enabled: true, + }); + + needs.pretender((server, helper) => { + server.get("/assign/suggestions", () => { + return helper.response({ + success: true, + assign_allowed_groups: false, + assign_allowed_for_groups: [], + suggestions: [ + { + id: 19, + username: "eviltrout", + name: "Robin Ward", + avatar_template: + "/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png", + }, + ], + }); + }); + }); + + test("Assigning user to a post", async function (assert) { + await visit("/t/internationalization-localization/280"); + + assert + .dom("#post_2 .extra-buttons .d-icon-user-plus") + .doesNotExist("assign to post button is hidden"); + + await click("#post_2 button.show-more-actions"); + assert + .dom("#post_2 .extra-buttons .d-icon-user-plus") + .exists("assign to post button exists"); + + await click("#post_2 .extra-buttons .d-icon-user-plus"); + assert.dom(".assign.d-modal").exists("assign modal opens"); + + const menu = selectKit(".assign.d-modal .user-chooser"); + assert.true(menu.isExpanded(), "user selector is expanded"); + + await click(".assign.d-modal .btn-primary"); + assert.dom(".error-label").includesText("Choose a user to assign"); + + await menu.expand(); + await menu.selectRowByIndex(0); + assert.strictEqual(menu.header().value(), "eviltrout"); + assert.dom(".error-label").doesNotExist(); + + pretender.put("/assign/assign", ({ requestBody }) => { + const body = parsePostData(requestBody); + assert.strictEqual(body.target_type, "Post"); + assert.strictEqual(body.username, "eviltrout"); + assert.strictEqual(body.note, "a note!"); + return response({ success: true }); + }); + + await fillIn("#assign-modal-note", "a note!"); + await click(".assign.d-modal .btn-primary"); + + assert.dom(".assign.d-modal").doesNotExist("assign modal closes"); + }); + + test("Footer dropdown contains button", async function (assert) { + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-button-assign"); + + assert.dom(".assign.d-modal").exists("assign modal opens"); + }); + } +); + +acceptance( + "Discourse Assign | Widget Post Menu | Assign Status enabled", + function (needs) { + needs.user({ + can_assign: true, + }); + needs.settings({ + glimmer_post_menu_mode: "disabled", + assign_enabled: true, + enable_assign_status: true, + assign_statuses: "New|In Progress|Done", + }); + + needs.pretender((server, helper) => { + server.get("/assign/suggestions", () => { + return helper.response({ + success: true, + assign_allowed_groups: false, + assign_allowed_for_groups: [], + suggestions: [ + { + id: 19, + username: "eviltrout", + name: "Robin Ward", + avatar_template: + "/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png", + }, + ], + }); + }); + }); + + test("Modal contains status dropdown", async function (assert) { + pretender.put("/assign/assign", ({ requestBody }) => { + const body = parsePostData(requestBody); + assert.strictEqual(body.target_type, "Topic"); + assert.strictEqual(body.target_id, "280"); + assert.strictEqual(body.username, "eviltrout"); + assert.strictEqual(body.status, "In Progress"); + + return response({ success: true }); + }); + + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-button-assign"); + + assert + .dom(".assign.d-modal #assign-status") + .exists("assign status dropdown exists"); + + const statusDropdown = selectKit("#assign-status"); + assert.strictEqual(statusDropdown.header().value(), "New"); + + await statusDropdown.expand(); + await statusDropdown.selectRowByValue("In Progress"); + assert.strictEqual(statusDropdown.header().value(), "In Progress"); + + const menu = selectKit(".assign.d-modal .user-chooser"); + await menu.expand(); + await menu.selectRowByIndex(0); + + await click(".assign.d-modal .btn-primary"); + }); + } +); + +acceptance( + "Discourse Assign | Widget Post Menu | Assign Status disabled", + function (needs) { + needs.user({ + can_assign: true, + }); + needs.settings({ + glimmer_post_menu_mode: "disabled", + assign_enabled: true, + enable_assign_status: false, + }); + + needs.pretender((server, helper) => { + server.get("/assign/suggestions", () => { + return helper.response({ + success: true, + assign_allowed_groups: false, + assign_allowed_for_groups: [], + suggestions: [ + { + id: 19, + username: "eviltrout", + name: "Robin Ward", + avatar_template: + "/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png", + }, + ], + }); + }); + }); + + test("Modal contains status dropdown", async function (assert) { + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-button-assign"); + + assert + .dom(".assign.d-modal #assign-status") + .doesNotExist("assign status dropdown doesn't exists"); + }); + } +); + +// See RemindAssignsFrequencySiteSettings +const remindersFrequency = [ + { + name: "discourse_assign.reminders_frequency.never", + value: 0, + }, + { + name: "discourse_assign.reminders_frequency.daily", + value: 1440, + }, + { + name: "discourse_assign.reminders_frequency.weekly", + value: 10080, + }, + { + name: "discourse_assign.reminders_frequency.monthly", + value: 43200, + }, + { + name: "discourse_assign.reminders_frequency.quarterly", + value: 129600, + }, +]; + +acceptance( + "Discourse Assign | Widget Post Menu | User preferences", + function (needs) { + needs.user({ can_assign: true, reminders_frequency: remindersFrequency }); + needs.settings({ + glimmer_post_menu_mode: "disabled", + assign_enabled: true, + remind_assigns_frequency: 43200, + }); + + test("The frequency for assigned topic reminders defaults to the site setting", async function (assert) { + await visit("/u/eviltrout/preferences/notifications"); + + assert.strictEqual( + selectKit("#remind-assigns-frequency").header().value(), + "43200", + "set frequency to default of Monthly" + ); + }); + + test("The user can change the frequency to Never", async function (assert) { + await visit("/u/eviltrout/preferences/notifications"); + + await selectKit("#remind-assigns-frequency").expand(); + await selectKit("#remind-assigns-frequency").selectRowByValue(0); + + assert.strictEqual( + selectKit("#remind-assigns-frequency").header().value(), + "0", + "set frequency to Never" + ); + }); + + test("The user can change the frequency to some other non-default value", async function (assert) { + await visit("/u/eviltrout/preferences/notifications"); + + await selectKit("#remind-assigns-frequency").expand(); + await selectKit("#remind-assigns-frequency").selectRowByValue(10080); // weekly + + assert.strictEqual( + selectKit("#remind-assigns-frequency").header().value(), + "10080", + "set frequency to Weekly" + ); + }); + } +); + +acceptance( + "Discourse Assign | Widget Post Menu | User preferences | Pre-selected reminder frequency", + function (needs) { + needs.user({ can_assign: true, reminders_frequency: remindersFrequency }); + needs.settings({ + glimmer_post_menu_mode: "disabled", + assign_enabled: true, + remind_assigns_frequency: 43200, + }); + + needs.pretender((server, helper) => { + server.get("/u/eviltrout.json", () => { + let json = cloneJSON(userFixtures["/u/eviltrout.json"]); + json.user.custom_fields = { remind_assigns_frequency: 10080 }; + + // usually this is done automatically by this pretender but we + // have to do it manually here because we are overriding the + // pretender see app/assets/javascripts/discourse/tests/helpers/create-pretender.js + json.user.can_edit = true; + + return helper.response(200, json); + }); + }); + + test("The user's previously selected value is loaded", async function (assert) { + await visit("/u/eviltrout/preferences/notifications"); + + assert.strictEqual( + selectKit("#remind-assigns-frequency").header().value(), + "10080", + "frequency is pre-selected to Weekly" + ); + }); + } +);