DEV: Use the new component-based modal API (#489)
Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
parent
a4ea754c1d
commit
1c079b792b
|
@ -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"}} <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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,36 +60,34 @@ 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(() => {
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
|
||||
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(() => {
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
});
|
||||
|
||||
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, {
|
||||
targetType: "Topic",
|
||||
isAssigned: this.topic.isAssigned(),
|
||||
})
|
||||
.set("model.onSuccess", () => {
|
||||
await taskActions.showAssignModal(this.topic, {
|
||||
targetType: "Topic",
|
||||
isAssigned: this.topic.isAssigned(),
|
||||
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(() => {
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
|
||||
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", () => {
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
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,10 +341,11 @@ function registerTopicFooterButtons(api) {
|
|||
|
||||
this.set("topic.assigned_to_user", null);
|
||||
this.set("topic.assigned_to_group", null);
|
||||
taskActions.reassignUserToTopic(this.currentUser, this.topic).then(() => {
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
|
||||
await taskActions.reassignUserToTopic(this.currentUser, this.topic);
|
||||
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
},
|
||||
dropdown() {
|
||||
|
@ -382,23 +382,21 @@ 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, {
|
||||
targetType: "Topic",
|
||||
isAssigned: this.topic.isAssigned(),
|
||||
})
|
||||
.set("model.onSuccess", () => {
|
||||
await taskActions.showAssignModal(this.topic, {
|
||||
targetType: "Topic",
|
||||
isAssigned: this.topic.isAssigned(),
|
||||
onSuccess: () =>
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
});
|
||||
}),
|
||||
});
|
||||
},
|
||||
dropdown() {
|
||||
return this.currentUser?.can_assign && this.topic.isAssigned();
|
||||
|
@ -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",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"}} <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>
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
});
|
|
@ -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");
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue