FEATURE: Reassign workflow (#231)
Build a new workflow that adds a dropdown in place of the old assign button with the ability to Re-assign a new user / group to an assigned topic Re-assign yourself to an assigned topic Unassign a user / group from an assigned topic
This commit is contained in:
parent
0e4649d507
commit
98103586b2
|
@ -44,27 +44,11 @@ module DiscourseAssign
|
|||
end
|
||||
|
||||
def assign
|
||||
target_id = params.require(:target_id)
|
||||
target_type = params.require(:target_type)
|
||||
username = params.permit(:username)['username']
|
||||
group_name = params.permit(:group_name)['group_name']
|
||||
reassign_or_assign_target(action: "assign")
|
||||
end
|
||||
|
||||
assign_to = username.present? ? User.find_by(username_lower: username.downcase) : Group.where("LOWER(name) = ?", group_name.downcase).first
|
||||
|
||||
raise Discourse::NotFound unless assign_to
|
||||
raise Discourse::NotFound if !Assignment.valid_type?(target_type)
|
||||
target = target_type.constantize.where(id: target_id).first
|
||||
raise Discourse::NotFound unless target
|
||||
|
||||
# perhaps?
|
||||
#Scheduler::Defer.later "assign topic" do
|
||||
assign = Assigner.new(target, current_user).assign(assign_to)
|
||||
|
||||
if assign[:success]
|
||||
render json: success_json
|
||||
else
|
||||
render json: translate_failure(assign[:reason], assign_to), status: 400
|
||||
end
|
||||
def reassign
|
||||
reassign_or_assign_target(action: "reassign")
|
||||
end
|
||||
|
||||
def assigned
|
||||
|
@ -185,5 +169,29 @@ module DiscourseAssign
|
|||
def ensure_assign_allowed
|
||||
raise Discourse::InvalidAccess.new unless current_user.can_assign?
|
||||
end
|
||||
|
||||
def reassign_or_assign_target(action:)
|
||||
target_id = params.require(:target_id)
|
||||
target_type = params.require(:target_type)
|
||||
username = params.permit(:username)['username']
|
||||
group_name = params.permit(:group_name)['group_name']
|
||||
|
||||
assign_to = username.present? ? User.find_by(username_lower: username.downcase) : Group.where("LOWER(name) = ?", group_name.downcase).first
|
||||
|
||||
raise Discourse::NotFound unless assign_to
|
||||
raise Discourse::NotFound if !Assignment.valid_type?(target_type)
|
||||
target = target_type.constantize.where(id: target_id).first
|
||||
raise Discourse::NotFound unless target
|
||||
|
||||
# perhaps?
|
||||
#Scheduler::Defer.later "assign topic" do
|
||||
assign = Assigner.new(target, current_user).send(action, assign_to)
|
||||
|
||||
if assign[:success]
|
||||
render json: success_json
|
||||
else
|
||||
render json: translate_failure(assign[:reason], assign_to), status: 400
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,6 @@ import { popupAjaxError } from "discourse/lib/ajax-error";
|
|||
import { not, or } from "@ember/object/computed";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { action } from "@ember/object";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Controller.extend({
|
||||
topicBulkActions: controller(),
|
||||
|
@ -39,14 +38,43 @@ export default Controller.extend({
|
|||
});
|
||||
},
|
||||
|
||||
@discourseComputed("model.targetType")
|
||||
i18nSuffix(targetType) {
|
||||
switch (targetType) {
|
||||
case "Post":
|
||||
return "_post_modal";
|
||||
case "Topic":
|
||||
return "_modal";
|
||||
reassignOrAssignTarget(assign_action) {
|
||||
if (this.isBulkAction) {
|
||||
this.bulkAction(this.model.username);
|
||||
return;
|
||||
}
|
||||
let path = "/assign/" + assign_action;
|
||||
|
||||
if (isEmpty(this.get("model.username"))) {
|
||||
this.model.target.set("assigned_to_user", null);
|
||||
}
|
||||
|
||||
if (isEmpty(this.get("model.group_name"))) {
|
||||
this.model.target.set("assigned_to_group", null);
|
||||
}
|
||||
|
||||
if (
|
||||
isEmpty(this.get("model.username")) &&
|
||||
isEmpty(this.get("model.group_name"))
|
||||
) {
|
||||
path = "/assign/unassign";
|
||||
}
|
||||
|
||||
this.send("closeModal");
|
||||
|
||||
return ajax(path, {
|
||||
type: "PUT",
|
||||
data: {
|
||||
username: this.get("model.username"),
|
||||
group_name: this.get("model.group_name"),
|
||||
target_id: this.get("model.target.id"),
|
||||
target_type: this.get("model.targetType"),
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.get("model.onSuccess")?.();
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
@action
|
||||
|
@ -77,48 +105,47 @@ export default Controller.extend({
|
|||
|
||||
@action
|
||||
assign() {
|
||||
this.reassignOrAssignTarget("assign");
|
||||
},
|
||||
|
||||
@action
|
||||
reassign() {
|
||||
this.reassignOrAssignTarget("reassign");
|
||||
},
|
||||
|
||||
@action
|
||||
reassignUser(name) {
|
||||
if (this.isBulkAction) {
|
||||
this.bulkAction(this.model.username);
|
||||
this.bulkAction(name);
|
||||
return;
|
||||
}
|
||||
let path = "/assign/assign";
|
||||
|
||||
if (isEmpty(this.get("model.username"))) {
|
||||
this.model.target.set("assigned_to_user", null);
|
||||
if (this.allowedGroupsForAssignment.includes(name)) {
|
||||
this.setProperties({
|
||||
"model.username": null,
|
||||
"model.group_name": name,
|
||||
"model.allowedGroups": this.taskActions.allowedGroups,
|
||||
});
|
||||
} else {
|
||||
this.setProperties({
|
||||
"model.username": name,
|
||||
"model.group_name": null,
|
||||
"model.allowedGroups": this.taskActions.allowedGroups,
|
||||
});
|
||||
}
|
||||
|
||||
if (isEmpty(this.get("model.group_name"))) {
|
||||
this.model.target.set("assigned_to_group", null);
|
||||
if (name) {
|
||||
return this.reassign();
|
||||
}
|
||||
|
||||
if (
|
||||
isEmpty(this.get("model.username")) &&
|
||||
isEmpty(this.get("model.group_name"))
|
||||
) {
|
||||
path = "/assign/unassign";
|
||||
}
|
||||
|
||||
this.send("closeModal");
|
||||
|
||||
return ajax(path, {
|
||||
type: "PUT",
|
||||
data: {
|
||||
username: this.get("model.username"),
|
||||
group_name: this.get("model.group_name"),
|
||||
target_id: this.get("model.target.id"),
|
||||
target_type: this.get("model.targetType"),
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
if (this.get("model.onSuccess")) {
|
||||
this.get("model.onSuccess")();
|
||||
}
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
@action
|
||||
assignUsername(selected) {
|
||||
this.assignUser(selected.firstObject);
|
||||
},
|
||||
|
||||
@action
|
||||
reassignUsername(selected) {
|
||||
this.reassignUser(selected.firstObject);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -14,9 +14,17 @@ import TopicButtonAction, {
|
|||
import { inject } from "@ember/controller";
|
||||
import I18n from "I18n";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { registerTopicFooterDropdown } from "discourse/lib/register-topic-footer-dropdown";
|
||||
|
||||
const PLUGIN_ID = "discourse-assign";
|
||||
|
||||
const DEPENDENT_KEYS = [
|
||||
"topic.assigned_to_user",
|
||||
"topic.assigned_to_group",
|
||||
"currentUser.can_assign",
|
||||
"topic.assigned_to_user.username",
|
||||
];
|
||||
|
||||
function titleForState(name) {
|
||||
if (name) {
|
||||
return I18n.t("discourse_assign.unassign.help", {
|
||||
|
@ -27,14 +35,124 @@ function titleForState(name) {
|
|||
}
|
||||
}
|
||||
|
||||
function defaultTitle(topic) {
|
||||
return titleForState(
|
||||
topic.get("topic.assigned_to_user.username") ||
|
||||
topic.get("topic.assigned_to_group.name")
|
||||
);
|
||||
}
|
||||
|
||||
function includeIsAssignedOnTopic(api) {
|
||||
api.modifyClass("model:topic", {
|
||||
pluginId: PLUGIN_ID,
|
||||
isAssigned() {
|
||||
return this.assigned_to_user || this.assigned_to_group;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function registerTopicFooterButtons(api) {
|
||||
registerTopicFooterDropdown({
|
||||
id: "reassign",
|
||||
|
||||
action(id) {
|
||||
if (!this.get("currentUser.can_assign")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const taskActions = getOwner(this).lookup("service:task-actions");
|
||||
|
||||
switch (id) {
|
||||
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,
|
||||
});
|
||||
});
|
||||
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,
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "reassign": {
|
||||
taskActions.reassign(this.topic).set("model.onSuccess", () => {
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
noneItem() {
|
||||
const user = this.get("topic.assigned_to_user");
|
||||
const group = this.get("topic.assigned_to_group");
|
||||
const label = I18n.t("discourse_assign.unassign.title_w_ellipsis");
|
||||
const groupLabel = I18n.t("discourse_assign.unassign.title");
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
id: null,
|
||||
name: htmlSafe(
|
||||
`${renderAvatar(user, {
|
||||
imageSize: "tiny",
|
||||
ignoreTitle: true,
|
||||
})}<span class="unassign-label">${label}</span>`
|
||||
),
|
||||
};
|
||||
} else if (group) {
|
||||
return {
|
||||
id: null,
|
||||
name: htmlSafe(
|
||||
`<span class="unassign-label">${groupLabel}</span> @${group.name}...`
|
||||
),
|
||||
};
|
||||
}
|
||||
},
|
||||
dependentKeys: DEPENDENT_KEYS,
|
||||
classNames: ["reassign"],
|
||||
content() {
|
||||
const content = [
|
||||
{ id: "unassign", name: I18n.t("discourse_assign.unassign.title") },
|
||||
];
|
||||
if (
|
||||
this.topic.isAssigned() &&
|
||||
this.get("topic.assigned_to_user")?.username !==
|
||||
this.currentUser.username
|
||||
) {
|
||||
content.push({
|
||||
id: "reassign-self",
|
||||
name: I18n.t("discourse_assign.reassign.to_self"),
|
||||
});
|
||||
}
|
||||
content.push({
|
||||
id: "reassign",
|
||||
name: I18n.t("discourse_assign.reassign.title_w_ellipsis"),
|
||||
});
|
||||
return content;
|
||||
},
|
||||
|
||||
displayed() {
|
||||
return !this.site.mobileView && this.topic.isAssigned();
|
||||
},
|
||||
});
|
||||
|
||||
api.registerTopicFooterButton({
|
||||
id: "assign",
|
||||
icon() {
|
||||
const hasAssignement =
|
||||
this.get("topic.assigned_to_user") ||
|
||||
this.get("topic.assigned_to_group");
|
||||
return hasAssignement
|
||||
return this.topic.isAssigned()
|
||||
? this.site.mobileView
|
||||
? "user-times"
|
||||
: null
|
||||
|
@ -42,47 +160,13 @@ function registerTopicFooterButtons(api) {
|
|||
},
|
||||
priority: 250,
|
||||
translatedTitle() {
|
||||
return titleForState(
|
||||
this.get("topic.assigned_to_user.username") ||
|
||||
this.get("topic.assigned_to_group.name")
|
||||
);
|
||||
defaultTitle(this);
|
||||
},
|
||||
translatedAriaLabel() {
|
||||
return titleForState(
|
||||
this.get("topic.assigned_to_user.username") ||
|
||||
this.get("topic.assigned_to_group.name")
|
||||
);
|
||||
defaultTitle(this);
|
||||
},
|
||||
translatedLabel() {
|
||||
const user = this.get("topic.assigned_to_user");
|
||||
const group = this.get("topic.assigned_to_group");
|
||||
const label = I18n.t("discourse_assign.unassign.title");
|
||||
|
||||
if (user) {
|
||||
if (this.site.mobileView) {
|
||||
return htmlSafe(
|
||||
`<span class="unassign-label"><span class="text">${label}</span><span class="username">${
|
||||
user.username
|
||||
}</span></span>${renderAvatar(user, {
|
||||
imageSize: "small",
|
||||
ignoreTitle: true,
|
||||
})}`
|
||||
);
|
||||
} else {
|
||||
return htmlSafe(
|
||||
`${renderAvatar(user, {
|
||||
imageSize: "tiny",
|
||||
ignoreTitle: true,
|
||||
})}<span class="unassign-label">${label}</span>`
|
||||
);
|
||||
}
|
||||
} else if (group) {
|
||||
return htmlSafe(
|
||||
`<span class="unassign-label">${label}</span> @${group.name}`
|
||||
);
|
||||
} else {
|
||||
return I18n.t("discourse_assign.assign.title");
|
||||
}
|
||||
return I18n.t("discourse_assign.assign.title");
|
||||
},
|
||||
action() {
|
||||
if (!this.get("currentUser.can_assign")) {
|
||||
|
@ -90,20 +174,19 @@ function registerTopicFooterButtons(api) {
|
|||
}
|
||||
|
||||
const taskActions = getOwner(this).lookup("service:task-actions");
|
||||
const topic = this.topic;
|
||||
|
||||
if (topic.assigned_to_user || topic.assigned_to_group) {
|
||||
if (this.topic.isAssigned()) {
|
||||
this.set("topic.assigned_to_user", null);
|
||||
this.set("topic.assigned_to_group", null);
|
||||
taskActions.unassign(topic.id, "Topic").then(() => {
|
||||
taskActions.unassign(this.topic.id, "Topic").then(() => {
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: topic.postStream.firstPostId,
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
taskActions.assign(topic).set("model.onSuccess", () => {
|
||||
taskActions.assign(this.topic).set("model.onSuccess", () => {
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: topic.postStream.firstPostId,
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -112,14 +195,183 @@ function registerTopicFooterButtons(api) {
|
|||
return this.site.mobileView;
|
||||
},
|
||||
classNames: ["assign"],
|
||||
dependentKeys: [
|
||||
"topic.assigned_to_user",
|
||||
"topic.assigned_to_group",
|
||||
"currentUser.can_assign",
|
||||
"topic.assigned_to_user.username",
|
||||
],
|
||||
dependentKeys: DEPENDENT_KEYS,
|
||||
displayed() {
|
||||
return this.currentUser && this.currentUser.can_assign;
|
||||
return this.currentUser?.can_assign && !this.topic.isAssigned();
|
||||
},
|
||||
});
|
||||
|
||||
api.registerTopicFooterButton({
|
||||
id: "unassign-mobile-header",
|
||||
translatedTitle() {
|
||||
defaultTitle(this);
|
||||
},
|
||||
translatedAriaLabel() {
|
||||
defaultTitle(this);
|
||||
},
|
||||
translatedLabel() {
|
||||
const user = this.get("topic.assigned_to_user");
|
||||
const group = this.get("topic.assigned_to_group");
|
||||
const label = I18n.t("discourse_assign.assigned_to_w_ellipsis");
|
||||
|
||||
if (user) {
|
||||
return htmlSafe(
|
||||
`<span class="unassign-label"><span class="text">${label}</span><span class="username">${
|
||||
user.username
|
||||
}</span></span>${renderAvatar(user, {
|
||||
imageSize: "small",
|
||||
ignoreTitle: true,
|
||||
})}`
|
||||
);
|
||||
} else if (group) {
|
||||
return htmlSafe(
|
||||
`<span class="unassign-label">${label}</span> @${group.name}`
|
||||
);
|
||||
}
|
||||
},
|
||||
dropdown() {
|
||||
return this.currentUser?.can_assign && this.topic.isAssigned();
|
||||
},
|
||||
classNames: ["assign"],
|
||||
dependentKeys: DEPENDENT_KEYS,
|
||||
displayed() {
|
||||
// only display the button in the mobile view
|
||||
return this.site.mobileView;
|
||||
},
|
||||
});
|
||||
|
||||
api.registerTopicFooterButton({
|
||||
id: "unassign-mobile",
|
||||
icon() {
|
||||
return "user-times";
|
||||
},
|
||||
translatedTitle() {
|
||||
defaultTitle(this);
|
||||
},
|
||||
translatedAriaLabel() {
|
||||
defaultTitle(this);
|
||||
},
|
||||
translatedLabel() {
|
||||
const label = I18n.t("discourse_assign.unassign.title");
|
||||
|
||||
return htmlSafe(
|
||||
`<span class="unassign-label"><span class="text">${label}</span></span>`
|
||||
);
|
||||
},
|
||||
action() {
|
||||
if (!this.get("currentUser.can_assign")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const taskActions = getOwner(this).lookup("service:task-actions");
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
},
|
||||
dropdown() {
|
||||
return this.currentUser?.can_assign && this.topic.isAssigned();
|
||||
},
|
||||
classNames: ["assign"],
|
||||
dependentKeys: DEPENDENT_KEYS,
|
||||
displayed() {
|
||||
// only display the button in the mobile view
|
||||
return this.site.mobileView && this.topic.isAssigned();
|
||||
},
|
||||
});
|
||||
|
||||
api.registerTopicFooterButton({
|
||||
id: "reassign-self-mobile",
|
||||
icon() {
|
||||
return "user-plus";
|
||||
},
|
||||
translatedTitle() {
|
||||
defaultTitle(this);
|
||||
},
|
||||
translatedAriaLabel() {
|
||||
defaultTitle(this);
|
||||
},
|
||||
translatedLabel() {
|
||||
const label = I18n.t("discourse_assign.reassign.to_self");
|
||||
|
||||
return htmlSafe(
|
||||
`<span class="unassign-label"><span class="text">${label}</span></span>`
|
||||
);
|
||||
},
|
||||
action() {
|
||||
if (!this.get("currentUser.can_assign")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const taskActions = getOwner(this).lookup("service:task-actions");
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
},
|
||||
dropdown() {
|
||||
return this.currentUser?.can_assign && this.topic.isAssigned();
|
||||
},
|
||||
classNames: ["assign"],
|
||||
dependentKeys: DEPENDENT_KEYS,
|
||||
displayed() {
|
||||
return (
|
||||
// only display the button in the mobile view
|
||||
this.site.mobileView &&
|
||||
this.topic.isAssigned() &&
|
||||
this.get("topic.assigned_to_user")?.username !==
|
||||
this.currentUser.username
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
api.registerTopicFooterButton({
|
||||
id: "reassign-mobile",
|
||||
icon() {
|
||||
return "user-plus";
|
||||
},
|
||||
translatedTitle() {
|
||||
defaultTitle(this);
|
||||
},
|
||||
translatedAriaLabel() {
|
||||
defaultTitle(this);
|
||||
},
|
||||
translatedLabel() {
|
||||
const label = I18n.t("discourse_assign.reassign.title_w_ellipsis");
|
||||
|
||||
return htmlSafe(
|
||||
`<span class="unassign-label"><span class="text">${label}</span></span>`
|
||||
);
|
||||
},
|
||||
action() {
|
||||
if (!this.get("currentUser.can_assign")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const taskActions = getOwner(this).lookup("service:task-actions");
|
||||
|
||||
taskActions.reassign(this.topic).set("model.onSuccess", () => {
|
||||
this.appEvents.trigger("post-stream:refresh", {
|
||||
id: this.topic.postStream.firstPostId,
|
||||
});
|
||||
});
|
||||
},
|
||||
dropdown() {
|
||||
return this.currentUser?.can_assign && this.topic.isAssigned();
|
||||
},
|
||||
classNames: ["assign"],
|
||||
dependentKeys: DEPENDENT_KEYS,
|
||||
displayed() {
|
||||
// only display the button in the mobile view
|
||||
return this.site.mobileView;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -240,16 +492,19 @@ function initialize(api) {
|
|||
api.addPostSmallActionIcon("unassigned_group", "group-times");
|
||||
api.addPostSmallActionIcon("unassigned_from_post", "user-times");
|
||||
api.addPostSmallActionIcon("unassigned_group_from_post", "group-times");
|
||||
|
||||
api.includePostAttributes("assigned_to_user", "assigned_to_group");
|
||||
api.addPostSmallActionIcon("reassigned", "user-plus");
|
||||
api.addPostSmallActionIcon("reassigned_group", "group-plus");
|
||||
|
||||
api.addPostTransformCallback((transformed) => {
|
||||
if (
|
||||
[
|
||||
"assigned",
|
||||
"unassigned",
|
||||
"reassigned",
|
||||
"assigned_group",
|
||||
"unassigned_group",
|
||||
"reassigned_group",
|
||||
"assigned_to_post",
|
||||
"assigned_group_to_post",
|
||||
"unassigned_from_post",
|
||||
|
@ -592,6 +847,7 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
withPluginApi("0.13.0", (api) => includeIsAssignedOnTopic(api));
|
||||
withPluginApi("0.11.0", (api) => initialize(api));
|
||||
withPluginApi("0.8.28", (api) => registerTopicFooterButtons(api));
|
||||
|
||||
|
|
|
@ -3,6 +3,15 @@ import { ajax } from "discourse/lib/ajax";
|
|||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
export default Service.extend({
|
||||
i18nSuffix(targetType) {
|
||||
switch (targetType) {
|
||||
case "Post":
|
||||
return "_post_modal";
|
||||
case "Topic":
|
||||
return "_modal";
|
||||
}
|
||||
},
|
||||
|
||||
unassign(targetId, targetType = "Topic") {
|
||||
return ajax("/assign/unassign", {
|
||||
type: "PUT",
|
||||
|
@ -15,22 +24,47 @@ export default Service.extend({
|
|||
|
||||
assign(target, targetType = "Topic") {
|
||||
return showModal("assign-user", {
|
||||
title: "discourse_assign.assign" + this.i18nSuffix(targetType) + ".title",
|
||||
model: {
|
||||
username: target.get("assigned_to_user.username"),
|
||||
group_name: target.get("assigned_to_group.name"),
|
||||
description:
|
||||
"discourse_assign.assign" +
|
||||
this.i18nSuffix(targetType) +
|
||||
".description",
|
||||
username: target.assigned_to_user?.username,
|
||||
group_name: target.assigned_to_group?.name,
|
||||
target,
|
||||
targetType,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
assignUserToTopic(user, topic) {
|
||||
return ajax("/assign/assign", {
|
||||
reassignUserToTopic(user, target, targetType = "Topic") {
|
||||
return ajax("/assign/reassign", {
|
||||
type: "PUT",
|
||||
data: {
|
||||
username: user.username,
|
||||
target_id: topic.id,
|
||||
target_type: "Topic",
|
||||
target_id: target.id,
|
||||
target_type: targetType,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
reassign(target, targetType = "Topic") {
|
||||
return showModal("assign-user", {
|
||||
title:
|
||||
"discourse_assign.assign" +
|
||||
this.i18nSuffix(targetType) +
|
||||
".reassign_title",
|
||||
model: {
|
||||
description:
|
||||
"discourse_assign.reassign" +
|
||||
this.i18nSuffix(targetType) +
|
||||
".description",
|
||||
reassign: true,
|
||||
username: target.assigned_to_user?.username,
|
||||
group_name: target.assigned_to_group?.name,
|
||||
target,
|
||||
targetType,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{{#d-modal-body title=(concat "discourse_assign.assign" i18nSuffix ".title") class="assign"}}
|
||||
{{#d-modal-body class="assign"}}
|
||||
<div>
|
||||
<p>{{i18n (concat "discourse_assign.assign" i18nSuffix ".description")}}</p>
|
||||
<p>{{i18n model.description}}</p>
|
||||
{{email-group-user-chooser
|
||||
autocomplete="off"
|
||||
value=assigneeName
|
||||
onChange=(action "assignUsername")
|
||||
onChange=(action (if model.reassign "reassignUsername" "assignUsername"))
|
||||
autofocus="autofocus"
|
||||
options=(hash
|
||||
placementStrategy="absolute"
|
||||
|
@ -19,7 +19,7 @@
|
|||
}}
|
||||
<div class="assign-suggestions">
|
||||
{{#each assignSuggestions as |user|}}
|
||||
<a href {{action "assignUser" user.username }}>
|
||||
<a href {{action (if model.reassign "reassignUser" "assignUser") user.username }}>
|
||||
{{avatar user imageSize="small"}}
|
||||
</a>
|
||||
{{/each}}
|
||||
|
@ -29,9 +29,9 @@
|
|||
|
||||
<div class="modal-footer">
|
||||
{{d-button
|
||||
label="discourse_assign.assign_modal.assign"
|
||||
label= (if model.reassign "discourse_assign.reassign.title" "discourse_assign.assign_modal.assign")
|
||||
icon=inviteIcon
|
||||
action=(action "assign")
|
||||
action=(action (if model.reassign "reassign" "assign"))
|
||||
class="btn-primary"
|
||||
disabled=disabled
|
||||
}}
|
||||
|
|
|
@ -107,6 +107,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.topic-footer-dropdown {
|
||||
.avatar {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
// Group assigns sidebar nav
|
||||
|
||||
.group-assignments {
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.select-kit-row[data-value="unassign-mobile-header"] {
|
||||
background: rgba(var(--primary-low-rgb), 0.5);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.select-kit-row.assign {
|
||||
.name {
|
||||
display: flex;
|
||||
|
|
|
@ -13,6 +13,8 @@ en:
|
|||
unassigned_group: "unassigned %{who} %{when}"
|
||||
unassigned_from_post: "unassigned %{who} from <a href='%{path}' data-auto-route='true'>post</a> %{when}"
|
||||
unassigned_group_from_post: "unassigned %{who} from <a href='%{path}' data-auto-route='true'>post</a> %{when}"
|
||||
reassigned: "Re-assigned %{who} %{when}"
|
||||
reassigned_group: "Re-assigned %{who} %{when}"
|
||||
discourse_assign:
|
||||
add_unassigned_filter: "Add 'unassigned' filter to category"
|
||||
cant_act: "You cannot act on flags that have been assigned to other users"
|
||||
|
@ -22,10 +24,12 @@ en:
|
|||
assigned: "Assigned"
|
||||
group_everyone: "Everyone"
|
||||
assigned_to: "Assigned to"
|
||||
assigned_to_w_ellipsis: "Assigned to..."
|
||||
assign_notification: "<p><span>%{username}</span> %{description}</p>"
|
||||
assign_group_notification: "<p><span>%{username}</span> %{description}</p>"
|
||||
unassign:
|
||||
title: "Unassign"
|
||||
title_w_ellipsis: "Unassign..."
|
||||
help: "Unassign %{username} from Topic"
|
||||
assign:
|
||||
title: "Assign"
|
||||
|
@ -37,9 +41,15 @@ en:
|
|||
help: "Unassign %{username} from Post"
|
||||
reassign:
|
||||
title: "Re-assign"
|
||||
title_w_ellipsis: "Re-assign to..."
|
||||
to_self: "Re-assign to me"
|
||||
help: "Re-assign Topic to a different user"
|
||||
reassign_modal:
|
||||
title: "Re-assign Topic"
|
||||
description: "Enter the name of the user you'd like to Re-assign this topic"
|
||||
assign_modal:
|
||||
title: "Assign Topic"
|
||||
reassign_title: "Re-assign Topic"
|
||||
description: "Enter the name of the user you'd like to assign this topic"
|
||||
assign: "Assign"
|
||||
assign_post_modal:
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
DiscourseAssign::Engine.routes.draw do
|
||||
put "/claim/:topic_id" => "assign#claim"
|
||||
put "/assign" => "assign#assign"
|
||||
put "/reassign" => "assign#reassign"
|
||||
put "/unassign" => "assign#unassign"
|
||||
get "/suggestions" => "assign#suggestions"
|
||||
get "/assigned" => "assign#assigned"
|
||||
|
|
|
@ -180,7 +180,7 @@ class ::Assigner
|
|||
topic.posts.where(post_number: 1).first
|
||||
end
|
||||
|
||||
def assign(assign_to, silent: false)
|
||||
def forbidden_reasons(assign_to:, type:)
|
||||
forbidden_reason =
|
||||
case
|
||||
when assign_to.is_a?(User) && !can_assignee_see_target?(assign_to)
|
||||
|
@ -189,19 +189,18 @@ class ::Assigner
|
|||
topic.private_message? ? :forbidden_group_assignee_not_pm_participant : :forbidden_group_assignee_cant_see_topic
|
||||
when !can_be_assigned?(assign_to)
|
||||
assign_to.is_a?(User) ? :forbidden_assign_to : :forbidden_group_assign_to
|
||||
when topic.assignment&.assigned_to_id == assign_to.id
|
||||
when topic.assignment&.assigned_to_id == assign_to.id && topic.assignment&.assigned_to_type == type
|
||||
assign_to.is_a?(User) ? :already_assigned : :group_already_assigned
|
||||
when Assignment.where(topic: topic).count >= ASSIGNMENTS_PER_TOPIC_LIMIT
|
||||
:too_many_assigns_for_topic
|
||||
when !can_assign_to?(assign_to)
|
||||
:too_many_assigns
|
||||
end
|
||||
end
|
||||
|
||||
return { success: false, reason: forbidden_reason } if forbidden_reason
|
||||
|
||||
def assign_or_reassign_target(assign_to:, type:, silent:, action_code:)
|
||||
@target.assignment&.destroy!
|
||||
|
||||
type = assign_to.is_a?(User) ? "User" : "Group"
|
||||
assignment = @target.create_assignment!(assigned_to_id: assign_to.id, assigned_to_type: type, assigned_by_user_id: @assigned_by.id, topic_id: topic.id)
|
||||
|
||||
first_post.publish_change_to_clients!(:revised, reload_topic: true)
|
||||
|
@ -262,7 +261,7 @@ class ::Assigner
|
|||
nil,
|
||||
bump: false,
|
||||
post_type: SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper],
|
||||
action_code: moderator_post_assign_action_code(assignment),
|
||||
action_code: moderator_post_assign_action_code(assignment, action_code),
|
||||
custom_fields: custom_fields
|
||||
)
|
||||
end
|
||||
|
@ -296,6 +295,26 @@ class ::Assigner
|
|||
{ success: true }
|
||||
end
|
||||
|
||||
def assign(assign_to, silent: false)
|
||||
type = assign_to.is_a?(User) ? "User" : "Group"
|
||||
|
||||
forbidden_reason = forbidden_reasons(assign_to: assign_to, type: type)
|
||||
return { success: false, reason: forbidden_reason } if forbidden_reason
|
||||
|
||||
action_code = { user: "assigned", group: "assigned_group" }
|
||||
assign_or_reassign_target(assign_to: assign_to, type: type, silent: silent, action_code: action_code)
|
||||
end
|
||||
|
||||
def reassign(assign_to, silent: false)
|
||||
type = assign_to.is_a?(User) ? "User" : "Group"
|
||||
|
||||
forbidden_reason = forbidden_reasons(assign_to: assign_to, type: type)
|
||||
return { success: false, reason: forbidden_reason } if forbidden_reason
|
||||
|
||||
action_code = { user: "reassigned", group: "reassigned_group" }
|
||||
assign_or_reassign_target(assign_to: assign_to, type: type, silent: silent, action_code: action_code)
|
||||
end
|
||||
|
||||
def unassign(silent: false)
|
||||
if assignment = @target.assignment
|
||||
assignment.destroy!
|
||||
|
@ -386,15 +405,15 @@ class ::Assigner
|
|||
|
||||
private
|
||||
|
||||
def moderator_post_assign_action_code(assignment)
|
||||
def moderator_post_assign_action_code(assignment, action_code)
|
||||
suffix =
|
||||
if assignment.target.is_a?(Post)
|
||||
"_to_post"
|
||||
elsif assignment.target.is_a?(Topic)
|
||||
""
|
||||
end
|
||||
return "assigned#{suffix}" if assignment.assigned_to_user?
|
||||
return "assigned_group#{suffix}" if assignment.assigned_to_group?
|
||||
return "#{action_code[:user]}#{suffix}" if assignment.assigned_to_user?
|
||||
return "#{action_code[:group]}#{suffix}" if assignment.assigned_to_group?
|
||||
end
|
||||
|
||||
def moderator_post_unassign_action_code(assignment)
|
||||
|
|
|
@ -8,15 +8,9 @@ import {
|
|||
import { visit } from "@ember/test-helpers";
|
||||
import { cloneJSON } from "discourse-common/lib/object";
|
||||
import topicFixtures from "discourse/tests/fixtures/topic";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
|
||||
acceptance("Discourse Assign | Assigned topic", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
assign_enabled: true,
|
||||
tagging_enabled: true,
|
||||
assigns_user_url_path: "/",
|
||||
});
|
||||
|
||||
function assignCurrentUserToTopic(needs) {
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/t/44.json", () => {
|
||||
let topic = cloneJSON(topicFixtures["/t/28830/1.json"]);
|
||||
|
@ -42,6 +36,45 @@ acceptance("Discourse Assign | Assigned topic", function (needs) {
|
|||
return helper.response(topic);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function assignNewUserToTopic(needs) {
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/t/44.json", () => {
|
||||
let topic = cloneJSON(topicFixtures["/t/28830/1.json"]);
|
||||
topic["assigned_to_user"] = {
|
||||
username: "isaacjanzen",
|
||||
name: "Isaac Janzen",
|
||||
avatar_template:
|
||||
"/letter_avatar/isaacjanzen/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
|
||||
};
|
||||
topic["indirectly_assigned_to"] = {
|
||||
2: {
|
||||
name: "Developers",
|
||||
},
|
||||
};
|
||||
return helper.response(topic);
|
||||
});
|
||||
|
||||
server.get("/t/45.json", () => {
|
||||
let topic = cloneJSON(topicFixtures["/t/28830/1.json"]);
|
||||
topic["assigned_to_group"] = {
|
||||
name: "Developers",
|
||||
};
|
||||
return helper.response(topic);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
acceptance("Discourse Assign | Assigned topic", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
assign_enabled: true,
|
||||
tagging_enabled: true,
|
||||
assigns_user_url_path: "/",
|
||||
});
|
||||
|
||||
assignCurrentUserToTopic(needs);
|
||||
|
||||
test("Shows user assignment info", async (assert) => {
|
||||
updateCurrentUser({ can_assign: true });
|
||||
|
@ -59,8 +92,8 @@ acceptance("Discourse Assign | Assigned topic", function (needs) {
|
|||
);
|
||||
assert.ok(exists("#post_1 .assigned-to svg.d-icon-user-plus"));
|
||||
assert.ok(
|
||||
exists("#topic-footer-button-assign .unassign-label"),
|
||||
"shows unassign button at the bottom of the topic"
|
||||
exists("#topic-footer-dropdown-reassign"),
|
||||
"shows reassign dropdown at the bottom of the topic"
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -80,8 +113,76 @@ acceptance("Discourse Assign | Assigned topic", function (needs) {
|
|||
);
|
||||
assert.ok(exists("#post_1 .assigned-to svg.d-icon-group-plus"));
|
||||
assert.ok(
|
||||
exists("#topic-footer-button-assign .unassign-label"),
|
||||
"shows unassign button at the bottom of the topic"
|
||||
exists("#topic-footer-dropdown-reassign"),
|
||||
"shows reassign dropdown at the bottom of the topic"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Discourse Assign | Re-assign topic", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
assign_enabled: true,
|
||||
tagging_enabled: true,
|
||||
assigns_user_url_path: "/",
|
||||
});
|
||||
|
||||
assignNewUserToTopic(needs);
|
||||
|
||||
test("Re-assign Footer dropdown contains reassign buttons", async (assert) => {
|
||||
updateCurrentUser({ can_assign: true });
|
||||
const menu = selectKit("#topic-footer-dropdown-reassign");
|
||||
|
||||
await visit("/t/assignment-topic/44");
|
||||
await menu.expand();
|
||||
|
||||
assert.ok(menu.rowByValue("unassign").exists());
|
||||
assert.ok(menu.rowByValue("reassign").exists());
|
||||
assert.ok(menu.rowByValue("reassign-self").exists());
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Discourse Assign | Re-assign topic | mobile", function (needs) {
|
||||
needs.user();
|
||||
needs.mobileView();
|
||||
needs.settings({
|
||||
assign_enabled: true,
|
||||
tagging_enabled: true,
|
||||
assigns_user_url_path: "/",
|
||||
});
|
||||
|
||||
assignNewUserToTopic(needs);
|
||||
|
||||
test("Mobile Footer dropdown contains reassign buttons", async (assert) => {
|
||||
updateCurrentUser({ can_assign: true });
|
||||
const menu = selectKit(".topic-footer-mobile-dropdown");
|
||||
|
||||
await visit("/t/assignment-topic/44");
|
||||
await menu.expand();
|
||||
|
||||
assert.ok(menu.rowByValue("unassign-mobile").exists());
|
||||
assert.ok(menu.rowByValue("reassign-mobile").exists());
|
||||
assert.ok(menu.rowByValue("reassign-self-mobile").exists());
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Discourse Assign | Re-assign topic conditionals", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
assign_enabled: true,
|
||||
tagging_enabled: true,
|
||||
assigns_user_url_path: "/",
|
||||
});
|
||||
|
||||
assignCurrentUserToTopic(needs);
|
||||
|
||||
test("Reassign Footer dropdown won't display reassign-to-self button when already assigned to current user", async (assert) => {
|
||||
updateCurrentUser({ can_assign: true });
|
||||
const menu = selectKit("#topic-footer-dropdown-reassign");
|
||||
|
||||
await visit("/t/assignment-topic/44");
|
||||
await menu.expand();
|
||||
|
||||
assert.notOk(menu.rowByValue("reassign-self").exists());
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue