FEATURE: Assign Status (#363)
Adds a new plugin setting that when enabled adds a status field for every assignment. This setting defaults to off. The possible status for an assignment are customizable via yet another new setting, and the first one on this list will be the default status for new assignments. The status is not yet show anywhere except the assign modal and the small action posts in topics at the moment. Adding status to the assignment list for users and groups will be handled in the near future. Co-authored-by: Penar Musaraj <pmusaraj@gmail.com>
This commit is contained in:
parent
a29a02abe7
commit
7a2fde72c6
|
@ -56,6 +56,7 @@ module DiscourseAssign
|
|||
username = params.permit(:username)["username"]
|
||||
group_name = params.permit(:group_name)["group_name"]
|
||||
note = params.permit(:note)["note"].presence
|
||||
status = params.permit(:status)["status"].presence
|
||||
|
||||
assign_to =
|
||||
(
|
||||
|
@ -71,7 +72,7 @@ module DiscourseAssign
|
|||
target = target_type.constantize.where(id: target_id).first
|
||||
raise Discourse::NotFound unless target
|
||||
|
||||
assign = Assigner.new(target, current_user).assign(assign_to, note: note)
|
||||
assign = Assigner.new(target, current_user).assign(assign_to, note: note, status: status)
|
||||
|
||||
if assign[:success]
|
||||
render json: success_json
|
||||
|
|
|
@ -15,10 +15,26 @@ class Assignment < ActiveRecord::Base
|
|||
)
|
||||
}
|
||||
|
||||
before_validation :default_status
|
||||
|
||||
validate :validate_status, if: -> { SiteSetting.enable_assign_status }
|
||||
|
||||
def self.valid_type?(type)
|
||||
VALID_TYPES.include?(type.downcase)
|
||||
end
|
||||
|
||||
def self.statuses
|
||||
SiteSetting.assign_statuses.split("|")
|
||||
end
|
||||
|
||||
def self.default_status
|
||||
Assignment.statuses.first
|
||||
end
|
||||
|
||||
def self.status_enabled?
|
||||
SiteSetting.enable_assign_status
|
||||
end
|
||||
|
||||
def assigned_to_user?
|
||||
assigned_to_type == "User"
|
||||
end
|
||||
|
@ -26,6 +42,18 @@ class Assignment < ActiveRecord::Base
|
|||
def assigned_to_group?
|
||||
assigned_to_type == "Group"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_status
|
||||
self.status ||= Assignment.default_status if SiteSetting.enable_assign_status
|
||||
end
|
||||
|
||||
def validate_status
|
||||
if SiteSetting.enable_assign_status && !Assignment.statuses.include?(self.status)
|
||||
errors.add(:status, :invalid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { action } from "@ember/object";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { not, or } from "@ember/object/computed";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
|
@ -42,6 +43,27 @@ export default Controller.extend(ModalFunctionality, {
|
|||
});
|
||||
},
|
||||
|
||||
@discourseComputed("siteSettings.enable_assign_status")
|
||||
statusEnabled() {
|
||||
return this.siteSettings.enable_assign_status;
|
||||
},
|
||||
|
||||
@discourseComputed("siteSettings.assign_statuses")
|
||||
availableStatuses() {
|
||||
return this.siteSettings.assign_statuses.split("|").map((status) => {
|
||||
return { id: status, name: status };
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed("siteSettings.assign_statuses", "model.status")
|
||||
status() {
|
||||
return (
|
||||
this.model.status ||
|
||||
this.model.target.assignment_status ||
|
||||
this.siteSettings.assign_statuses.split("|")[0]
|
||||
);
|
||||
},
|
||||
|
||||
@action
|
||||
handleTextAreaKeydown(value, event) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
|
||||
|
@ -82,6 +104,7 @@ export default Controller.extend(ModalFunctionality, {
|
|||
target_id: this.get("model.target.id"),
|
||||
target_type: this.get("model.targetType"),
|
||||
note: this.get("model.note"),
|
||||
status: this.get("model.status"),
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
|
|
|
@ -577,7 +577,9 @@ function initialize(api) {
|
|||
"assigned_group_to_post",
|
||||
"unassigned_from_post",
|
||||
"unassigned_group_from_post",
|
||||
"details_change",
|
||||
"note_change",
|
||||
"status_change",
|
||||
].includes(transformed.actionCode)
|
||||
) {
|
||||
transformed.isSmallAction = true;
|
||||
|
@ -592,7 +594,8 @@ function initialize(api) {
|
|||
topic.getProperties(
|
||||
"assigned_to_user",
|
||||
"assigned_to_group",
|
||||
"assignment_note"
|
||||
"assignment_note",
|
||||
"assignment_status"
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -617,6 +620,7 @@ function initialize(api) {
|
|||
topicAssignee,
|
||||
assignedToIndirectly.map((assigned) => ({
|
||||
assignee: assigned.assigned_to,
|
||||
status: assigned.assignment_status,
|
||||
note: assigned.assignment_note,
|
||||
}))
|
||||
)
|
||||
|
@ -782,6 +786,7 @@ function initialize(api) {
|
|||
const target = post || topic;
|
||||
|
||||
target.set("assignment_note", data.assignment_note);
|
||||
target.set("assignment_status", data.assignment_status);
|
||||
if (data.assigned_type === "User") {
|
||||
target.set(
|
||||
"assigned_to_user_id",
|
||||
|
|
|
@ -38,6 +38,7 @@ export default Service.extend({
|
|||
group_name: target.assigned_to_group?.name,
|
||||
target,
|
||||
targetType: options.targetType,
|
||||
status: target.assignment_status,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@ -49,6 +50,7 @@ export default Service.extend({
|
|||
username: user.username,
|
||||
target_id: target.id,
|
||||
target_type: targetType,
|
||||
status: target.assignment_status,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -25,8 +25,21 @@
|
|||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
<label>{{i18n "discourse_assign.assign_modal.note_label"}}</label>
|
||||
{{textarea id="assign-modal-note" value=model.note key-down=(action "handleTextAreaKeydown")}}
|
||||
{{#if this.statusEnabled}}
|
||||
<div class="control-group assign-status">
|
||||
<label>{{i18n "discourse_assign.assign_modal.status_label"}}</label>
|
||||
{{combo-box
|
||||
id="assign-status"
|
||||
content=availableStatuses
|
||||
value=status
|
||||
onChange=(action (mut model.status))
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="control-group assign-status">
|
||||
<label>{{i18n "discourse_assign.assign_modal.note_label"}}</label>
|
||||
{{textarea id="assign-modal-note" value=model.note key-down=(action "handleTextAreaKeydown")}}
|
||||
</div>
|
||||
</div>
|
||||
{{/d-modal-body}}
|
||||
|
||||
|
|
|
@ -15,7 +15,9 @@ en:
|
|||
unassigned_group_from_post: "unassigned %{who} from <a href='%{path}'>post</a> %{when}"
|
||||
reassigned: "Reassigned %{who} %{when}"
|
||||
reassigned_group: "Reassigned %{who} %{when}"
|
||||
details_change: "changed assignment details for %{who} %{when}"
|
||||
note_change: "changed assignment note for %{who} %{when}"
|
||||
status_change: "changed assignment status for %{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"
|
||||
|
@ -41,11 +43,11 @@ en:
|
|||
title: "Unassign from Post"
|
||||
help: "Unassign %{username} from Post"
|
||||
reassign:
|
||||
title: "Reassign"
|
||||
title: "Edit"
|
||||
title_w_ellipsis: "Edit assignment..."
|
||||
to_self: "Reassign to me"
|
||||
to_self_help: "Reassign Topic to me"
|
||||
help: "Reassign Topic to a different user"
|
||||
help: "Edit assignment details"
|
||||
reassign_modal:
|
||||
title: "Reassign Topic"
|
||||
description: "Enter the name of the user you'd like to Reassign this topic"
|
||||
|
@ -55,6 +57,7 @@ en:
|
|||
description: "Enter the name of the user you'd like to assign this topic"
|
||||
assign: "Assign"
|
||||
note_label: Note
|
||||
status_label: Status
|
||||
assign_post_modal:
|
||||
title: "Assign Post"
|
||||
description: "Enter the name of the user you'd like to assign this post"
|
||||
|
|
|
@ -15,6 +15,13 @@ en:
|
|||
remind_assigns_frequency: "Frequency for reminding users about assigned topics."
|
||||
max_assigned_topics: "Maximum number of topics that can be assigned to a user."
|
||||
assign_allowed_on_groups: "Users in these groups are allowed to assign topics and can be assigned topics."
|
||||
enable_assign_status: "Add a customizable status field to every assignment."
|
||||
assign_statuses: "List of statuses available to each assignment. The first status is the default status applied to every new assignment."
|
||||
errors:
|
||||
assign_statuses:
|
||||
too_few: "There must be at least two different statuses available."
|
||||
duplicate: "There are duplicate status values."
|
||||
removed_in_use: "Can't remove a status from the list if there are existing assignments using this status."
|
||||
discourse_assign:
|
||||
assigned_to: "Topic assigned to @%{username}"
|
||||
unassigned: "Topic was unassigned"
|
||||
|
@ -77,3 +84,10 @@ en:
|
|||
discourse_push_notifications:
|
||||
popup:
|
||||
assigned: "@%{username} assigned you"
|
||||
activerecord:
|
||||
errors:
|
||||
models:
|
||||
assignment:
|
||||
attributes:
|
||||
status:
|
||||
invalid: "Selected status is invalid (it is not included in the assigned_status site setting)."
|
||||
|
|
|
@ -33,3 +33,12 @@ plugins:
|
|||
default: ""
|
||||
allow_any: false
|
||||
refresh: true
|
||||
enable_assign_status:
|
||||
default: false
|
||||
client: true
|
||||
assign_statuses:
|
||||
client: true
|
||||
type: list
|
||||
default: "New|In Progress|Done"
|
||||
allow_any: true
|
||||
validator: AssignStatusesValidator
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddStatusToAssignments < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :assignments, :status, :text, null: true
|
||||
end
|
||||
end
|
|
@ -195,7 +195,7 @@ class ::Assigner
|
|||
topic.posts.where(post_number: 1).first
|
||||
end
|
||||
|
||||
def forbidden_reasons(assign_to:, type:, note:)
|
||||
def forbidden_reasons(assign_to:, type:, note:, status:)
|
||||
case
|
||||
when assign_to.is_a?(User) && !can_assignee_see_target?(assign_to)
|
||||
if topic.private_message?
|
||||
|
@ -211,9 +211,9 @@ class ::Assigner
|
|||
end
|
||||
when !can_be_assigned?(assign_to)
|
||||
assign_to.is_a?(User) ? :forbidden_assign_to : :forbidden_group_assign_to
|
||||
when topic_same_assignee_and_note(assign_to, type, note)
|
||||
when topic_same_assignee_and_details(assign_to, type, note, status)
|
||||
assign_to.is_a?(User) ? :already_assigned : :group_already_assigned
|
||||
when post_same_assignee_and_note(assign_to, type, note)
|
||||
when post_same_assignee_and_details(assign_to, type, note, status)
|
||||
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
|
||||
|
@ -222,32 +222,49 @@ class ::Assigner
|
|||
end
|
||||
end
|
||||
|
||||
def update_note(assign_to, note, skip_small_action_post: false)
|
||||
@target.assignment.update!(note: note)
|
||||
def update_details(assign_to, note, status, skip_small_action_post: false)
|
||||
case
|
||||
when @target.assignment.note != note && @target.assignment.status != status && status.present?
|
||||
small_action_text = <<~TEXT
|
||||
Status: #{@target.assignment.status} → #{status}
|
||||
|
||||
#{note}
|
||||
TEXT
|
||||
change_type = "details"
|
||||
when @target.assignment.note != note
|
||||
small_action_text = note
|
||||
change_type = "note"
|
||||
when @target.assignment.status != status
|
||||
small_action_text = "#{@target.assignment.status} → #{status}"
|
||||
change_type = "status"
|
||||
end
|
||||
|
||||
@target.assignment.update!(note: note, status: status)
|
||||
|
||||
queue_notification(assign_to, skip_small_action_post)
|
||||
|
||||
assignment = @target.assignment
|
||||
publish_assignment(assignment, assign_to, note)
|
||||
publish_assignment(assignment, assign_to, note, status)
|
||||
|
||||
# email is skipped, for now
|
||||
|
||||
unless skip_small_action_post
|
||||
action_code = "note_change"
|
||||
add_small_action_post(action_code, assign_to, note)
|
||||
action_code = "#{change_type}_change"
|
||||
add_small_action_post(action_code, assign_to, small_action_text)
|
||||
end
|
||||
|
||||
{ success: true }
|
||||
end
|
||||
|
||||
def assign(assign_to, note: nil, skip_small_action_post: false)
|
||||
def assign(assign_to, note: nil, skip_small_action_post: false, status: nil)
|
||||
assigned_to_type = assign_to.is_a?(User) ? "User" : "Group"
|
||||
|
||||
forbidden_reason = forbidden_reasons(assign_to: assign_to, type: assigned_to_type, note: note)
|
||||
forbidden_reason =
|
||||
forbidden_reasons(assign_to: assign_to, type: assigned_to_type, note: note, status: status)
|
||||
return { success: false, reason: forbidden_reason } if forbidden_reason
|
||||
|
||||
if no_assignee_change?(assign_to)
|
||||
return update_note(assign_to, note, skip_small_action_post: skip_small_action_post)
|
||||
return update_details(assign_to, note, status, skip_small_action_post: skip_small_action_post)
|
||||
end
|
||||
|
||||
action_code = {}
|
||||
|
@ -265,13 +282,14 @@ class ::Assigner
|
|||
assigned_by_user_id: @assigned_by.id,
|
||||
topic_id: topic.id,
|
||||
note: note,
|
||||
status: status,
|
||||
)
|
||||
|
||||
first_post.publish_change_to_clients!(:revised, reload_topic: true)
|
||||
|
||||
queue_notification(assign_to, skip_small_action_post)
|
||||
|
||||
publish_assignment(assignment, assign_to, note)
|
||||
publish_assignment(assignment, assign_to, note, status)
|
||||
|
||||
if assignment.assigned_to_user?
|
||||
if !TopicUser.exists?(
|
||||
|
@ -413,6 +431,7 @@ class ::Assigner
|
|||
post_number: post_target? && @target.post_number,
|
||||
assigned_type: assignment.assigned_to.is_a?(User) ? "User" : "Group",
|
||||
assignment_note: nil,
|
||||
assignment_status: nil,
|
||||
},
|
||||
user_ids: allowed_user_ids,
|
||||
)
|
||||
|
@ -433,7 +452,7 @@ class ::Assigner
|
|||
)
|
||||
end
|
||||
|
||||
def add_small_action_post(action_code, assign_to, note)
|
||||
def add_small_action_post(action_code, assign_to, text)
|
||||
custom_fields = {
|
||||
"action_code_who" => assign_to.is_a?(User) ? assign_to.username : assign_to.name,
|
||||
}
|
||||
|
@ -446,7 +465,7 @@ class ::Assigner
|
|||
|
||||
topic.add_moderator_post(
|
||||
@assigned_by,
|
||||
note,
|
||||
text,
|
||||
bump: false,
|
||||
post_type: SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper],
|
||||
action_code: action_code,
|
||||
|
@ -454,7 +473,7 @@ class ::Assigner
|
|||
)
|
||||
end
|
||||
|
||||
def publish_assignment(assignment, assign_to, note)
|
||||
def publish_assignment(assignment, assign_to, note, status)
|
||||
serializer = assignment.assigned_to_user? ? BasicUserSerializer : BasicGroupSerializer
|
||||
MessageBus.publish(
|
||||
"/staff/topic-assignment",
|
||||
|
@ -466,6 +485,7 @@ class ::Assigner
|
|||
assigned_type: assignment.assigned_to_type,
|
||||
assigned_to: serializer.new(assign_to, scope: Guardian.new, root: false).as_json,
|
||||
assignment_note: note,
|
||||
assignment_status: status,
|
||||
},
|
||||
user_ids: allowed_user_ids,
|
||||
)
|
||||
|
@ -491,19 +511,27 @@ class ::Assigner
|
|||
return "unassigned_group#{suffix}" if assignment.assigned_to_group?
|
||||
end
|
||||
|
||||
def topic_same_assignee_and_note(assign_to, type, note)
|
||||
def topic_same_assignee_and_details(assign_to, type, note, status)
|
||||
topic.assignment&.assigned_to_id == assign_to.id &&
|
||||
topic.assignment&.assigned_to_type == type && topic.assignment.active == true &&
|
||||
topic.assignment&.note == note
|
||||
topic.assignment&.note == note &&
|
||||
(
|
||||
topic.assignment&.status == status ||
|
||||
topic.assignment&.status == Assignment.default_status && status.nil?
|
||||
)
|
||||
end
|
||||
|
||||
def post_same_assignee_and_note(assign_to, type, note)
|
||||
def post_same_assignee_and_details(assign_to, type, note, status)
|
||||
@target.is_a?(Topic) &&
|
||||
Assignment
|
||||
.where(topic_id: topic.id, target_type: "Post", active: true)
|
||||
.any? do |assignment|
|
||||
assignment.assigned_to_id == assign_to.id && assignment.assigned_to_type == type &&
|
||||
assignment&.note == note
|
||||
assignment&.note == note &&
|
||||
(
|
||||
topic.assignment&.status == status ||
|
||||
topic.assignment&.status == Assignment.default_status && status.nil?
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ module DiscourseAssign
|
|||
.map do |post_id, assigned_map|
|
||||
assigned_to = assigned_map[:assigned_to]
|
||||
note = assigned_map[:assignment_note]
|
||||
status = assigned_map[:assignment_status]
|
||||
post_number = assigned_map[:post_number]
|
||||
|
||||
if (assigned_to.is_a?(User))
|
||||
|
@ -41,6 +42,7 @@ module DiscourseAssign
|
|||
assigned_to: build_assigned_to_user(assigned_to, topic),
|
||||
post_number: post_number,
|
||||
assignment_note: note,
|
||||
assignment_status: status,
|
||||
},
|
||||
]
|
||||
elsif assigned_to.is_a?(Group)
|
||||
|
@ -50,6 +52,7 @@ module DiscourseAssign
|
|||
assigned_to: build_assigned_to_group(assigned_to, topic),
|
||||
post_number: post_number,
|
||||
assignment_note: note,
|
||||
assignment_status: status,
|
||||
},
|
||||
]
|
||||
end
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AssignStatusesValidator
|
||||
def initialize(opts = {})
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def valid_value?(value)
|
||||
statuses = value.split("|")
|
||||
|
||||
case
|
||||
when statuses.size < 2
|
||||
@reason = "too_few"
|
||||
return false
|
||||
when statuses.size != statuses.uniq.size
|
||||
@reason = "duplicate"
|
||||
return false
|
||||
when Assignment.where.not(status: statuses).count > 0
|
||||
@reason = "removed_in_use"
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def error_message
|
||||
I18n.t("site_settings.errors.assign_statuses.#{@reason}")
|
||||
end
|
||||
end
|
37
plugin.rb
37
plugin.rb
|
@ -19,6 +19,7 @@ register_svg_icon "user-times"
|
|||
|
||||
load File.expand_path("../lib/discourse_assign/engine.rb", __FILE__)
|
||||
load File.expand_path("../lib/discourse_assign/helpers.rb", __FILE__)
|
||||
load File.expand_path("../lib/validators/assign_statuses_validator.rb", __FILE__)
|
||||
|
||||
Discourse::Application.routes.append do
|
||||
mount ::DiscourseAssign::Engine, at: "/assign"
|
||||
|
@ -497,11 +498,16 @@ after_initialize do
|
|||
.where(topic_id: id, target_type: "Post", active: true)
|
||||
.includes(:target)
|
||||
.inject({}) do |acc, assignment|
|
||||
acc[assignment.target_id] = {
|
||||
assigned_to: assignment.assigned_to,
|
||||
post_number: assignment.target.post_number,
|
||||
assignment_note: assignment.note,
|
||||
} if assignment.target
|
||||
if assignment.target
|
||||
acc[assignment.target_id] = {
|
||||
assigned_to: assignment.assigned_to,
|
||||
post_number: assignment.target.post_number,
|
||||
assignment_note: assignment.note,
|
||||
}
|
||||
acc[assignment.target_id][
|
||||
:assignment_status
|
||||
] = assignment.status if SiteSetting.enable_assign_status
|
||||
end
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
@ -563,6 +569,13 @@ after_initialize do
|
|||
(SiteSetting.assigns_public || scope.can_assign?) && object.topic.assignment.present?
|
||||
end
|
||||
|
||||
add_to_serializer(:topic_view, :assignment_status, false) { object.topic.assignment.status }
|
||||
|
||||
add_to_serializer(:topic_view, :include_assignment_status?, false) do
|
||||
SiteSetting.enable_assign_status && (SiteSetting.assigns_public || scope.can_assign?) &&
|
||||
object.topic.assignment.present?
|
||||
end
|
||||
|
||||
# SuggestedTopic serializer
|
||||
add_to_serializer(:suggested_topic, :assigned_to_user, false) do
|
||||
DiscourseAssign::Helpers.build_assigned_to_user(object.assigned_to, object)
|
||||
|
@ -613,6 +626,13 @@ after_initialize do
|
|||
(SiteSetting.assigns_public || scope.can_assign?) && object.assigned_to&.is_a?(Group)
|
||||
end
|
||||
|
||||
add_to_serializer(:topic_list_item, :assignment_status, false) { object.assignment.status }
|
||||
|
||||
add_to_serializer(:topic_list_item, :include_assignment_status?, false) do
|
||||
SiteSetting.enable_assign_status && (SiteSetting.assigns_public || scope.can_assign?) &&
|
||||
object.assignment.present?
|
||||
end
|
||||
|
||||
# SearchTopicListItem serializer
|
||||
add_to_serializer(:search_topic_list_item, :assigned_to_user, false) do
|
||||
DiscourseAssign::Helpers.build_assigned_to_user(object.assigned_to, object)
|
||||
|
@ -718,6 +738,13 @@ after_initialize do
|
|||
(SiteSetting.assigns_public || scope.can_assign?) && object.assignment.present?
|
||||
end
|
||||
|
||||
add_to_serializer(:post, :assignment_status, false) { object.assignment.status }
|
||||
|
||||
add_to_serializer(:post, :include_assignment_status?, false) do
|
||||
SiteSetting.enable_assign_status && (SiteSetting.assigns_public || scope.can_assign?) &&
|
||||
object.assignment.present?
|
||||
end
|
||||
|
||||
# CurrentUser serializer
|
||||
add_to_serializer(:current_user, :can_assign) { object.can_assign? }
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ describe Search do
|
|||
},
|
||||
post_number: post5.post_number,
|
||||
assignment_note: nil,
|
||||
assignment_status: nil,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe Assigner do
|
||||
before { SiteSetting.assign_enabled = true }
|
||||
before do
|
||||
SiteSetting.assign_enabled = true
|
||||
SiteSetting.enable_assign_status = true
|
||||
end
|
||||
|
||||
let(:assign_allowed_group) { Group.find_by(name: "staff") }
|
||||
let(:pm_post) { Fabricate(:private_message_post) }
|
||||
|
@ -53,11 +56,17 @@ RSpec.describe Assigner do
|
|||
expect(topic.posts.last.raw).to eq "tomtom best mom"
|
||||
end
|
||||
|
||||
it "can assign with status" do
|
||||
assigner.assign(moderator, status: "In Progress")
|
||||
|
||||
expect(topic.assignment.status).to eq "In Progress"
|
||||
end
|
||||
|
||||
it "publishes topic assignment after assign and unassign" do
|
||||
messages =
|
||||
MessageBus.track_publish("/staff/topic-assignment") do
|
||||
assigner = described_class.new(topic, moderator_2)
|
||||
assigner.assign(moderator, note: "tomtom best mom")
|
||||
assigner.assign(moderator, note: "tomtom best mom", status: "In Progress")
|
||||
assigner.unassign
|
||||
end
|
||||
|
||||
|
@ -71,6 +80,7 @@ RSpec.describe Assigner do
|
|||
assigned_type: "User",
|
||||
assigned_to: BasicUserSerializer.new(moderator, scope: Guardian.new, root: false).as_json,
|
||||
assignment_note: "tomtom best mom",
|
||||
assignment_status: "In Progress",
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -83,6 +93,7 @@ RSpec.describe Assigner do
|
|||
post_number: false,
|
||||
assigned_type: "User",
|
||||
assignment_note: nil,
|
||||
assignment_status: nil,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
@ -356,6 +367,76 @@ RSpec.describe Assigner do
|
|||
expect(small_action_post.action_code).to eq "note_change"
|
||||
end
|
||||
end
|
||||
|
||||
describe "updating status" do
|
||||
it "does not recreate assignment if no assignee change" do
|
||||
assigner.assign(moderator)
|
||||
|
||||
expect do assigner.assign(moderator, status: "Done") end.to_not change {
|
||||
Assignment.last.id
|
||||
}
|
||||
end
|
||||
|
||||
it "updates status" do
|
||||
assigner.assign(moderator)
|
||||
|
||||
assigner.assign(moderator, status: "Done")
|
||||
|
||||
expect(Assignment.last.status).to eq "Done"
|
||||
end
|
||||
|
||||
it "queues notification" do
|
||||
assigner.assign(moderator)
|
||||
|
||||
expect_enqueued_with(job: :assign_notification) do
|
||||
assigner.assign(moderator, status: "Done")
|
||||
end
|
||||
end
|
||||
|
||||
it "publishes topic assignment with note" do
|
||||
assigner.assign(moderator)
|
||||
|
||||
messages =
|
||||
MessageBus.track_publish("/staff/topic-assignment") do
|
||||
assigner = described_class.new(topic, moderator_2)
|
||||
assigner.assign(moderator, status: "Done")
|
||||
end
|
||||
|
||||
expect(messages[0].channel).to eq "/staff/topic-assignment"
|
||||
expect(messages[0].data).to include(
|
||||
{
|
||||
type: "assigned",
|
||||
topic_id: topic.id,
|
||||
post_id: false,
|
||||
post_number: false,
|
||||
assigned_type: "User",
|
||||
assigned_to:
|
||||
BasicUserSerializer.new(moderator, scope: Guardian.new, root: false).as_json,
|
||||
assignment_status: "Done",
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "adds a note_change small action post" do
|
||||
assigner.assign(moderator)
|
||||
|
||||
assigner.assign(moderator, status: "Done")
|
||||
|
||||
small_action_post = topic.posts.last
|
||||
expect(small_action_post.action_code).to eq "status_change"
|
||||
end
|
||||
end
|
||||
|
||||
describe "updating note and status at the same time" do
|
||||
it "adds a note_change small action post" do
|
||||
assigner.assign(moderator)
|
||||
|
||||
assigner.assign(moderator, note: "This is a note!", status: "Done")
|
||||
|
||||
small_action_post = topic.posts.last
|
||||
expect(small_action_post.action_code).to eq "details_change"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "assign_self_regex" do
|
||||
|
|
|
@ -115,6 +115,7 @@ RSpec.describe DiscourseAssign::AssignController do
|
|||
before do
|
||||
sign_in(user)
|
||||
add_to_assign_allowed_group(user2)
|
||||
SiteSetting.enable_assign_status = true
|
||||
end
|
||||
|
||||
it "assigns topic to a user" do
|
||||
|
@ -141,6 +142,29 @@ RSpec.describe DiscourseAssign::AssignController do
|
|||
expect(post.topic.reload.assignment.note).to eq("do dis pls")
|
||||
end
|
||||
|
||||
it "assigns topic with a set status to a user" do
|
||||
put "/assign/assign.json",
|
||||
params: {
|
||||
target_id: post.topic_id,
|
||||
target_type: "Topic",
|
||||
username: user2.username,
|
||||
status: "In Progress",
|
||||
}
|
||||
|
||||
expect(post.topic.reload.assignment.status).to eq("In Progress")
|
||||
end
|
||||
|
||||
it "assigns topic with default status to a user" do
|
||||
put "/assign/assign.json",
|
||||
params: {
|
||||
target_id: post.topic_id,
|
||||
target_type: "Topic",
|
||||
username: user2.username,
|
||||
}
|
||||
|
||||
expect(post.topic.reload.assignment.status).to eq("New")
|
||||
end
|
||||
|
||||
it "assigns topic to a group" do
|
||||
put "/assign/assign.json",
|
||||
params: {
|
||||
|
|
|
@ -39,4 +39,24 @@ describe PostSerializer do
|
|||
serializer = PostSerializer.new(post, scope: guardian)
|
||||
expect(serializer.as_json[:post][:assignment_note]).to eq("tomtom best")
|
||||
end
|
||||
|
||||
context "when status is enabled" do
|
||||
before { SiteSetting.enable_assign_status = true }
|
||||
|
||||
it "includes status in serializer" do
|
||||
Assigner.new(post, user).assign(user, status: "Done")
|
||||
serializer = PostSerializer.new(post, scope: guardian)
|
||||
expect(serializer.as_json[:post][:assignment_status]).to eq("Done")
|
||||
end
|
||||
end
|
||||
|
||||
context "when status is disabled" do
|
||||
before { SiteSetting.enable_assign_status = false }
|
||||
|
||||
it "doesn't include status in serializer" do
|
||||
Assigner.new(post, user).assign(user, status: "Done")
|
||||
serializer = PostSerializer.new(post, scope: guardian)
|
||||
expect(serializer.as_json[:post][:assignment_status]).not_to eq("Done")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,4 +45,40 @@ RSpec.describe TopicViewSerializer do
|
|||
serializer.as_json[:topic_view][:indirectly_assigned_to][post.id][:assignment_note],
|
||||
).to eq("note me down")
|
||||
end
|
||||
|
||||
context "when status is enabled" do
|
||||
before { SiteSetting.enable_assign_status = true }
|
||||
|
||||
it "includes status in serializer" do
|
||||
Assigner.new(topic, user).assign(user, status: "Done")
|
||||
serializer = TopicViewSerializer.new(TopicView.new(topic), scope: guardian)
|
||||
expect(serializer.as_json[:topic_view][:assignment_status]).to eq("Done")
|
||||
end
|
||||
|
||||
it "includes indirectly_assigned_to status in serializer" do
|
||||
Assigner.new(post, user).assign(user, status: "Done")
|
||||
serializer = TopicViewSerializer.new(TopicView.new(topic), scope: guardian)
|
||||
expect(
|
||||
serializer.as_json[:topic_view][:indirectly_assigned_to][post.id][:assignment_status],
|
||||
).to eq("Done")
|
||||
end
|
||||
end
|
||||
|
||||
context "when status is disabled" do
|
||||
before { SiteSetting.enable_assign_status = false }
|
||||
|
||||
it "doesn't include status in serializer" do
|
||||
Assigner.new(topic, user).assign(user, status: "Done")
|
||||
serializer = TopicViewSerializer.new(TopicView.new(topic), scope: guardian)
|
||||
expect(serializer.as_json[:topic_view][:assignment_status]).not_to eq("Done")
|
||||
end
|
||||
|
||||
it "doesn't include indirectly_assigned_to status in serializer" do
|
||||
Assigner.new(post, user).assign(user, status: "Done")
|
||||
serializer = TopicViewSerializer.new(TopicView.new(topic), scope: guardian)
|
||||
expect(
|
||||
serializer.as_json[:topic_view][:indirectly_assigned_to][post.id][:assignment_status],
|
||||
).not_to eq("Done")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -109,6 +109,92 @@ acceptance("Discourse Assign | Assign desktop", function (needs) {
|
|||
});
|
||||
});
|
||||
|
||||
acceptance("Discourse Assign | Assign Status enabled", function (needs) {
|
||||
needs.user({
|
||||
can_assign: true,
|
||||
});
|
||||
needs.settings({
|
||||
assign_enabled: true,
|
||||
enable_assign_status: true,
|
||||
assign_statuses: "New|In Progress|Done",
|
||||
});
|
||||
needs.hooks.beforeEach(() => clearTopicFooterButtons());
|
||||
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/assign/suggestions", () => {
|
||||
return helper.response({
|
||||
success: true,
|
||||
assign_allowed_groups: false,
|
||||
assign_allowed_for_groups: [],
|
||||
suggestions: [
|
||||
{
|
||||
id: 19,
|
||||
username: "eviltrout",
|
||||
name: "Robin Ward",
|
||||
avatar_template:
|
||||
"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
server.put("/assign/assign", () => {
|
||||
return helper.response({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
test("Modal contains status dropdown", async (assert) => {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await click("#topic-footer-button-assign");
|
||||
|
||||
assert.ok(
|
||||
exists(".assign.modal-body #assign-status"),
|
||||
"assign status dropdown exists"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Discourse Assign | Assign Status disabled", function (needs) {
|
||||
needs.user({
|
||||
can_assign: true,
|
||||
});
|
||||
needs.settings({ assign_enabled: true, enable_assign_status: false });
|
||||
needs.hooks.beforeEach(() => clearTopicFooterButtons());
|
||||
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/assign/suggestions", () => {
|
||||
return helper.response({
|
||||
success: true,
|
||||
assign_allowed_groups: false,
|
||||
assign_allowed_for_groups: [],
|
||||
suggestions: [
|
||||
{
|
||||
id: 19,
|
||||
username: "eviltrout",
|
||||
name: "Robin Ward",
|
||||
avatar_template:
|
||||
"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
server.put("/assign/assign", () => {
|
||||
return helper.response({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
test("Modal contains status dropdown", async (assert) => {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await click("#topic-footer-button-assign");
|
||||
|
||||
assert.notOk(
|
||||
exists(".assign.modal-body #assign-status"),
|
||||
"assign status dropdown doesn't exists"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// See RemindAssignsFrequencySiteSettings
|
||||
const remindersFrequency = [
|
||||
{
|
||||
|
|
|
@ -23,6 +23,7 @@ function assignCurrentUserToTopic(needs) {
|
|||
"/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
|
||||
};
|
||||
topic["assignment_note"] = "Shark Doododooo";
|
||||
topic["assignment_status"] = "New";
|
||||
topic["indirectly_assigned_to"] = {
|
||||
2: {
|
||||
assigned_to: {
|
||||
|
@ -89,6 +90,7 @@ acceptance("Discourse Assign | Assigned topic", function (needs) {
|
|||
tagging_enabled: true,
|
||||
assigns_user_url_path: "/",
|
||||
assigns_public: true,
|
||||
enable_assign_status: true,
|
||||
});
|
||||
|
||||
assignCurrentUserToTopic(needs);
|
||||
|
|
|
@ -28,6 +28,7 @@ discourseModule("Unit | Service | task-actions", function () {
|
|||
group_name: "cats",
|
||||
target,
|
||||
targetType: "Topic",
|
||||
status: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue