DEV: Use the new component-based modal API (#489)

Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
Jarek Radosz 2023-07-11 10:37:57 +02:00 committed by GitHub
parent a4ea754c1d
commit 1c079b792b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 522 additions and 424 deletions

View File

@ -0,0 +1,57 @@
<div class="control-group {{if this.assigneeError 'assignee-error'}}">
<label>{{i18n "discourse_assign.assign_modal.assignee_label"}}</label>
<AssigneeChooser
autocomplete="off"
@value={{this.assigneeName}}
@onChange={{this.assignUsername}}
@showUserStatus={{true}}
@options={{hash
mobilePlacementStrategy="absolute"
includeGroups=true
customSearchOptions=(hash
assignableGroups=true defaultSearchResults=this.taskActions.suggestions
)
groupMembersOf=this.taskActions.allowedGroups
maximum=1
autofocus=(not this.capabilities.touch)
tabindex=1
expandedOnInsert=(not this.assigneeName)
caretUpIcon="search"
caretDownIcon="search"
}}
/>
{{#if this.assigneeError}}
<span class="error-label">
{{d-icon "exclamation-triangle"}}
{{i18n "discourse_assign.assign_modal.choose_assignee"}}
</span>
{{/if}}
</div>
{{#if this.siteSettings.enable_assign_status}}
<div class="control-group assign-status">
<label>{{i18n "discourse_assign.assign_modal.status_label"}}</label>
<ComboBox
@id="assign-status"
@content={{this.availableStatuses}}
@value={{this.status}}
@onChange={{action (mut @model.status)}}
/>
</div>
{{/if}}
<div class="control-group assign-status">
<label>
{{i18n "discourse_assign.assign_modal.note_label"}}&nbsp;<span
class="label-optional"
>{{i18n "discourse_assign.assign_modal.optional_label"}}</span>
</label>
<Textarea
id="assign-modal-note"
@value={{@model.note}}
{{! template-lint-disable no-down-event-binding }}
{{on "keydown" this.handleTextAreaKeydown}}
/>
</div>

View File

@ -0,0 +1,65 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
export default class AssignUserForm extends Component {
@service taskActions;
@service siteSettings;
@service capabilities;
@tracked assigneeError = false;
@tracked assigneeName =
this.args.model.username || this.args.model.group_name;
constructor() {
super(...arguments);
this.args.formApi.submit = this.assign;
}
get availableStatuses() {
return this.siteSettings.assign_statuses
.split("|")
.map((status) => ({ id: status, name: status }));
}
get status() {
return (
this.args.model.status ||
this.args.model.target.assignment_status ||
this.siteSettings.assign_statuses.split("|")[0]
);
}
@action
handleTextAreaKeydown(event) {
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
this.assign();
}
}
@action
async assign() {
if (!this.assigneeName) {
this.assigneeError = true;
return;
}
await this.args.onSubmit();
}
@action
assignUsername([name]) {
this.assigneeName = name;
this.assigneeError = false;
if (this.taskActions.allowedGroupsForAssignment.includes(name)) {
this.args.model.username = null;
this.args.model.group_name = name;
} else {
this.args.model.username = name;
this.args.model.group_name = null;
}
}
}

View File

@ -0,0 +1,23 @@
<DModal class="assign" @title={{this.title}} @closeModal={{@closeModal}}>
<:body>
<AssignUserForm
@model={{@model}}
@onSubmit={{this.onSubmit}}
@formApi={{this.formApi}}
/>
</:body>
<:footer>
<DButton
class="btn-primary"
@action={{this.formApi.submit}}
@label={{if
@model.reassign
"discourse_assign.reassign.title"
"discourse_assign.assign_modal.assign"
}}
/>
<DModalCancel @close={{@closeModal}} />
</:footer>
</DModal>

View File

@ -0,0 +1,38 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import I18n from "I18n";
export default class AssignUser extends Component {
@service taskActions;
// `submit` property will be mutated by the `AssignUserForm` component
formApi = {
submit() {},
};
get title() {
let i18nSuffix;
switch (this.args.model.targetType) {
case "Post":
i18nSuffix = "_post_modal";
break;
case "Topic":
i18nSuffix = "_modal";
break;
}
return I18n.t(
`discourse_assign.assign${i18nSuffix}.${
this.args.model.reassign ? "reassign_title" : "title"
}`
);
}
@action
async onSubmit() {
this.args.closeModal();
await this.taskActions.assign(this.args.model);
}
}

View File

@ -1,13 +1,12 @@
<div class="reviewable-filter discourse-assign-assign-to-filter">
<label class="filter-label">{{i18n "discourse_assign.assigned_to"}}</label>
<EmailGroupUserChooser
@value={{this.additionalFilters.assigned_to}}
@onChange={{action "updateAssignedTo"}}
autocomplete="off"
@value={{this.additionalFilters.assigned_to}}
@onChange={{this.updateAssignedTo}}
@options={{hash
maximum=1
fullWidthWrap=true
filterPlaceholder=this.placeholderKey
includeGroups=false
groupMembersOf=this.allowedGroups
}}

View File

@ -1,145 +0,0 @@
import Controller, { inject as controller } from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { not, or } from "@ember/object/computed";
import { inject as service } from "@ember/service";
import { isEmpty } from "@ember/utils";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class AssignUser extends Controller.extend(ModalFunctionality) {
@service taskActions;
@controller topicBulkActions;
assignSuggestions = null;
allowedGroups = null;
assigneeError = false;
@not("capabilities.touch") autofocus;
@or("model.username", "model.group_name") assigneeName;
constructor() {
super(...arguments);
this.set("allowedGroups", []);
this.set("assigneeError", false);
ajax("/assign/suggestions").then((data) => {
if (this.isDestroying || this.isDestroyed) {
return;
}
this.set("assignSuggestions", data.suggestions);
this.set("allowedGroups", data.assign_allowed_on_groups);
this.set("allowedGroupsForAssignment", data.assign_allowed_for_groups);
});
}
onShow() {
this.set("assigneeError", false);
}
onClose() {
if (this.model.onClose && this.model.username) {
this.model.onClose(this.model.username);
}
}
bulkAction(username, note) {
return this.topicBulkActions.performAndRefresh({
type: "assign",
username,
note,
});
}
@discourseComputed("siteSettings.enable_assign_status")
statusEnabled() {
return this.siteSettings.enable_assign_status;
}
@discourseComputed("siteSettings.assign_statuses")
availableStatuses() {
return this.siteSettings.assign_statuses.split("|").map((status) => {
return { id: status, name: status };
});
}
@discourseComputed("siteSettings.assign_statuses", "model.status")
status() {
return (
this.model.status ||
this.model.target.assignment_status ||
this.siteSettings.assign_statuses.split("|")[0]
);
}
@action
handleTextAreaKeydown(event) {
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
this.assign();
}
}
@action
assign() {
if (this.isBulkAction) {
return this.bulkAction(this.model.username, this.model.note);
}
if (!this.assigneeName) {
this.set("assigneeError", true);
return;
}
let path = "/assign/assign";
if (isEmpty(this.model.username)) {
this.model.target.set("assigned_to_user", null);
}
if (isEmpty(this.model.group_name)) {
this.model.target.set("assigned_to_group", null);
}
if (isEmpty(this.model.username) && isEmpty(this.model.group_name)) {
path = "/assign/unassign";
}
this.send("closeModal");
return ajax(path, {
type: "PUT",
data: {
username: this.model.username,
group_name: this.model.group_name,
target_id: this.model.target.id,
target_type: this.model.targetType,
note: this.model.note,
status: this.model.status,
},
})
.then(() => {
this.model.onSuccess?.();
})
.catch(popupAjaxError);
}
@action
assignUsername([name]) {
this.set("assigneeError", false);
this.set("model.allowedGroups", this.taskActions.allowedGroups);
if (this.allowedGroupsForAssignment.includes(name)) {
this.setProperties({
"model.username": null,
"model.group_name": name,
});
} else {
this.setProperties({
"model.username": name,
"model.group_name": null,
});
}
}
}

View File

@ -0,0 +1,22 @@
import Controller, { inject as controller } from "@ember/controller";
import { action } from "@ember/object";
export default class BulkAssign extends Controller {
@controller topicBulkActions;
model;
// `submit` property will be mutated by the `AssignUserForm` component
formApi = {
submit() {},
};
@action
async assign() {
return this.topicBulkActions.performAndRefresh({
type: "assign",
username: this.model.username,
note: this.model.note,
});
}
}

View File

@ -43,17 +43,16 @@ export default class GroupAssignedShow extends UserTopicsList {
}
@action
unassign(targetId, targetType = "Topic") {
this.taskActions
.unassign(targetId, targetType)
.then(() => this.send("changeAssigned"));
async unassign(targetId, targetType = "Topic") {
await this.taskActions.unassign(targetId, targetType);
this.send("changeAssigned");
}
@action
reassign(topic) {
this.taskActions
.assign(topic)
.set("model.onSuccess", () => this.send("changeAssigned"));
this.taskActions.showAssignModal(topic, {
onSuccess: () => this.send("changeAssigned"),
});
}
@action

View File

@ -57,17 +57,16 @@ export default class UserActivityAssigned extends UserTopicsList {
}
@action
unassign(targetId, targetType = "Topic") {
this.taskActions
.unassign(targetId, targetType)
.then(() => this.send("changeAssigned"));
async unassign(targetId, targetType = "Topic") {
await this.taskActions.unassign(targetId, targetType);
this.send("changeAssigned");
}
@action
reassign(topic) {
this.taskActions
.assign(topic)
.set("model.onSuccess", () => this.send("changeAssigned"));
this.taskActions.showAssignModal(topic, {
onSuccess: () => this.send("changeAssigned"),
});
}
@action

View File

@ -11,7 +11,6 @@ import SearchAdvancedOptions from "discourse/components/search-advanced-options"
import TopicButtonAction, {
addBulkButton,
} from "discourse/controllers/topic-bulk-actions";
import { inject as controller } from "@ember/controller";
import I18n from "I18n";
import { isEmpty } from "@ember/utils";
import { registerTopicFooterDropdown } from "discourse/lib/register-topic-footer-dropdown";
@ -50,7 +49,7 @@ function registerTopicFooterButtons(api) {
registerTopicFooterDropdown({
id: "reassign",
action(id) {
async action(id) {
if (!this.currentUser?.can_assign) {
return;
}
@ -61,35 +60,33 @@ function registerTopicFooterButtons(api) {
case "unassign": {
this.set("topic.assigned_to_user", null);
this.set("topic.assigned_to_group", null);
taskActions.unassign(this.topic.id).then(() => {
await taskActions.unassign(this.topic.id);
this.appEvents.trigger("post-stream:refresh", {
id: this.topic.postStream.firstPostId,
});
});
break;
}
case "reassign-self": {
this.set("topic.assigned_to_user", null);
this.set("topic.assigned_to_group", null);
taskActions
.reassignUserToTopic(this.currentUser, this.topic)
.then(() => {
await taskActions.reassignUserToTopic(this.currentUser, this.topic);
this.appEvents.trigger("post-stream:refresh", {
id: this.topic.postStream.firstPostId,
});
});
break;
}
case "reassign": {
taskActions
.assign(this.topic, {
await taskActions.showAssignModal(this.topic, {
targetType: "Topic",
isAssigned: this.topic.isAssigned(),
})
.set("model.onSuccess", () => {
onSuccess: () =>
this.appEvents.trigger("post-stream:refresh", {
id: this.topic.postStream.firstPostId,
});
}),
});
break;
}
@ -195,7 +192,7 @@ function registerTopicFooterButtons(api) {
translatedLabel() {
return I18n.t("discourse_assign.assign.title");
},
action() {
async action() {
if (!this.currentUser?.can_assign) {
return;
}
@ -205,16 +202,18 @@ function registerTopicFooterButtons(api) {
if (this.topic.isAssigned()) {
this.set("topic.assigned_to_user", null);
this.set("topic.assigned_to_group", null);
taskActions.unassign(this.topic.id, "Topic").then(() => {
await taskActions.unassign(this.topic.id, "Topic");
this.appEvents.trigger("post-stream:refresh", {
id: this.topic.postStream.firstPostId,
});
});
} else {
taskActions.assign(this.topic).set("model.onSuccess", () => {
await taskActions.showAssignModal(this.topic, {
onSuccess: () =>
this.appEvents.trigger("post-stream:refresh", {
id: this.topic.postStream.firstPostId,
});
}),
});
}
},
@ -333,7 +332,7 @@ function registerTopicFooterButtons(api) {
`<span class="unassign-label"><span class="text">${label}</span></span>`
);
},
action() {
async action() {
if (!this.currentUser?.can_assign) {
return;
}
@ -342,11 +341,12 @@ function registerTopicFooterButtons(api) {
this.set("topic.assigned_to_user", null);
this.set("topic.assigned_to_group", null);
taskActions.reassignUserToTopic(this.currentUser, this.topic).then(() => {
await taskActions.reassignUserToTopic(this.currentUser, this.topic);
this.appEvents.trigger("post-stream:refresh", {
id: this.topic.postStream.firstPostId,
});
});
},
dropdown() {
return this.currentUser?.can_assign && this.topic.isAssigned();
@ -382,22 +382,20 @@ function registerTopicFooterButtons(api) {
`<span class="unassign-label"><span class="text">${label}</span></span>`
);
},
action() {
async action() {
if (!this.currentUser?.can_assign) {
return;
}
const taskActions = getOwner(this).lookup("service:task-actions");
taskActions
.assign(this.topic, {
await taskActions.showAssignModal(this.topic, {
targetType: "Topic",
isAssigned: this.topic.isAssigned(),
})
.set("model.onSuccess", () => {
onSuccess: () =>
this.appEvents.trigger("post-stream:refresh", {
id: this.topic.postStream.firstPostId,
});
}),
});
},
dropdown() {
@ -439,7 +437,7 @@ function initialize(api) {
},
before: "top",
});
if (api.getCurrentUser() && api.getCurrentUser().can_assign) {
if (api.getCurrentUser()?.can_assign) {
api.addPostMenuButton("assign", (post) => {
if (post.firstPost) {
return;
@ -465,13 +463,15 @@ function initialize(api) {
};
}
});
api.attachWidgetAction("post", "assignPost", function () {
const taskActions = getOwner(this).lookup("service:task-actions");
taskActions.assign(this.model, {
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(() => {
@ -488,7 +488,7 @@ function initialize(api) {
});
api.addAdvancedSearchOptions(
api.getCurrentUser() && api.getCurrentUser().can_assign
api.getCurrentUser()?.can_assign
? {
inOptionsForUsers: [
{
@ -914,25 +914,27 @@ export default {
});
TopicButtonAction.reopen({
assignUser: controller("assign-user"),
actions: {
showReAssign() {
this.set("assignUser.isBulkAction", true);
this.set("assignUser.model", { username: "", note: "" });
this.send("changeBulkTemplate", "modal/assign-user");
const controller = getOwner(this).lookup("controller:bulk-assign");
controller.set("model", { username: "", note: "" });
this.send("changeBulkTemplate", "modal/bulk-assign");
},
unassignTopics() {
this.performAndRefresh({ type: "unassign" });
},
},
});
addBulkButton("showReAssign", "assign", {
icon: "user-plus",
class: "btn-default",
class: "btn-default assign-topics",
});
addBulkButton("unassignTopics", "unassign", {
icon: "user-times",
class: "btn-default",
class: "btn-default unassign-topics",
});
}

View File

@ -1,15 +1,38 @@
import Service from "@ember/service";
import Service, { inject as service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import showModal from "discourse/lib/show-modal";
import AssignUser from "../components/modal/assign-user";
import { tracked } from "@glimmer/tracking";
import { isEmpty } from "@ember/utils";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class TaskActions extends Service {
i18nSuffix(targetType) {
switch (targetType) {
case "Post":
return "_post_modal";
case "Topic":
return "_modal";
@service modal;
@tracked allowedGroups;
@tracked allowedGroupsForAssignment;
#suggestionsPromise;
@tracked _suggestions;
get suggestions() {
if (this._suggestions) {
return this._suggestions;
}
this.#suggestionsPromise ||= this.#fetchSuggestions();
return null;
}
async #fetchSuggestions() {
const data = await ajax("/assign/suggestions");
if (this.isDestroying || this.isDestroyed) {
return;
}
this._suggestions = data.suggestions;
this.allowedGroups = data.assign_allowed_on_groups;
this.allowedGroupsForAssignment = data.assign_allowed_for_groups;
}
unassign(targetId, targetType = "Topic") {
@ -22,19 +45,19 @@ export default class TaskActions extends Service {
});
}
assign(target, options = { isAssigned: false, targetType: "Topic" }) {
return showModal("assign-user", {
title:
"discourse_assign.assign" +
this.i18nSuffix(options.targetType) +
`.${options.isAssigned ? "reassign_title" : "title"}`,
showAssignModal(
target,
{ isAssigned = false, targetType = "Topic", onSuccess }
) {
return this.modal.show(AssignUser, {
model: {
reassign: options.isAssigned,
reassign: isAssigned,
username: target.assigned_to_user?.username,
group_name: target.assigned_to_group?.name,
target,
targetType: options.targetType,
status: target.assignment_status,
target,
targetType,
onSuccess,
},
});
}
@ -50,4 +73,37 @@ export default class TaskActions extends Service {
},
});
}
async assign(model) {
if (isEmpty(model.username)) {
model.target.set("assigned_to_user", null);
}
if (isEmpty(model.group_name)) {
model.target.set("assigned_to_group", null);
}
let path = "/assign/assign";
if (isEmpty(model.username) && isEmpty(model.group_name)) {
path = "/assign/unassign";
}
try {
await ajax(path, {
type: "PUT",
data: {
username: model.username,
group_name: model.group_name,
target_id: model.target.id,
target_type: model.targetType,
note: model.note,
status: model.status,
},
});
model.onSuccess?.();
} catch (error) {
popupAjaxError(error);
}
}
}

View File

@ -1,76 +0,0 @@
<DModalBody class="assign">
<div>
<div class="control-group {{if this.assigneeError 'assignee-error'}}">
<label>{{i18n "discourse_assign.assign_modal.assignee_label"}}</label>
<AssigneeChooser
autocomplete="off"
@value={{this.assigneeName}}
@onChange={{this.assignUsername}}
autofocus="autofocus"
@showUserStatus={{true}}
@options={{hash
mobilePlacementStrategy="absolute"
filterPlaceholder=this.placeholderKey
includeGroups=true
customSearchOptions=(hash
assignableGroups=true defaultSearchResults=this.assignSuggestions
)
groupMembersOf=this.allowedGroups
maximum=1
autofocus=this.autofocus
tabindex=1
expandedOnInsert=(not this.assigneeName)
caretUpIcon="search"
caretDownIcon="search"
}}
/>
{{#if this.assigneeError}}
<span class="error-label">
{{d-icon "exclamation-triangle"}}
{{i18n "discourse_assign.assign_modal.choose_assignee"}}
</span>
{{/if}}
</div>
{{#if this.statusEnabled}}
<div class="control-group assign-status">
<label>{{i18n "discourse_assign.assign_modal.status_label"}}</label>
<ComboBox
@id="assign-status"
@content={{this.availableStatuses}}
@value={{this.status}}
@onChange={{action (mut this.model.status)}}
/>
</div>
{{/if}}
<div class="control-group assign-status">
<label>
{{i18n "discourse_assign.assign_modal.note_label"}}&nbsp;<span
class="label-optional"
>{{i18n "discourse_assign.assign_modal.optional_label"}}</span>
</label>
<Textarea
id="assign-modal-note"
@value={{this.model.note}}
{{! template-lint-disable no-down-event-binding }}
{{on "keydown" (action "handleTextAreaKeydown")}}
/>
</div>
</div>
</DModalBody>
<div class="modal-footer">
<DButton
@label={{if
this.model.reassign
"discourse_assign.reassign.title"
"discourse_assign.assign_modal.assign"
}}
@icon={{this.inviteIcon}}
@action={{this.assign}}
class="btn-primary"
@disabled={{this.disabled}}
/>
<DModalCancel @close={{route-action "closeModal"}} />
</div>

View File

@ -0,0 +1,19 @@
<div>
<AssignUserForm
@model={{this.model}}
@onSubmit={{this.assign}}
@formApi={{this.formApi}}
/>
</div>
<div>
<DButton
class="btn-primary"
@action={{this.formApi.submit}}
@label={{if
this.model.reassign
"discourse_assign.reassign.title"
"discourse_assign.assign_modal.assign"
}}
/>
</div>

View File

@ -66,12 +66,12 @@
margin-left: 5px;
}
.assign-user-modal {
.modal.assign {
.modal-inner-container {
width: 400px;
}
.assign.modal-body {
.modal-body {
overflow-y: unset;
}

View File

@ -5,8 +5,12 @@ import {
acceptance,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
import { click, visit } from "@ember/test-helpers";
import { click, fillIn, visit } from "@ember/test-helpers";
import { test } from "qunit";
import pretender, {
parsePostData,
response,
} from "discourse/tests/helpers/create-pretender";
acceptance("Discourse Assign | Assign mobile", function (needs) {
needs.user();
@ -30,10 +34,6 @@ acceptance("Discourse Assign | Assign mobile", function (needs) {
],
});
});
server.put("/assign/assign", () => {
return helper.response({ success: true });
});
});
test("Footer dropdown contains button", async function (assert) {
@ -44,7 +44,7 @@ acceptance("Discourse Assign | Assign mobile", function (needs) {
assert.true(menu.rowByValue("assign").exists());
await menu.selectRowByValue("assign");
assert.dom(".assign.modal-body").exists("assign modal opens");
assert.dom(".assign.modal").exists("assign modal opens");
});
});
@ -71,32 +71,53 @@ acceptance("Discourse Assign | Assign desktop", function (needs) {
],
});
});
server.put("/assign/assign", () => {
return helper.response({ success: true });
});
});
test("Post contains hidden assign button", async function (assert) {
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");
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.modal-body").exists("assign modal opens");
assert.dom(".assign.modal").exists("assign modal opens");
const menu = selectKit(".assign.modal .user-chooser");
assert.true(menu.isExpanded(), "user selector is expanded");
await click(".assign.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.modal .btn-primary");
assert.dom(".assign.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.modal-body").exists("assign modal opens");
assert.dom(".assign.modal").exists("assign modal opens");
});
});
@ -127,10 +148,6 @@ acceptance("Discourse Assign | Assign Status enabled", function (needs) {
],
});
});
server.put("/assign/assign", () => {
return helper.response({ success: true });
});
});
test("Modal contains status dropdown", async function (assert) {
@ -138,7 +155,7 @@ acceptance("Discourse Assign | Assign Status enabled", function (needs) {
await click("#topic-footer-button-assign");
assert
.dom(".assign.modal-body #assign-status")
.dom(".assign.modal #assign-status")
.exists("assign status dropdown exists");
});
});
@ -166,10 +183,6 @@ acceptance("Discourse Assign | Assign Status disabled", function (needs) {
],
});
});
server.put("/assign/assign", () => {
return helper.response({ success: true });
});
});
test("Modal contains status dropdown", async function (assert) {
@ -177,7 +190,7 @@ acceptance("Discourse Assign | Assign Status disabled", function (needs) {
await click("#topic-footer-button-assign");
assert
.dom(".assign.modal-body #assign-status")
.dom(".assign.modal #assign-status")
.doesNotExist("assign status dropdown doesn't exists");
});
});

View File

@ -0,0 +1,82 @@
import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
import { click, fillIn, visit } from "@ember/test-helpers";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import pretender, {
parsePostData,
response,
} from "discourse/tests/helpers/create-pretender";
import I18n from "I18n";
acceptance("Discourse Assign | Bulk actions", function (needs) {
needs.user({
moderator: true,
can_assign: true,
});
needs.settings({ 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 users to topics", async function (assert) {
await visit("/latest");
await click("button.bulk-select");
const topic1 = query(".topic-list-body tr:nth-child(1)");
const topic2 = query(".topic-list-body tr:nth-child(2)");
await click(topic1.querySelector("input.bulk-select"));
await click(topic2.querySelector("input.bulk-select"));
await click(".bulk-select-actions");
assert
.dom("#discourse-modal-title")
.includesText(I18n.t("topics.bulk.actions"), "opens bulk-select modal");
await click("button.assign-topics");
const menu = selectKit(".topic-bulk-actions-modal .user-chooser");
assert.true(menu.isExpanded(), "user selector is expanded");
await click(".topic-bulk-actions-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");
pretender.put("/topics/bulk", ({ requestBody }) => {
const body = parsePostData(requestBody);
assert.deepEqual(body.operation, {
type: "assign",
username: "eviltrout",
note: "a note!",
});
assert.deepEqual(body["topic_ids[]"], [
topic1.dataset.topicId,
topic2.dataset.topicId,
]);
return response({ success: true });
});
await fillIn("#assign-modal-note", "a note!");
await click(".topic-bulk-actions-modal .btn-primary");
});
});

View File

@ -0,0 +1,35 @@
import componentTest, {
setupRenderingTest,
} from "discourse/tests/helpers/component-test";
import { discourseModule, query } from "discourse/tests/helpers/qunit-helpers";
import hbs from "htmlbars-inline-precompile";
discourseModule(
"Discourse Assign | Integration | Component | group-assigned-filter",
function (hooks) {
setupRenderingTest(hooks);
componentTest("displays username and name", {
template: hbs`<GroupAssignedFilter @showAvatar={{true}} @filter={{filter}} />`,
beforeEach() {
this.set("filter", {
id: 2,
username: "Ahmed",
name: "Ahmed Gagan",
avatar_template: "/letter_avatar_proxy/v4/letter/a/8c91f0/{size}.png",
title: "trust_level_0",
last_posted_at: "2020-06-22T10:15:54.532Z",
last_seen_at: "2020-07-07T11:55:59.437Z",
added_at: "2020-06-22T09:55:31.692Z",
timezone: "Asia/Calcutta",
});
},
test(assert) {
assert.strictEqual(query(".assign-username").innerText, "Ahmed");
assert.strictEqual(query(".assign-name").innerText, "Ahmed Gagan");
},
});
}
);

View File

@ -1,36 +0,0 @@
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import EmberObject from "@ember/object";
import pretender, { response } from "discourse/tests/helpers/create-pretender";
import { getOwner } from "discourse-common/lib/get-owner";
module("Discourse Assign | Unit | Controller | assign-user", function (hooks) {
setupRenderingTest(hooks);
test("assigning a user by selector does not close the modal", async function (assert) {
pretender.get("/assign/suggestions", () =>
response({
suggestions: [],
assign_allowed_on_groups: ["nat"],
assign_allowed_for_groups: [],
})
);
let modalClosed = false;
const controller = getOwner(this).lookup("controller:assign-user");
controller.setProperties({
model: {
target: EmberObject.create({}),
},
allowedGroupsForAssignment: ["nat"],
taskActions: { allowedGroups: [] },
});
controller.set("actions.closeModal", () => {
modalClosed = true;
});
await controller.assignUsername("nat");
assert.false(modalClosed);
});
});

View File

@ -1,54 +0,0 @@
import { module, test } from "qunit";
import { setupTest } from "ember-qunit";
import sinon from "sinon";
import * as showModal from "discourse/lib/show-modal";
import pretender, { response } from "discourse/tests/helpers/create-pretender";
import { getOwner } from "discourse-common/lib/get-owner";
module("Discourse Assign | Unit | Service | task-actions", function (hooks) {
setupTest(hooks);
test("assign", function (assert) {
const stub = sinon.stub(showModal, "default").returns("the modal");
const service = getOwner(this).lookup("service:task-actions");
const target = {
assigned_to_user: { username: "tomtom" },
assigned_to_group: { name: "cats" },
};
const modal = service.assign(target);
const modalCall = stub.getCall(0).args;
assert.strictEqual(modal, "the modal");
assert.strictEqual(modalCall[0], "assign-user");
assert.deepEqual(modalCall[1], {
title: "discourse_assign.assign_modal.title",
model: {
reassign: false,
username: "tomtom",
group_name: "cats",
target,
targetType: "Topic",
status: undefined,
},
});
});
test("reassignUserToTopic", async function (assert) {
const service = getOwner(this).lookup("service:task-actions");
const target = { id: 1 };
const user = { username: "tomtom" };
let assignRequest;
pretender.put("/assign/assign", (request) => {
assignRequest = request;
return response({});
});
await service.reassignUserToTopic(user, target);
assert.strictEqual(
assignRequest.requestBody,
"username=tomtom&target_id=1&target_type=Topic"
);
});
});