FEATURE: Show note in tooltip (#327)

This commit is contained in:
Natalie Tay 2022-05-05 15:04:57 +08:00 committed by GitHub
parent 1ac2dfbae2
commit b314882a6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 68 additions and 24 deletions

View File

@ -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}
<span>${name}</span>
<span title="${escapeExpression(note)}">${name}</span>
</${tagName}>`;
})
.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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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: '<script>alert("xss")</script>',
},
};
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,
'<script>alert("xss")</script>',
"shows indirect assign notes"
);
assert.ok(
exists("#topic-footer-dropdown-reassign"),
"shows reassign dropdown at the bottom of the topic"