diff --git a/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js b/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js index 99fc9c8..8d2df0e 100644 --- a/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js +++ b/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js @@ -2,6 +2,7 @@ import { renderAvatar } from "discourse/helpers/user-avatar"; import { withPluginApi } from "discourse/lib/plugin-api"; import discourseComputed from "discourse-common/utils/decorators"; import { iconHTML, iconNode } from "discourse-common/lib/icon-library"; +import { escapeExpression } from "discourse/lib/utilities"; import { h } from "virtual-dom"; import { queryRegistry } from "discourse/widgets/widget"; import { getOwner } from "discourse-common/lib/get-owner"; @@ -586,10 +587,19 @@ function initialize(api) { api.addDiscoveryQueryParam("assigned", { replace: true, refreshModel: true }); api.addTagsHtmlCallback((topic, params = {}) => { - const [assignedToUser, assignedToGroup] = Object.values( - topic.getProperties("assigned_to_user", "assigned_to_group") + const [assignedToUser, assignedToGroup, topicNote] = Object.values( + topic.getProperties( + "assigned_to_user", + "assigned_to_group", + "assignment_note" + ) ); + const topicAssignee = { + assignee: assignedToUser || assignedToGroup, + note: topicNote, + }; + let assignedToIndirectly; if (topic.get("indirectly_assigned_to")) { assignedToIndirectly = Object.entries( @@ -603,17 +613,18 @@ function initialize(api) { } const assignedTo = [] .concat( - assignedToUser, - assignedToGroup, - assignedToIndirectly.map((assigned) => assigned.assigned_to) + topicAssignee, + assignedToIndirectly.map((assigned) => ({ + assignee: assigned.assigned_to, + note: assigned.assignment_note, + })) ) - .filter((element) => element) - .flat() - .uniqBy((assignee) => assignee.assign_path); + .filter(({ assignee }) => assignee) + .flat(); if (assignedTo) { return assignedTo - .map((assignee) => { + .map(({ assignee, note }) => { let assignedPath; if (assignee.assignedToPostId) { assignedPath = `/p/${assignee.assignedToPostId}`; @@ -629,7 +640,7 @@ function initialize(api) { : ""; return `<${tagName} class="assigned-to discourse-tag simple" ${href}> ${icon} - ${name} + ${name} `; }) .join(""); @@ -769,7 +780,7 @@ function initialize(api) { } const target = post || topic; - target.set("assignment_note", null); + target.set("assignment_note", data.assignment_note); if (data.assigned_type === "User") { target.set( "assigned_to_user_id", diff --git a/lib/assigner.rb b/lib/assigner.rb index f54e3e6..ec8c801 100644 --- a/lib/assigner.rb +++ b/lib/assigner.rb @@ -209,6 +209,8 @@ class ::Assigner action_code[:user] = topic.assignment.present? ? "reassigned" : "assigned" action_code[:group] = topic.assignment.present? ? "reassigned_group" : "assigned_group" + silent = silent || no_assignee_change?(assign_to) + @target.assignment&.destroy! assignment = @target.create_assignment!(assigned_to_id: assign_to.id, assigned_to_type: type, assigned_by_user_id: @assigned_by.id, topic_id: topic.id, note: note) @@ -233,7 +235,8 @@ class ::Assigner post_id: post_target? && @target.id, post_number: post_target? && @target.post_number, 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, + assignment_note: note, }, user_ids: allowed_user_ids ) @@ -385,7 +388,8 @@ class ::Assigner topic_id: topic.id, post_id: post_target? && @target.id, post_number: post_target? && @target.post_number, - assigned_type: assignment.assigned_to.is_a?(User) ? "User" : "Group" + assigned_type: assignment.assigned_to.is_a?(User) ? "User" : "Group", + assignment_note: nil, }, user_ids: allowed_user_ids ) @@ -429,4 +433,8 @@ class ::Assigner assignment&.note == note end end + + def no_assignee_change?(assignee) + @target.assignment&.assigned_to_id == assignee.id + end end diff --git a/lib/discourse_assign/helpers.rb b/lib/discourse_assign/helpers.rb index 8deea1a..cc44bf1 100644 --- a/lib/discourse_assign/helpers.rb +++ b/lib/discourse_assign/helpers.rb @@ -30,12 +30,13 @@ module DiscourseAssign def self.build_indirectly_assigned_to(post_assignments, topic) post_assignments.map do |post_id, assigned_map| assigned_to = assigned_map[:assigned_to] + note = assigned_map[:assignment_note] post_number = assigned_map[:post_number] if (assigned_to.is_a?(User)) - [post_id, { assigned_to: build_assigned_to_user(assigned_to, topic), post_number: post_number }] + [post_id, { assigned_to: build_assigned_to_user(assigned_to, topic), post_number: post_number, assignment_note: note }] elsif assigned_to.is_a?(Group) - [post_id, { assigned_to: build_assigned_to_group(assigned_to, topic), post_number: post_number }] + [post_id, { assigned_to: build_assigned_to_group(assigned_to, topic), post_number: post_number, assignment_note: note }] end end.to_h end diff --git a/plugin.rb b/plugin.rb index 1535f5a..e9866cd 100644 --- a/plugin.rb +++ b/plugin.rb @@ -430,7 +430,7 @@ after_initialize do 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", active: true).includes(:target).inject({}) do |acc, assignment| - acc[assignment.target_id] = { assigned_to: assignment.assigned_to, post_number: assignment.target.post_number } if assignment.target + acc[assignment.target_id] = { assigned_to: assignment.assigned_to, post_number: assignment.target.post_number, assignment_note: assignment.note } if assignment.target acc end end diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 4fa7c12..da3367d 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -47,13 +47,17 @@ describe Search do result = Search.execute('in:assigned', guardian: guardian) serializer = GroupedSearchResultSerializer.new(result, scope: guardian) indirectly_assigned_to = serializer.as_json[:topics].find { |topic| topic[:id] == post5.topic.id }[:indirectly_assigned_to] - expect(indirectly_assigned_to).to eq(post5.id => { assigned_to: { - assign_icon: "user-plus", - assign_path: "/u/#{user.username}/activity/assigned", - avatar_template: user.avatar_template, - name: user.name, - username: user.username - }, post_number: post5.post_number }) + expect(indirectly_assigned_to).to eq(post5.id => { + assigned_to: { + assign_icon: "user-plus", + assign_path: "/u/#{user.username}/activity/assigned", + avatar_template: user.avatar_template, + name: user.name, + username: user.username, + }, + post_number: post5.post_number, + assignment_note: nil, + }) end end end diff --git a/spec/lib/assigner_spec.rb b/spec/lib/assigner_spec.rb index 7bb6f80..f3e7038 100644 --- a/spec/lib/assigner_spec.rb +++ b/spec/lib/assigner_spec.rb @@ -52,7 +52,7 @@ RSpec.describe Assigner do 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) + assigner.assign(moderator, note: "tomtom best mom") assigner.unassign end @@ -64,6 +64,7 @@ RSpec.describe Assigner do post_number: false, assigned_type: "User", assigned_to: BasicUserSerializer.new(moderator, scope: Guardian.new, root: false).as_json, + assignment_note: "tomtom best mom" }) expect(messages[1].channel).to eq "/staff/topic-assignment" @@ -73,6 +74,7 @@ RSpec.describe Assigner do post_id: false, post_number: false, assigned_type: "User", + assignment_note: nil }) end diff --git a/spec/serializers/topic_view_serializer_spec.rb b/spec/serializers/topic_view_serializer_spec.rb index f948299..23a25d8 100644 --- a/spec/serializers/topic_view_serializer_spec.rb +++ b/spec/serializers/topic_view_serializer_spec.rb @@ -35,4 +35,10 @@ RSpec.describe TopicViewSerializer do serializer = TopicViewSerializer.new(TopicView.new(topic), scope: guardian) expect(serializer.as_json[:topic_view][:assignment_note]).to eq("note me down") end + + it "includes indirectly_assigned_to notes in serializer" do + Assigner.new(post, user).assign(user, note: "note me down") + serializer = TopicViewSerializer.new(TopicView.new(topic), scope: guardian) + expect(serializer.as_json[:topic_view][:indirectly_assigned_to][post.id][:assignment_note]).to eq("note me down") + end end diff --git a/test/javascripts/acceptance/assigned-topic-test.js b/test/javascripts/acceptance/assigned-topic-test.js index 90de34c..82cd69a 100644 --- a/test/javascripts/acceptance/assigned-topic-test.js +++ b/test/javascripts/acceptance/assigned-topic-test.js @@ -20,12 +20,14 @@ function assignCurrentUserToTopic(needs) { avatar_template: "/letter_avatar/eviltrout/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png", }; + topic["assignment_note"] = "Shark Doododooo"; topic["indirectly_assigned_to"] = { 2: { assigned_to: { name: "Developers", }, post_number: 2, + assignment_note: '', }, }; return helper.response(topic); @@ -98,6 +100,16 @@ acceptance("Discourse Assign | Assigned topic", function (needs) { "shows assignment and indirect assignments in the first post" ); assert.ok(exists("#post_1 .assigned-to svg.d-icon-user-plus")); + assert.equal( + query(".discourse-tags .assigned-to[href='/t/28830'] span").title, + "Shark Doododooo", + "shows topic assign notes" + ); + assert.equal( + query(".discourse-tags .assigned-to[href='/p/2'] span").title, + '', + "shows indirect assign notes" + ); assert.ok( exists("#topic-footer-dropdown-reassign"), "shows reassign dropdown at the bottom of the topic"