FEATURE: assign to post (#224)
Ability to assign an individual post to a user or group
This commit is contained in:
parent
54c670536c
commit
c396605d2f
|
@ -133,24 +133,24 @@ module DiscourseAssign
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
group_assignment_count = Topic
|
group_assignments = Topic
|
||||||
.joins("JOIN assignments a ON a.target_id = topics.id AND a.target_type = 'Topic'")
|
.joins("JOIN assignments a ON a.topic_id = topics.id")
|
||||||
.where(<<~SQL, group_id: group.id)
|
.where(<<~SQL, group_id: group.id)
|
||||||
a.assigned_to_id = :group_id AND a.assigned_to_type = 'Group'
|
a.assigned_to_id = :group_id AND a.assigned_to_type = 'Group'
|
||||||
SQL
|
SQL
|
||||||
.count
|
.pluck(:topic_id)
|
||||||
|
|
||||||
assignment_count = Topic
|
assignments = Topic
|
||||||
.joins("JOIN assignments a ON a.target_id = topics.id AND a.target_type = 'Topic'")
|
.joins("JOIN assignments a ON a.topic_id = topics.id")
|
||||||
.joins("JOIN group_users ON group_users.user_id = a.assigned_to_id ")
|
.joins("JOIN group_users ON group_users.user_id = a.assigned_to_id ")
|
||||||
.where("group_users.group_id = ?", group.id)
|
.where("group_users.group_id = ?", group.id)
|
||||||
.where("a.assigned_to_type = 'User'")
|
.where("a.assigned_to_type = 'User'")
|
||||||
.count
|
.pluck(:topic_id)
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
members: serialize_data(members, GroupUserAssignedSerializer),
|
members: serialize_data(members, GroupUserAssignedSerializer),
|
||||||
assignment_count: assignment_count + group_assignment_count,
|
assignment_count: (assignments | group_assignments).count,
|
||||||
group_assignment_count: group_assignment_count
|
group_assignment_count: group_assignments.count
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -174,6 +174,8 @@ module DiscourseAssign
|
||||||
{ error: I18n.t('discourse_assign.forbidden_group_assignee_not_pm_participant', group: assign_to.name) }
|
{ error: I18n.t('discourse_assign.forbidden_group_assignee_not_pm_participant', group: assign_to.name) }
|
||||||
when :forbidden_group_assignee_cant_see_topic
|
when :forbidden_group_assignee_cant_see_topic
|
||||||
{ error: I18n.t('discourse_assign.forbidden_group_assignee_cant_see_topic', group: assign_to.name) }
|
{ error: I18n.t('discourse_assign.forbidden_group_assignee_cant_see_topic', group: assign_to.name) }
|
||||||
|
when :too_many_assigns_for_topic
|
||||||
|
{ error: I18n.t('discourse_assign.too_many_assigns_for_topic', limit: Assigner::ASSIGNMENTS_PER_TOPIC_LIMIT) }
|
||||||
else
|
else
|
||||||
max = SiteSetting.max_assigned_topics
|
max = SiteSetting.max_assigned_topics
|
||||||
{ error: I18n.t('discourse_assign.too_many_assigns', username: assign_to.username, max: max) }
|
{ error: I18n.t('discourse_assign.too_many_assigns', username: assign_to.username, max: max) }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Assignment < ActiveRecord::Base
|
class Assignment < ActiveRecord::Base
|
||||||
VALID_TYPES = %w(topic).freeze
|
VALID_TYPES = %w(topic post).freeze
|
||||||
|
|
||||||
belongs_to :topic
|
belongs_to :topic
|
||||||
belongs_to :assigned_to, polymorphic: true
|
belongs_to :assigned_to, polymorphic: true
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { not, or } from "@ember/object/computed";
|
import { not, or } from "@ember/object/computed";
|
||||||
import { isEmpty } from "@ember/utils";
|
import { isEmpty } from "@ember/utils";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
topicBulkActions: controller(),
|
topicBulkActions: controller(),
|
||||||
|
@ -38,6 +39,16 @@ export default Controller.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@discourseComputed("model.targetType")
|
||||||
|
i18nSuffix(targetType) {
|
||||||
|
switch (targetType) {
|
||||||
|
case "Post":
|
||||||
|
return "_post_modal";
|
||||||
|
case "Topic":
|
||||||
|
return "_modal";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
assignUser(name) {
|
assignUser(name) {
|
||||||
if (this.isBulkAction) {
|
if (this.isBulkAction) {
|
||||||
|
@ -73,11 +84,11 @@ export default Controller.extend({
|
||||||
let path = "/assign/assign";
|
let path = "/assign/assign";
|
||||||
|
|
||||||
if (isEmpty(this.get("model.username"))) {
|
if (isEmpty(this.get("model.username"))) {
|
||||||
this.model.topic.set("assigned_to_user", null);
|
this.model.target.set("assigned_to_user", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEmpty(this.get("model.group_name"))) {
|
if (isEmpty(this.get("model.group_name"))) {
|
||||||
this.model.topic.set("assigned_to_group", null);
|
this.model.target.set("assigned_to_group", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -94,8 +105,8 @@ export default Controller.extend({
|
||||||
data: {
|
data: {
|
||||||
username: this.get("model.username"),
|
username: this.get("model.username"),
|
||||||
group_name: this.get("model.group_name"),
|
group_name: this.get("model.group_name"),
|
||||||
target_id: this.get("model.topic.id"),
|
target_id: this.get("model.target.id"),
|
||||||
target_type: "Topic",
|
target_type: this.get("model.targetType"),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
|
@ -48,9 +48,9 @@ export default UserTopicsList.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
unassign(topic) {
|
unassign(targetId, targetType = "Topic") {
|
||||||
this.taskActions
|
this.taskActions
|
||||||
.unassign(topic.get("id"))
|
.unassign(targetId, targetType)
|
||||||
.then(() => this.send("changeAssigned"));
|
.then(() => this.send("changeAssigned"));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -58,9 +58,9 @@ export default UserTopicsList.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
unassign(topic) {
|
unassign(targetId, targetType = "Topic") {
|
||||||
this.taskActions
|
this.taskActions
|
||||||
.unassign(topic.get("id"))
|
.unassign(targetId, targetType)
|
||||||
.then(() => this.send("changeAssigned"));
|
.then(() => this.send("changeAssigned"));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ function registerTopicFooterButtons(api) {
|
||||||
if (topic.assigned_to_user || topic.assigned_to_group) {
|
if (topic.assigned_to_user || topic.assigned_to_group) {
|
||||||
this.set("topic.assigned_to_user", null);
|
this.set("topic.assigned_to_user", null);
|
||||||
this.set("topic.assigned_to_group", null);
|
this.set("topic.assigned_to_group", null);
|
||||||
taskActions.unassign(topic.id).then(() => {
|
taskActions.unassign(topic.id, "Topic").then(() => {
|
||||||
this.appEvents.trigger("post-stream:refresh", {
|
this.appEvents.trigger("post-stream:refresh", {
|
||||||
id: topic.postStream.firstPostId,
|
id: topic.postStream.firstPostId,
|
||||||
});
|
});
|
||||||
|
@ -151,6 +151,38 @@ function initialize(api) {
|
||||||
},
|
},
|
||||||
before: "top",
|
before: "top",
|
||||||
});
|
});
|
||||||
|
if (api.getCurrentUser() && api.getCurrentUser().can_assign) {
|
||||||
|
api.addPostMenuButton("assign", (post) => {
|
||||||
|
if (post.firstPost) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (post.assigned_to_user || post.assigned_to_group) {
|
||||||
|
return {
|
||||||
|
action: "unassignPost",
|
||||||
|
icon: "user-times",
|
||||||
|
className: "unassign-post",
|
||||||
|
title: "discourse_assign.unassign_post.title",
|
||||||
|
position: "second-last-hidden",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
action: "assignPost",
|
||||||
|
icon: "user-plus",
|
||||||
|
className: "assign-post",
|
||||||
|
title: "discourse_assign.assign_post.title",
|
||||||
|
position: "second-last-hidden",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
api.attachWidgetAction("post", "assignPost", function () {
|
||||||
|
const taskActions = getOwner(this).lookup("service:task-actions");
|
||||||
|
taskActions.assign(this.model, "Post");
|
||||||
|
});
|
||||||
|
api.attachWidgetAction("post", "unassignPost", function () {
|
||||||
|
const taskActions = getOwner(this).lookup("service:task-actions");
|
||||||
|
taskActions.unassign(this.model.id, "Post");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
api.addAdvancedSearchOptions(
|
api.addAdvancedSearchOptions(
|
||||||
|
@ -170,52 +202,55 @@ function initialize(api) {
|
||||||
: {}
|
: {}
|
||||||
);
|
);
|
||||||
|
|
||||||
api.modifyClass("model:topic", {
|
function assignedToUserPath(assignedToUser) {
|
||||||
pluginId: PLUGIN_ID,
|
return getURL(
|
||||||
|
siteSettings.assigns_user_url_path.replace(
|
||||||
|
"{username}",
|
||||||
|
assignedToUser.username
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@discourseComputed("assigned_to_user")
|
function assignedToGroupPath(assignedToGroup) {
|
||||||
assignedToUserPath(assignedToUser) {
|
return getURL(`/g/${assignedToGroup.name}/assigned/everyone`);
|
||||||
return getURL(
|
}
|
||||||
siteSettings.assigns_user_url_path.replace(
|
|
||||||
"{username}",
|
|
||||||
assignedToUser.username
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
@discourseComputed("assigned_to_group")
|
|
||||||
assignedToGroupPath(assignedToGroup) {
|
|
||||||
return getURL(`/g/${assignedToGroup.name}/assigned/everyone`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
api.modifyClass("model:bookmark", {
|
api.modifyClass("model:bookmark", {
|
||||||
pluginId: PLUGIN_ID,
|
pluginId: PLUGIN_ID,
|
||||||
|
|
||||||
@discourseComputed("assigned_to_user")
|
@discourseComputed("assigned_to_user")
|
||||||
assignedToUserPath(assignedToUser) {
|
assignedToUserPath(assignedToUser) {
|
||||||
return getURL(
|
return assignedToUserPath(assignedToUser);
|
||||||
this.siteSettings.assigns_user_url_path.replace(
|
|
||||||
"{username}",
|
|
||||||
assignedToUser.username
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
@discourseComputed("assigned_to_group")
|
@discourseComputed("assigned_to_group")
|
||||||
assignedToGroupPath(assignedToGroup) {
|
assignedToGroupPath(assignedToGroup) {
|
||||||
return getURL(`/g/${assignedToGroup.name}/assigned/everyone`);
|
return assignedToGroupPath(assignedToGroup);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
api.addPostSmallActionIcon("assigned", "user-plus");
|
api.addPostSmallActionIcon("assigned", "user-plus");
|
||||||
|
api.addPostSmallActionIcon("assigned_to_post", "user-plus");
|
||||||
api.addPostSmallActionIcon("assigned_group", "group-plus");
|
api.addPostSmallActionIcon("assigned_group", "group-plus");
|
||||||
|
api.addPostSmallActionIcon("assigned_group_to_post", "group-plus");
|
||||||
api.addPostSmallActionIcon("unassigned", "user-times");
|
api.addPostSmallActionIcon("unassigned", "user-times");
|
||||||
api.addPostSmallActionIcon("unassigned_group", "group-times");
|
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.addPostTransformCallback((transformed) => {
|
api.addPostTransformCallback((transformed) => {
|
||||||
if (
|
if (
|
||||||
["assigned", "unassigned", "assigned_group", "unassigned_group"].includes(
|
[
|
||||||
transformed.actionCode
|
"assigned",
|
||||||
)
|
"unassigned",
|
||||||
|
"assigned_group",
|
||||||
|
"unassigned_group",
|
||||||
|
"assigned_to_post",
|
||||||
|
"assigned_group_to_post",
|
||||||
|
"unassigned_from_post",
|
||||||
|
"unassigned_group_from_post",
|
||||||
|
].includes(transformed.actionCode)
|
||||||
) {
|
) {
|
||||||
transformed.isSmallAction = true;
|
transformed.isSmallAction = true;
|
||||||
transformed.canEdit = false;
|
transformed.canEdit = false;
|
||||||
|
@ -225,22 +260,45 @@ function initialize(api) {
|
||||||
api.addDiscoveryQueryParam("assigned", { replace: true, refreshModel: true });
|
api.addDiscoveryQueryParam("assigned", { replace: true, refreshModel: true });
|
||||||
|
|
||||||
api.addTagsHtmlCallback((topic, params = {}) => {
|
api.addTagsHtmlCallback((topic, params = {}) => {
|
||||||
const assignedToUser = topic.get("assigned_to_user.username");
|
const [
|
||||||
const assignedToGroup = topic.get("assigned_to_group.name");
|
assignedToUser,
|
||||||
if (assignedToUser || assignedToGroup) {
|
assignedToGroup,
|
||||||
const assignedPath = assignedToUser
|
assignedToIndirectly,
|
||||||
? topic.assignedToUserPath
|
] = Object.values(
|
||||||
: topic.assignedToGroupPath;
|
topic.getProperties(
|
||||||
const tagName = params.tagName || "a";
|
"assigned_to_user",
|
||||||
const icon = assignedToUser
|
"assigned_to_group",
|
||||||
? iconHTML("user-plus")
|
"indirectly_assigned_to"
|
||||||
: iconHTML("group-plus");
|
)
|
||||||
const href =
|
);
|
||||||
tagName === "a" ? `href="${assignedPath}" data-auto-route="true"` : "";
|
|
||||||
return `<${tagName} class="assigned-to discourse-tag simple" ${href}>
|
const assignedTo = []
|
||||||
|
.concat(
|
||||||
|
assignedToUser,
|
||||||
|
assignedToGroup,
|
||||||
|
assignedToIndirectly ? Object.values(assignedToIndirectly) : []
|
||||||
|
)
|
||||||
|
.filter((element) => element)
|
||||||
|
.flat()
|
||||||
|
.uniqBy((assignee) => assignee.assign_path);
|
||||||
|
|
||||||
|
if (assignedTo) {
|
||||||
|
return assignedTo
|
||||||
|
.map((assignee) => {
|
||||||
|
const assignedPath = getURL(assignee.assign_path);
|
||||||
|
const icon = iconHTML(assignee.assign_icon);
|
||||||
|
const name = assignee.username || assignee.name;
|
||||||
|
const tagName = params.tagName || "a";
|
||||||
|
const href =
|
||||||
|
tagName === "a"
|
||||||
|
? `href="${assignedPath}" data-auto-route="true"`
|
||||||
|
: "";
|
||||||
|
return `<${tagName} class="assigned-to discourse-tag simple" ${href}>
|
||||||
${icon}
|
${icon}
|
||||||
<span>${assignedToUser || assignedToGroup}</span>
|
<span>${name}</span>
|
||||||
</${tagName}>`;
|
</${tagName}>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -299,19 +357,31 @@ function initialize(api) {
|
||||||
const topicId = topic.id;
|
const topicId = topic.id;
|
||||||
|
|
||||||
if (data.topic_id === topicId) {
|
if (data.topic_id === topicId) {
|
||||||
|
let post;
|
||||||
|
if (data.post_id) {
|
||||||
|
post = topic.postStream.posts.find((p) => p.id === data.post_id);
|
||||||
|
}
|
||||||
|
const target = post || topic;
|
||||||
if (data.assigned_type === "User") {
|
if (data.assigned_type === "User") {
|
||||||
topic.set(
|
target.set(
|
||||||
"assigned_to_user_id",
|
"assigned_to_user_id",
|
||||||
data.type === "assigned" ? data.assigned_to.id : null
|
data.type === "assigned" ? data.assigned_to.id : null
|
||||||
);
|
);
|
||||||
topic.set("assigned_to_user", data.assigned_to);
|
target.set("assigned_to_user", data.assigned_to);
|
||||||
}
|
}
|
||||||
if (data.assigned_type === "Group") {
|
if (data.assigned_type === "Group") {
|
||||||
topic.set(
|
target.set(
|
||||||
"assigned_to_group_id",
|
"assigned_to_group_id",
|
||||||
data.type === "assigned" ? data.assigned_to.id : null
|
data.type === "assigned" ? data.assigned_to.id : null
|
||||||
);
|
);
|
||||||
topic.set("assigned_to_group", data.assigned_to);
|
target.set("assigned_to_group", data.assigned_to);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.post_id) {
|
||||||
|
if (data.type === "unassigned") {
|
||||||
|
delete topic.indirectly_assigned_to[data.post_id];
|
||||||
|
}
|
||||||
|
this.appEvents.trigger("post-stream:refresh", { id: data.post_id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.appEvents.trigger("header:update-topic", topic);
|
this.appEvents.trigger("header:update-topic", topic);
|
||||||
|
@ -330,20 +400,32 @@ function initialize(api) {
|
||||||
});
|
});
|
||||||
|
|
||||||
api.decorateWidget("post-contents:after-cooked", (dec) => {
|
api.decorateWidget("post-contents:after-cooked", (dec) => {
|
||||||
if (dec.attrs.post_number === 1) {
|
const postModel = dec.getModel();
|
||||||
const postModel = dec.getModel();
|
if (postModel) {
|
||||||
if (postModel) {
|
let assignedToUser, assignedToGroup, postAssignment, href;
|
||||||
const assignedToUser = get(postModel, "topic.assigned_to_user");
|
if (dec.attrs.post_number === 1) {
|
||||||
const assignedToGroup = get(postModel, "topic.assigned_to_group");
|
assignedToUser = get(postModel, "topic.assigned_to_user");
|
||||||
if (assignedToUser || assignedToGroup) {
|
assignedToGroup = get(postModel, "topic.assigned_to_group");
|
||||||
return dec.widget.attach("assigned-to", {
|
} else {
|
||||||
assignedToUser,
|
postAssignment = postModel.topic.indirectly_assigned_to?.[postModel.id];
|
||||||
assignedToGroup,
|
if (postAssignment?.username) {
|
||||||
href: assignedToUser
|
assignedToUser = postAssignment;
|
||||||
? get(postModel, "topic.assignedToUserPath")
|
|
||||||
: get(postModel, "topic.assignedToGroupPath"),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (postAssignment?.name) {
|
||||||
|
assignedToGroup = postAssignment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (assignedToUser || assignedToGroup) {
|
||||||
|
href = assignedToUser
|
||||||
|
? assignedToUserPath(assignedToUser)
|
||||||
|
: assignedToGroupPath(assignedToGroup);
|
||||||
|
}
|
||||||
|
if (href) {
|
||||||
|
return dec.widget.attach("assigned-to", {
|
||||||
|
assignedToUser,
|
||||||
|
assignedToGroup,
|
||||||
|
href,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -441,6 +523,10 @@ export default {
|
||||||
withPluginApi("0.12.2", (api) => {
|
withPluginApi("0.12.2", (api) => {
|
||||||
api.addGroupPostSmallActionCode("assigned_group");
|
api.addGroupPostSmallActionCode("assigned_group");
|
||||||
api.addGroupPostSmallActionCode("unassigned_group");
|
api.addGroupPostSmallActionCode("unassigned_group");
|
||||||
|
api.addGroupPostSmallActionCode("assigned_to_post");
|
||||||
|
api.addGroupPostSmallActionCode("assigned_group_to_post");
|
||||||
|
api.addGroupPostSmallActionCode("unassigned_from_post");
|
||||||
|
api.addGroupPostSmallActionCode("unassigned_group_from_post");
|
||||||
});
|
});
|
||||||
withPluginApi("0.12.3", (api) => {
|
withPluginApi("0.12.3", (api) => {
|
||||||
api.addUserSearchOption("assignableGroups");
|
api.addUserSearchOption("assignableGroups");
|
||||||
|
|
|
@ -10,33 +10,55 @@ export default DropdownSelectBoxComponent.extend({
|
||||||
showFullTitle: true,
|
showFullTitle: true,
|
||||||
|
|
||||||
computeContent() {
|
computeContent() {
|
||||||
return [
|
let options = [];
|
||||||
{
|
if (this.assignee) {
|
||||||
id: "unassign",
|
options = options.concat([
|
||||||
icon: this.group ? "group-times" : "user-times",
|
{
|
||||||
name: I18n.t("discourse_assign.unassign.title"),
|
id: "unassign",
|
||||||
description: I18n.t("discourse_assign.unassign.help", {
|
icon: this.group ? "group-times" : "user-times",
|
||||||
username: this.assignee,
|
name: I18n.t("discourse_assign.unassign.title"),
|
||||||
}),
|
description: I18n.t("discourse_assign.unassign.help", {
|
||||||
},
|
username: this.assignee,
|
||||||
{
|
}),
|
||||||
id: "reassign",
|
},
|
||||||
icon: "users",
|
{
|
||||||
name: I18n.t("discourse_assign.reassign.title"),
|
id: "reassign",
|
||||||
description: I18n.t("discourse_assign.reassign.help"),
|
icon: "users",
|
||||||
},
|
name: I18n.t("discourse_assign.reassign.title"),
|
||||||
];
|
description: I18n.t("discourse_assign.reassign.help"),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.topic.indirectly_assigned_to) {
|
||||||
|
Object.entries(this.topic.indirectly_assigned_to).forEach((entry) => {
|
||||||
|
const [postId, assignee] = entry;
|
||||||
|
options = options.concat({
|
||||||
|
id: `unassign_post_${postId}`,
|
||||||
|
icon: assignee.username ? "user-times" : "group-times",
|
||||||
|
name: I18n.t("discourse_assign.unassign_post.title"),
|
||||||
|
description: I18n.t("discourse_assign.unassign_post.help", {
|
||||||
|
username: assignee.username || assignee.name,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return options;
|
||||||
},
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
onSelect(id) {
|
onSelect(id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case "unassign":
|
case "unassign":
|
||||||
this.unassign(this.topic, this.assignee);
|
this.unassign(this.topic.id);
|
||||||
break;
|
break;
|
||||||
case "reassign":
|
case "reassign":
|
||||||
this.reassign(this.topic, this.assignee);
|
this.reassign(this.topic, this.assignee);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
const postId = id.match(/unassign_post_(\d+)/)?.[1];
|
||||||
|
if (postId) {
|
||||||
|
this.unassign(postId, "Post");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,22 +3,23 @@ import { ajax } from "discourse/lib/ajax";
|
||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
|
||||||
export default Service.extend({
|
export default Service.extend({
|
||||||
unassign(topicId) {
|
unassign(targetId, targetType = "Topic") {
|
||||||
return ajax("/assign/unassign", {
|
return ajax("/assign/unassign", {
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
data: {
|
data: {
|
||||||
target_id: topicId,
|
target_id: targetId,
|
||||||
target_type: "Topic",
|
target_type: targetType,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
assign(topic) {
|
assign(target, targetType = "Topic") {
|
||||||
return showModal("assign-user", {
|
return showModal("assign-user", {
|
||||||
model: {
|
model: {
|
||||||
username: topic.get("assigned_to_user.username"),
|
username: target.get("assigned_to_user.username"),
|
||||||
group_name: topic.get("assigned_to_group.name"),
|
group_name: target.get("assigned_to_group.name"),
|
||||||
topic,
|
target,
|
||||||
|
targetType,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
unassign=unassign
|
unassign=unassign
|
||||||
reassign=reassign
|
reassign=reassign
|
||||||
}}
|
}}
|
||||||
{{else}}
|
{{else if topic.assigned_to_group}}
|
||||||
{{assign-actions-dropdown
|
{{assign-actions-dropdown
|
||||||
topic=topic
|
topic=topic
|
||||||
assignee=topic.assigned_to_group.name
|
assignee=topic.assigned_to_group.name
|
||||||
|
@ -62,5 +62,10 @@
|
||||||
unassign=unassign
|
unassign=unassign
|
||||||
reassign=reassign
|
reassign=reassign
|
||||||
}}
|
}}
|
||||||
|
{{else}}
|
||||||
|
{{assign-actions-dropdown
|
||||||
|
topic=topic
|
||||||
|
unassign=unassign
|
||||||
|
}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -28,13 +28,19 @@
|
||||||
unassign=unassign
|
unassign=unassign
|
||||||
reassign=reassign
|
reassign=reassign
|
||||||
}}
|
}}
|
||||||
{{else}}
|
{{else if topic.assigned_to_group}}
|
||||||
{{assign-actions-dropdown
|
{{assign-actions-dropdown
|
||||||
topic=topic
|
topic=topic
|
||||||
assignee=topic.assigned_to_group.name
|
assignee=topic.assigned_to_group.name
|
||||||
|
group=true
|
||||||
unassign=unassign
|
unassign=unassign
|
||||||
reassign=reassign
|
reassign=reassign
|
||||||
}}
|
}}
|
||||||
|
{{else}}
|
||||||
|
{{assign-actions-dropdown
|
||||||
|
topic=topic
|
||||||
|
unassign=unassign
|
||||||
|
}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{#d-modal-body title="discourse_assign.assign_modal.title" class="assign"}}
|
{{#d-modal-body title=(concat "discourse_assign.assign" i18nSuffix ".title") class="assign"}}
|
||||||
<div>
|
<div>
|
||||||
{{i18n "discourse_assign.assign_modal.description"}}
|
<p>{{i18n (concat "discourse_assign.assign" i18nSuffix ".description")}}</p>
|
||||||
{{email-group-user-chooser
|
{{email-group-user-chooser
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
value=assigneeName
|
value=assigneeName
|
||||||
|
|
|
@ -69,6 +69,10 @@
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assign-user-modal p {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.assign-suggestions {
|
.assign-suggestions {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
img {
|
img {
|
||||||
|
|
|
@ -7,8 +7,12 @@ en:
|
||||||
action_codes:
|
action_codes:
|
||||||
assigned: "assigned %{who} %{when}"
|
assigned: "assigned %{who} %{when}"
|
||||||
assigned_group: "assigned %{who} %{when}"
|
assigned_group: "assigned %{who} %{when}"
|
||||||
|
assigned_to_post: "assigned %{who} to post %{when}"
|
||||||
|
assigned_group_to_post: "assigned %{who} to post %{when}"
|
||||||
unassigned: "unassigned %{who} %{when}"
|
unassigned: "unassigned %{who} %{when}"
|
||||||
unassigned_group: "unassigned %{who} %{when}"
|
unassigned_group: "unassigned %{who} %{when}"
|
||||||
|
unassigned_from_post: "unassigned %{who} from post %{when}"
|
||||||
|
unassigned_group_from_post: "unassigned %{who} from post %{when}"
|
||||||
discourse_assign:
|
discourse_assign:
|
||||||
add_unassigned_filter: "Add 'unassigned' filter to category"
|
add_unassigned_filter: "Add 'unassigned' filter to category"
|
||||||
cant_act: "You cannot act on flags that have been assigned to other users"
|
cant_act: "You cannot act on flags that have been assigned to other users"
|
||||||
|
@ -26,6 +30,11 @@ en:
|
||||||
assign:
|
assign:
|
||||||
title: "Assign"
|
title: "Assign"
|
||||||
help: "Assign Topic to User"
|
help: "Assign Topic to User"
|
||||||
|
assign_post:
|
||||||
|
title: "Assign Post"
|
||||||
|
unassign_post:
|
||||||
|
title: "Unassign from Post"
|
||||||
|
help: "Unassign %{username} from Post"
|
||||||
reassign:
|
reassign:
|
||||||
title: "Re-assign"
|
title: "Re-assign"
|
||||||
help: "Re-assign Topic to a different user"
|
help: "Re-assign Topic to a different user"
|
||||||
|
@ -33,6 +42,9 @@ en:
|
||||||
title: "Assign Topic"
|
title: "Assign Topic"
|
||||||
description: "Enter the name of the user you'd like to assign this topic"
|
description: "Enter the name of the user you'd like to assign this topic"
|
||||||
assign: "Assign"
|
assign: "Assign"
|
||||||
|
assign_post_modal:
|
||||||
|
title: "Assign Post"
|
||||||
|
description: "Enter the name of the user you'd like to assign this post"
|
||||||
claim:
|
claim:
|
||||||
title: "claim"
|
title: "claim"
|
||||||
help: "Assign topic to yourself"
|
help: "Assign topic to yourself"
|
||||||
|
|
|
@ -20,6 +20,7 @@ en:
|
||||||
already_claimed: "That topic has already been claimed."
|
already_claimed: "That topic has already been claimed."
|
||||||
already_assigned: "Topic is already assigned to @%{username}"
|
already_assigned: "Topic is already assigned to @%{username}"
|
||||||
too_many_assigns: "@%{username} has already reached the maximum number of assigned topics (%{max})."
|
too_many_assigns: "@%{username} has already reached the maximum number of assigned topics (%{max})."
|
||||||
|
too_many_assigns_for_topic: "Limit of %{limit} assignments per topic has been reached."
|
||||||
forbidden_assign_to: "@%{username} can't be assigned since they don't belong to assigned allowed groups."
|
forbidden_assign_to: "@%{username} can't be assigned since they don't belong to assigned allowed groups."
|
||||||
forbidden_assignee_not_pm_participant: "@%{username} can't be assigned because they don't have access to this PM. You can grant @%{username} access by inviting them to this PM."
|
forbidden_assignee_not_pm_participant: "@%{username} can't be assigned because they don't have access to this PM. You can grant @%{username} access by inviting them to this PM."
|
||||||
forbidden_assignee_cant_see_topic: "@%{username} can't be assigned because they don't have access to this topic."
|
forbidden_assignee_cant_see_topic: "@%{username} can't be assigned because they don't have access to this topic."
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RemoveTopicIdIndex < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
remove_index :assignments, :topic_id
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,14 +4,15 @@ module Jobs
|
||||||
class AssignNotification < ::Jobs::Base
|
class AssignNotification < ::Jobs::Base
|
||||||
def execute(args)
|
def execute(args)
|
||||||
raise Discourse::InvalidParameters.new(:topic_id) if args[:topic_id].nil?
|
raise Discourse::InvalidParameters.new(:topic_id) if args[:topic_id].nil?
|
||||||
|
raise Discourse::InvalidParameters.new(:post_id) if args[:post_id].nil?
|
||||||
raise Discourse::InvalidParameters.new(:assigned_to_id) if args[:assigned_to_id].nil?
|
raise Discourse::InvalidParameters.new(:assigned_to_id) if args[:assigned_to_id].nil?
|
||||||
raise Discourse::InvalidParameters.new(:assigned_to_type) if args[:assigned_to_type].nil?
|
raise Discourse::InvalidParameters.new(:assigned_to_type) if args[:assigned_to_type].nil?
|
||||||
raise Discourse::InvalidParameters.new(:assigned_by_id) if args[:assigned_by_id].nil?
|
raise Discourse::InvalidParameters.new(:assigned_by_id) if args[:assigned_by_id].nil?
|
||||||
raise Discourse::InvalidParameters.new(:silent) if args[:silent].nil?
|
raise Discourse::InvalidParameters.new(:silent) if args[:silent].nil?
|
||||||
|
|
||||||
topic = Topic.find(args[:topic_id])
|
topic = Topic.find(args[:topic_id])
|
||||||
|
post = Post.find(args[:post_id])
|
||||||
assigned_by = User.find(args[:assigned_by_id])
|
assigned_by = User.find(args[:assigned_by_id])
|
||||||
first_post = topic.posts.find_by(post_number: 1)
|
|
||||||
assigned_to = args[:assigned_to_type] == "User" ? User.find(args[:assigned_to_id]) : Group.find(args[:assigned_to_id])
|
assigned_to = args[:assigned_to_type] == "User" ? User.find(args[:assigned_to_id]) : Group.find(args[:assigned_to_id])
|
||||||
assigned_to_users = args[:assigned_to_type] == "User" ? [assigned_to] : assigned_to.users
|
assigned_to_users = args[:assigned_to_type] == "User" ? [assigned_to] : assigned_to.users
|
||||||
|
|
||||||
|
@ -20,9 +21,9 @@ module Jobs
|
||||||
|
|
||||||
next if assigned_by == user
|
next if assigned_by == user
|
||||||
|
|
||||||
PostAlerter.new(first_post).create_notification_alert(
|
PostAlerter.new(post).create_notification_alert(
|
||||||
user: user,
|
user: user,
|
||||||
post: first_post,
|
post: post,
|
||||||
username: assigned_by.username,
|
username: assigned_by.username,
|
||||||
notification_type: Notification.types[:custom],
|
notification_type: Notification.types[:custom],
|
||||||
excerpt: I18n.t(
|
excerpt: I18n.t(
|
||||||
|
@ -37,7 +38,7 @@ module Jobs
|
||||||
notification_type: Notification.types[:custom],
|
notification_type: Notification.types[:custom],
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
topic_id: topic.id,
|
topic_id: topic.id,
|
||||||
post_number: 1,
|
post_number: post.post_number,
|
||||||
high_priority: true,
|
high_priority: true,
|
||||||
data: {
|
data: {
|
||||||
message: args[:assigned_to_type] == "User" ? 'discourse_assign.assign_notification' : 'discourse_assign.assign_group_notification',
|
message: args[:assigned_to_type] == "User" ? 'discourse_assign.assign_notification' : 'discourse_assign.assign_group_notification',
|
||||||
|
|
|
@ -17,7 +17,6 @@ module Jobs
|
||||||
notification_type: Notification.types[:custom],
|
notification_type: Notification.types[:custom],
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
topic_id: topic.id,
|
topic_id: topic.id,
|
||||||
post_number: 1
|
|
||||||
).where("data like '%discourse_assign.assign_notification%' OR data like '%discourse_assign.assign_group_notification%'").destroy_all
|
).where("data like '%discourse_assign.assign_notification%' OR data like '%discourse_assign.assign_group_notification%'").destroy_all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,8 @@ require 'email/sender'
|
||||||
require 'nokogiri'
|
require 'nokogiri'
|
||||||
|
|
||||||
class ::Assigner
|
class ::Assigner
|
||||||
|
ASSIGNMENTS_PER_TOPIC_LIMIT = 5
|
||||||
|
|
||||||
def self.backfill_auto_assign
|
def self.backfill_auto_assign
|
||||||
staff_mention = User
|
staff_mention = User
|
||||||
.assign_allowed
|
.assign_allowed
|
||||||
|
@ -107,7 +109,6 @@ class ::Assigner
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.publish_topic_tracking_state(topic, user_id)
|
def self.publish_topic_tracking_state(topic, user_id)
|
||||||
# TODO decide later if we want general or separate method to publish about post tracking
|
|
||||||
if topic.private_message?
|
if topic.private_message?
|
||||||
MessageBus.publish(
|
MessageBus.publish(
|
||||||
"/private-messages/assigned",
|
"/private-messages/assigned",
|
||||||
|
@ -190,6 +191,8 @@ class ::Assigner
|
||||||
assign_to.is_a?(User) ? :forbidden_assign_to : :forbidden_group_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
|
||||||
assign_to.is_a?(User) ? :already_assigned : :group_already_assigned
|
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)
|
when !can_assign_to?(assign_to)
|
||||||
:too_many_assigns
|
:too_many_assigns
|
||||||
end
|
end
|
||||||
|
@ -205,20 +208,20 @@ class ::Assigner
|
||||||
|
|
||||||
serializer = assignment.assigned_to_user? ? BasicUserSerializer : BasicGroupSerializer
|
serializer = assignment.assigned_to_user? ? BasicUserSerializer : BasicGroupSerializer
|
||||||
|
|
||||||
#TODO assign notification for post
|
|
||||||
Jobs.enqueue(:assign_notification,
|
Jobs.enqueue(:assign_notification,
|
||||||
topic_id: topic.id,
|
topic_id: topic.id,
|
||||||
|
post_id: topic_target? ? first_post.id : @target.id,
|
||||||
assigned_to_id: assign_to.id,
|
assigned_to_id: assign_to.id,
|
||||||
assigned_to_type: type,
|
assigned_to_type: type,
|
||||||
assigned_by_id: @assigned_by.id,
|
assigned_by_id: @assigned_by.id,
|
||||||
silent: silent)
|
silent: silent)
|
||||||
|
|
||||||
#TODO message bus for post assignment
|
|
||||||
MessageBus.publish(
|
MessageBus.publish(
|
||||||
"/staff/topic-assignment",
|
"/staff/topic-assignment",
|
||||||
{
|
{
|
||||||
type: "assigned",
|
type: "assigned",
|
||||||
topic_id: topic.id,
|
topic_id: topic.id,
|
||||||
|
post_id: post_target? && @target.id,
|
||||||
assigned_type: type,
|
assigned_type: type,
|
||||||
assigned_to: serializer.new(assign_to, scope: Guardian.new, root: false).as_json
|
assigned_to: serializer.new(assign_to, scope: Guardian.new, root: false).as_json
|
||||||
},
|
},
|
||||||
|
@ -247,16 +250,14 @@ class ::Assigner
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if !silent
|
if !silent
|
||||||
#TODO moderator post when assigned to post
|
|
||||||
topic.add_moderator_post(
|
topic.add_moderator_post(
|
||||||
@assigned_by,
|
@assigned_by,
|
||||||
nil,
|
nil,
|
||||||
bump: false,
|
bump: false,
|
||||||
post_type: SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper],
|
post_type: SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper],
|
||||||
action_code: assignment.assigned_to_user? ? "assigned" : "assigned_group",
|
action_code: moderator_post_assign_action_code(assignment),
|
||||||
custom_fields: { "action_code_who" => assign_to.is_a?(User) ? assign_to.username : assign_to.name }
|
custom_fields: { "action_code_who" => assign_to.is_a?(User) ? assign_to.username : assign_to.name }
|
||||||
)
|
)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create a webhook event
|
# Create a webhook event
|
||||||
|
@ -296,7 +297,6 @@ class ::Assigner
|
||||||
|
|
||||||
first_post.publish_change_to_clients!(:revised, reload_topic: true)
|
first_post.publish_change_to_clients!(:revised, reload_topic: true)
|
||||||
|
|
||||||
#TODO unassign notification for post
|
|
||||||
Jobs.enqueue(:unassign_notification,
|
Jobs.enqueue(:unassign_notification,
|
||||||
topic_id: topic.id,
|
topic_id: topic.id,
|
||||||
assigned_to_id: assignment.assigned_to.id,
|
assigned_to_id: assignment.assigned_to.id,
|
||||||
|
@ -321,7 +321,6 @@ class ::Assigner
|
||||||
|
|
||||||
assigned_to = assignment.assigned_to
|
assigned_to = assignment.assigned_to
|
||||||
|
|
||||||
# TODO unassign post for post assignment
|
|
||||||
if SiteSetting.unassign_creates_tracking_post && !silent
|
if SiteSetting.unassign_creates_tracking_post && !silent
|
||||||
post_type = SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper]
|
post_type = SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper]
|
||||||
topic.add_moderator_post(
|
topic.add_moderator_post(
|
||||||
|
@ -329,7 +328,7 @@ class ::Assigner
|
||||||
bump: false,
|
bump: false,
|
||||||
post_type: post_type,
|
post_type: post_type,
|
||||||
custom_fields: { "action_code_who" => assigned_to.is_a?(User) ? assigned_to.username : assigned_to.name },
|
custom_fields: { "action_code_who" => assigned_to.is_a?(User) ? assigned_to.username : assigned_to.name },
|
||||||
action_code: assignment.assigned_to_user? ? "unassigned" : "unassigned_group",
|
action_code: moderator_post_unassign_action_code(assignment),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -362,9 +361,35 @@ class ::Assigner
|
||||||
{
|
{
|
||||||
type: 'unassigned',
|
type: 'unassigned',
|
||||||
topic_id: topic.id,
|
topic_id: topic.id,
|
||||||
|
post_id: post_target? && @target.id,
|
||||||
|
assigned_type: assignment.assigned_to.is_a?(User) ? "User" : "Group"
|
||||||
},
|
},
|
||||||
user_ids: allowed_user_ids
|
user_ids: allowed_user_ids
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def moderator_post_assign_action_code(assignment)
|
||||||
|
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?
|
||||||
|
end
|
||||||
|
|
||||||
|
def moderator_post_unassign_action_code(assignment)
|
||||||
|
suffix =
|
||||||
|
if assignment.target.is_a?(Post)
|
||||||
|
"_from_post"
|
||||||
|
elsif assignment.target.is_a?(Topic)
|
||||||
|
""
|
||||||
|
end
|
||||||
|
return "unassigned#{suffix}" if assignment.assigned_to_user?
|
||||||
|
return "unassigned_group#{suffix}" if assignment.assigned_to_group?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,12 +4,12 @@ module DiscourseAssign
|
||||||
module Helpers
|
module Helpers
|
||||||
def self.build_assigned_to_user(user, topic)
|
def self.build_assigned_to_user(user, topic)
|
||||||
return if !user
|
return if !user
|
||||||
|
|
||||||
{
|
{
|
||||||
username: user.username,
|
username: user.username,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
avatar_template: user.avatar_template,
|
avatar_template: user.avatar_template,
|
||||||
assigned_at: Assignment.where(target: topic).pluck_first(:created_at)
|
assign_icon: 'user-plus',
|
||||||
|
assign_path: SiteSetting.assigns_user_url_path.gsub("{username}", user.username),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -22,8 +22,19 @@ module DiscourseAssign
|
||||||
flair_color: group.flair_color,
|
flair_color: group.flair_color,
|
||||||
flair_icon: group.flair_icon,
|
flair_icon: group.flair_icon,
|
||||||
flair_upload_id: group.flair_upload_id,
|
flair_upload_id: group.flair_upload_id,
|
||||||
assigned_at: Assignment.where(target: topic).pluck_first(:created_at)
|
assign_icon: 'group-plus',
|
||||||
|
assign_path: "/g/#{group.name}/assigned/everyone",
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.build_indirectly_assigned_to(post_assignments, topic)
|
||||||
|
post_assignments.map do |post_id, assigned_to|
|
||||||
|
if (assigned_to.is_a?(User))
|
||||||
|
[post_id, build_assigned_to_user(assigned_to, topic)]
|
||||||
|
elsif assigned_to.is_a?(Group)
|
||||||
|
[post_id, build_assigned_to_group(assigned_to, topic)]
|
||||||
|
end
|
||||||
|
end.to_h
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
98
plugin.rb
98
plugin.rb
|
@ -52,6 +52,10 @@ after_initialize do
|
||||||
has_one :assignment, as: :target, dependent: :destroy
|
has_one :assignment, as: :target, dependent: :destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class ::Post
|
||||||
|
has_one :assignment, as: :target, dependent: :destroy
|
||||||
|
end
|
||||||
|
|
||||||
class ::Group
|
class ::Group
|
||||||
scope :assignable, ->(user) {
|
scope :assignable, ->(user) {
|
||||||
where("assignable_level in (:levels) OR
|
where("assignable_level in (:levels) OR
|
||||||
|
@ -195,6 +199,10 @@ after_initialize do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
TopicView.on_preload do |topic_view|
|
||||||
|
topic_view.instance_variable_set(:@posts, topic_view.posts.includes(:assignment))
|
||||||
|
end
|
||||||
|
|
||||||
TopicList.on_preload do |topics, topic_list|
|
TopicList.on_preload do |topics, topic_list|
|
||||||
if SiteSetting.assign_enabled?
|
if SiteSetting.assign_enabled?
|
||||||
can_assign = topic_list.current_user && topic_list.current_user.can_assign?
|
can_assign = topic_list.current_user && topic_list.current_user.can_assign?
|
||||||
|
@ -202,7 +210,7 @@ after_initialize do
|
||||||
|
|
||||||
if allowed_access && topics.length > 0
|
if allowed_access && topics.length > 0
|
||||||
assignments = Assignment.strict_loading.where(topic: topics)
|
assignments = Assignment.strict_loading.where(topic: topics)
|
||||||
assignments_map = assignments.index_by(&:topic_id)
|
assignments_map = assignments.group_by(&:topic_id)
|
||||||
|
|
||||||
user_ids = assignments.filter { |assignment| assignment.assigned_to_user? }.map(&:assigned_to_id)
|
user_ids = assignments.filter { |assignment| assignment.assigned_to_user? }.map(&:assigned_to_id)
|
||||||
users_map = User.where(id: user_ids).select(UserLookup.lookup_columns).index_by(&:id)
|
users_map = User.where(id: user_ids).select(UserLookup.lookup_columns).index_by(&:id)
|
||||||
|
@ -211,10 +219,23 @@ after_initialize do
|
||||||
groups_map = Group.where(id: group_ids).index_by(&:id)
|
groups_map = Group.where(id: group_ids).index_by(&:id)
|
||||||
|
|
||||||
topics.each do |topic|
|
topics.each do |topic|
|
||||||
assignment = assignments_map[topic.id]
|
assignments = assignments_map[topic.id]
|
||||||
assigned_to = users_map[assignment.assigned_to_id] if assignment&.assigned_to_user?
|
direct_assignment = assignments&.find { |assignment| assignment.target_type == "Topic" && assignment.target_id == topic.id }
|
||||||
assigned_to = groups_map[assignment.assigned_to_id] if assignment&.assigned_to_group?
|
indirectly_assigned_to = {}
|
||||||
|
assignments&.each do |assignment|
|
||||||
|
next if assignment.target_type == "Topic"
|
||||||
|
next indirectly_assigned_to[assignment.target_id] = users_map[assignment.assigned_to_id] if assignment&.assigned_to_user?
|
||||||
|
next indirectly_assigned_to[assignment.target_id] = groups_map[assignment.assigned_to_id] if assignment&.assigned_to_group?
|
||||||
|
end&.compact&.uniq
|
||||||
|
|
||||||
|
assigned_to =
|
||||||
|
if direct_assignment&.assigned_to_user?
|
||||||
|
users_map[direct_assignment.assigned_to_id]
|
||||||
|
elsif direct_assignment&.assigned_to_group?
|
||||||
|
groups_map[direct_assignment.assigned_to_id]
|
||||||
|
end
|
||||||
topic.preload_assigned_to(assigned_to)
|
topic.preload_assigned_to(assigned_to)
|
||||||
|
topic.preload_indirectly_assigned_to(indirectly_assigned_to)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -389,10 +410,22 @@ after_initialize do
|
||||||
@assigned_to = assignment&.assigned_to
|
@assigned_to = assignment&.assigned_to
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_to_class(:topic, :indirectly_assigned_to) do
|
||||||
|
return @indirectly_assigned_to if defined?(@indirectly_assigned_to)
|
||||||
|
@indirectly_assigned_to = Assignment.where(topic_id: id, target_type: "Post").inject({}) do |acc, assignment|
|
||||||
|
acc[assignment.target_id] = assignment.assigned_to
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
add_to_class(:topic, :preload_assigned_to) do |assigned_to|
|
add_to_class(:topic, :preload_assigned_to) do |assigned_to|
|
||||||
@assigned_to = assigned_to
|
@assigned_to = assigned_to
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_to_class(:topic, :preload_indirectly_assigned_to) do |indirectly_assigned_to|
|
||||||
|
@indirectly_assigned_to = indirectly_assigned_to
|
||||||
|
end
|
||||||
|
|
||||||
# TopicList serializer
|
# TopicList serializer
|
||||||
add_to_serializer(:topic_list, :assigned_messages_count) do
|
add_to_serializer(:topic_list, :assigned_messages_count) do
|
||||||
TopicQuery.new(object.current_user, guardian: scope, limit: false)
|
TopicQuery.new(object.current_user, guardian: scope, limit: false)
|
||||||
|
@ -426,6 +459,14 @@ after_initialize do
|
||||||
(SiteSetting.assigns_public || scope.can_assign?) && object.topic.assigned_to&.is_a?(Group)
|
(SiteSetting.assigns_public || scope.can_assign?) && object.topic.assigned_to&.is_a?(Group)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:topic_view, :indirectly_assigned_to) do
|
||||||
|
DiscourseAssign::Helpers.build_indirectly_assigned_to(object.topic.indirectly_assigned_to, object.topic)
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:topic_view, :include_indirectly_assigned_to?) do
|
||||||
|
(SiteSetting.assigns_public || scope.can_assign?) && object.topic.indirectly_assigned_to.present?
|
||||||
|
end
|
||||||
|
|
||||||
# SuggestedTopic serializer
|
# SuggestedTopic serializer
|
||||||
add_to_serializer(:suggested_topic, :assigned_to_user, false) do
|
add_to_serializer(:suggested_topic, :assigned_to_user, false) do
|
||||||
DiscourseAssign::Helpers.build_assigned_to_user(object.assigned_to, object)
|
DiscourseAssign::Helpers.build_assigned_to_user(object.assigned_to, object)
|
||||||
|
@ -443,7 +484,23 @@ after_initialize do
|
||||||
(SiteSetting.assigns_public || scope.can_assign?) && object.assigned_to&.is_a?(Group)
|
(SiteSetting.assigns_public || scope.can_assign?) && object.assigned_to&.is_a?(Group)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:suggested_topic, :indirectly_assigned_to) do
|
||||||
|
DiscourseAssign::Helpers.build_indirectly_assigned_to(object.indirectly_assigned_to, object)
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:suggested_topic, :include_indirectly_assigned_to?) do
|
||||||
|
(SiteSetting.assigns_public || scope.can_assign?) && object.indirectly_assigned_to.present?
|
||||||
|
end
|
||||||
|
|
||||||
# TopicListItem serializer
|
# TopicListItem serializer
|
||||||
|
add_to_serializer(:topic_list_item, :indirectly_assigned_to) do
|
||||||
|
DiscourseAssign::Helpers.build_indirectly_assigned_to(object.indirectly_assigned_to, object)
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:topic_list_item, :include_indirectly_assigned_to?) do
|
||||||
|
(SiteSetting.assigns_public || scope.can_assign?) && object.indirectly_assigned_to.present?
|
||||||
|
end
|
||||||
|
|
||||||
add_to_serializer(:topic_list_item, :assigned_to_user) do
|
add_to_serializer(:topic_list_item, :assigned_to_user) do
|
||||||
BasicUserSerializer.new(object.assigned_to, scope: scope, root: false).as_json
|
BasicUserSerializer.new(object.assigned_to, scope: scope, root: false).as_json
|
||||||
end
|
end
|
||||||
|
@ -504,6 +561,22 @@ after_initialize do
|
||||||
topic.assigned_to
|
topic.assigned_to
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:basic_user, :assign_icon) do
|
||||||
|
'user-plus'
|
||||||
|
end
|
||||||
|
add_to_serializer(:basic_user, :assign_path) do
|
||||||
|
return if !object.is_a?(User)
|
||||||
|
SiteSetting.assigns_user_url_path.gsub("{username}", object.username)
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:basic_group, :assign_icon) do
|
||||||
|
'group-plus'
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:basic_group, :assign_path) do
|
||||||
|
"/g/#{object.name}/assigned/everyone"
|
||||||
|
end
|
||||||
|
|
||||||
add_to_serializer(:user_bookmark, 'include_assigned_to_user?') do
|
add_to_serializer(:user_bookmark, 'include_assigned_to_user?') do
|
||||||
(SiteSetting.assigns_public || scope.can_assign?) && topic.assigned_to&.is_a?(User)
|
(SiteSetting.assigns_public || scope.can_assign?) && topic.assigned_to&.is_a?(User)
|
||||||
end
|
end
|
||||||
|
@ -516,6 +589,23 @@ after_initialize do
|
||||||
(SiteSetting.assigns_public || scope.can_assign?) && topic.assigned_to&.is_a?(Group)
|
(SiteSetting.assigns_public || scope.can_assign?) && topic.assigned_to&.is_a?(Group)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# PostSerializer
|
||||||
|
add_to_serializer(:post, :assigned_to_user) do
|
||||||
|
object.assignment&.assigned_to
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:post, 'include_assigned_to_user?') do
|
||||||
|
(SiteSetting.assigns_public || scope.can_assign?) && object.assignment&.assigned_to&.is_a?(User)
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:post, :assigned_to_group, false) do
|
||||||
|
object.assignment&.assigned_to
|
||||||
|
end
|
||||||
|
|
||||||
|
add_to_serializer(:post, 'include_assigned_to_group?') do
|
||||||
|
(SiteSetting.assigns_public || scope.can_assign?) && object.assignment&.assigned_to&.is_a?(Group)
|
||||||
|
end
|
||||||
|
|
||||||
# CurrentUser serializer
|
# CurrentUser serializer
|
||||||
add_to_serializer(:current_user, :can_assign) do
|
add_to_serializer(:current_user, :can_assign) do
|
||||||
object.can_assign?
|
object.can_assign?
|
||||||
|
|
|
@ -29,7 +29,7 @@ RSpec.describe Jobs::AssignNotification do
|
||||||
|
|
||||||
it 'sends notification alert' do
|
it 'sends notification alert' do
|
||||||
messages = MessageBus.track_publish("/notification-alert/#{user2.id}") do
|
messages = MessageBus.track_publish("/notification-alert/#{user2.id}") do
|
||||||
described_class.new.execute({ topic_id: topic.id, assigned_to_id: user2.id, assigned_to_type: 'User', assigned_by_id: user1.id, silent: false })
|
described_class.new.execute({ topic_id: topic.id, post_id: post.id, assigned_to_id: user2.id, assigned_to_type: 'User', assigned_by_id: user1.id, silent: false })
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(messages.length).to eq(1)
|
expect(messages.length).to eq(1)
|
||||||
|
@ -41,7 +41,7 @@ RSpec.describe Jobs::AssignNotification do
|
||||||
assign_allowed_group.add(user)
|
assign_allowed_group.add(user)
|
||||||
|
|
||||||
assert_publish_topic_state(pm, user) do
|
assert_publish_topic_state(pm, user) do
|
||||||
described_class.new.execute({ topic_id: pm.id, assigned_to_id: pm.allowed_users.first.id, assigned_to_type: 'User', assigned_by_id: user1.id, silent: false })
|
described_class.new.execute({ topic_id: pm.id, post_id: pm_post.id, assigned_to_id: pm.allowed_users.first.id, assigned_to_type: 'User', assigned_by_id: user1.id, silent: false })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ RSpec.describe Jobs::AssignNotification do
|
||||||
topic_title: topic.title
|
topic_title: topic.title
|
||||||
}.to_json
|
}.to_json
|
||||||
)
|
)
|
||||||
described_class.new.execute({ topic_id: topic.id, assigned_to_id: user2.id, assigned_to_type: 'User', assigned_by_id: user1.id, silent: false })
|
described_class.new.execute({ topic_id: topic.id, post_id: post.id, assigned_to_id: user2.id, assigned_to_type: 'User', assigned_by_id: user1.id, silent: false })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -76,19 +76,19 @@ RSpec.describe Jobs::AssignNotification do
|
||||||
|
|
||||||
it 'sends notification alert to all group members' do
|
it 'sends notification alert to all group members' do
|
||||||
messages = MessageBus.track_publish("/notification-alert/#{user2.id}") do
|
messages = MessageBus.track_publish("/notification-alert/#{user2.id}") do
|
||||||
described_class.new.execute({ topic_id: topic.id, assigned_to_id: group.id, assigned_to_type: 'Group', assigned_by_id: user1.id, silent: false })
|
described_class.new.execute({ topic_id: topic.id, post_id: post.id, assigned_to_id: group.id, assigned_to_type: 'Group', assigned_by_id: user1.id, silent: false })
|
||||||
end
|
end
|
||||||
expect(messages.length).to eq(1)
|
expect(messages.length).to eq(1)
|
||||||
expect(messages.first.data[:excerpt]).to eq("assigned you the topic '#{topic.title}'")
|
expect(messages.first.data[:excerpt]).to eq("assigned you the topic '#{topic.title}'")
|
||||||
|
|
||||||
messages = MessageBus.track_publish("/notification-alert/#{user3.id}") do
|
messages = MessageBus.track_publish("/notification-alert/#{user3.id}") do
|
||||||
described_class.new.execute({ topic_id: topic.id, assigned_to_id: group.id, assigned_to_type: 'Group', assigned_by_id: user1.id, silent: false })
|
described_class.new.execute({ topic_id: topic.id, post_id: post.id, assigned_to_id: group.id, assigned_to_type: 'Group', assigned_by_id: user1.id, silent: false })
|
||||||
end
|
end
|
||||||
expect(messages.length).to eq(1)
|
expect(messages.length).to eq(1)
|
||||||
expect(messages.first.data[:excerpt]).to eq("assigned you the topic '#{topic.title}'")
|
expect(messages.first.data[:excerpt]).to eq("assigned you the topic '#{topic.title}'")
|
||||||
|
|
||||||
messages = MessageBus.track_publish("/notification-alert/#{user4.id}") do
|
messages = MessageBus.track_publish("/notification-alert/#{user4.id}") do
|
||||||
described_class.new.execute({ topic_id: topic.id, assigned_to_id: group.id, assigned_to_type: 'Group', assigned_by_id: user1.id, silent: false })
|
described_class.new.execute({ topic_id: topic.id, post_id: post.id, assigned_to_id: group.id, assigned_to_type: 'Group', assigned_by_id: user1.id, silent: false })
|
||||||
end
|
end
|
||||||
expect(messages.length).to eq(0)
|
expect(messages.length).to eq(0)
|
||||||
end
|
end
|
||||||
|
@ -109,7 +109,7 @@ RSpec.describe Jobs::AssignNotification do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
described_class.new.execute({ topic_id: topic.id, assigned_to_id: group.id, assigned_to_type: 'Group', assigned_by_id: user1.id, silent: false })
|
described_class.new.execute({ topic_id: topic.id, post_id: post.id, assigned_to_id: group.id, assigned_to_type: 'Group', assigned_by_id: user1.id, silent: false })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,10 +27,10 @@ RSpec.describe Jobs::UnassignNotification do
|
||||||
|
|
||||||
context 'User' do
|
context 'User' do
|
||||||
it 'deletes notifications' do
|
it 'deletes notifications' do
|
||||||
Jobs::AssignNotification.new.execute({ topic_id: topic.id, assigned_to_id: user2.id, assigned_to_type: 'User', assigned_by_id: user1.id, silent: false })
|
Jobs::AssignNotification.new.execute({ topic_id: topic.id, post_id: post.id, assigned_to_id: user2.id, assigned_to_type: 'User', assigned_by_id: user1.id, silent: false })
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
described_class.new.execute({ topic_id: topic.id, assigned_to_id: user2.id, assigned_to_type: 'User' })
|
described_class.new.execute({ topic_id: topic.id, post_id: post.id, assigned_to_id: user2.id, assigned_to_type: 'User' })
|
||||||
}.to change { user2.notifications.count }.by(-1)
|
}.to change { user2.notifications.count }.by(-1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ RSpec.describe Jobs::UnassignNotification do
|
||||||
assign_allowed_group.add(user)
|
assign_allowed_group.add(user)
|
||||||
|
|
||||||
assert_publish_topic_state(pm, user) do
|
assert_publish_topic_state(pm, user) do
|
||||||
described_class.new.execute({ topic_id: pm.id, assigned_to_id: pm.allowed_users.first.id, assigned_to_type: 'User' })
|
described_class.new.execute({ topic_id: pm.id, post_id: pm_post.id, assigned_to_id: pm.allowed_users.first.id, assigned_to_type: 'User' })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -55,10 +55,10 @@ RSpec.describe Jobs::UnassignNotification do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'deletes notifications' do
|
it 'deletes notifications' do
|
||||||
Jobs::AssignNotification.new.execute({ topic_id: topic.id, assigned_to_id: group.id, assigned_to_type: 'Group', assigned_by_id: user1.id, silent: false })
|
Jobs::AssignNotification.new.execute({ topic_id: topic.id, post_id: post.id, assigned_to_id: group.id, assigned_to_type: 'Group', assigned_by_id: user1.id, silent: false })
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
described_class.new.execute({ topic_id: topic.id, assigned_to_id: group.id, assigned_to_type: 'Group' })
|
described_class.new.execute({ topic_id: topic.id, post_id: post.id, assigned_to_id: group.id, assigned_to_type: 'Group' })
|
||||||
}.to change { Notification.count }.by(-2)
|
}.to change { Notification.count }.by(-2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,8 +45,9 @@ describe FlaggedTopicSerializer do
|
||||||
expect(json[:flagged_topic][:assigned_to_user]).to match({
|
expect(json[:flagged_topic][:assigned_to_user]).to match({
|
||||||
username: user.username,
|
username: user.username,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
|
assign_icon: "user-plus",
|
||||||
avatar_template: /letter_avatar_proxy.*/,
|
avatar_template: /letter_avatar_proxy.*/,
|
||||||
assigned_at: Assignment.last.created_at,
|
assign_path: "/u/#{user.username}/activity/assigned",
|
||||||
})
|
})
|
||||||
expect(json[:flagged_topic]).to_not have_key(:assigned_to_group)
|
expect(json[:flagged_topic]).to_not have_key(:assigned_to_group)
|
||||||
end
|
end
|
||||||
|
@ -75,7 +76,8 @@ describe FlaggedTopicSerializer do
|
||||||
flair_color: assign_allowed_group.flair_color,
|
flair_color: assign_allowed_group.flair_color,
|
||||||
flair_icon: assign_allowed_group.flair_icon,
|
flair_icon: assign_allowed_group.flair_icon,
|
||||||
flair_upload_id: assign_allowed_group.flair_upload_id,
|
flair_upload_id: assign_allowed_group.flair_upload_id,
|
||||||
assigned_at: Assignment.last.created_at,
|
assign_icon: "group-plus",
|
||||||
|
assign_path: "/g/#{assign_allowed_group.name}/assigned/everyone",
|
||||||
})
|
})
|
||||||
expect(json[:flagged_topic]).to_not have_key(:assigned_to_user)
|
expect(json[:flagged_topic]).to_not have_key(:assigned_to_user)
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,6 +80,22 @@ acceptance("Discourse Assign | Assign desktop", function (needs) {
|
||||||
return helper.response({ success: true });
|
return helper.response({ success: true });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
test("Post contains hidden assign button", async (assert) => {
|
||||||
|
updateCurrentUser({ can_assign: true });
|
||||||
|
await visit("/t/internationalization-localization/280");
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
!exists("#post_2 .extra-buttons .d-icon-user-plus"),
|
||||||
|
"assign to post button is hidden"
|
||||||
|
);
|
||||||
|
await click("#post_2 button.show-more-actions");
|
||||||
|
assert.ok(
|
||||||
|
exists("#post_2 .extra-buttons .d-icon-user-plus"),
|
||||||
|
"assign to post button exists"
|
||||||
|
);
|
||||||
|
await click("#post_2 .extra-buttons .d-icon-user-plus");
|
||||||
|
assert.ok(exists(".assign.modal-body"), "assign modal opens");
|
||||||
|
});
|
||||||
|
|
||||||
test("Footer dropdown contains button", async (assert) => {
|
test("Footer dropdown contains button", async (assert) => {
|
||||||
updateCurrentUser({ can_assign: true });
|
updateCurrentUser({ can_assign: true });
|
||||||
|
|
|
@ -25,7 +25,6 @@ acceptance("Discourse Assign | Assigned topic", function (needs) {
|
||||||
name: "Robin Ward",
|
name: "Robin Ward",
|
||||||
avatar_template:
|
avatar_template:
|
||||||
"/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
|
"/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
|
||||||
assigned_at: "2021-06-13T16:33:14.189Z",
|
|
||||||
};
|
};
|
||||||
return helper.response(topic);
|
return helper.response(topic);
|
||||||
});
|
});
|
||||||
|
@ -34,7 +33,6 @@ acceptance("Discourse Assign | Assigned topic", function (needs) {
|
||||||
let topic = cloneJSON(topicFixtures["/t/28830/1.json"]);
|
let topic = cloneJSON(topicFixtures["/t/28830/1.json"]);
|
||||||
topic["assigned_to_group"] = {
|
topic["assigned_to_group"] = {
|
||||||
name: "Developers",
|
name: "Developers",
|
||||||
assigned_at: "2021-06-13T16:33:14.189Z",
|
|
||||||
};
|
};
|
||||||
return helper.response(topic);
|
return helper.response(topic);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue