Feature #42630 » 0001-Show-reaction-counts-and-user-names-only-for-reactions-visible-to-the-logged-in-user.patch
app/models/reaction.rb | ||
---|---|---|
25 | 25 | |
26 | 26 |
scope :by, ->(user) { where(user: user) } |
27 | 27 |
scope :for_reactable, ->(reactable) { where(reactable: reactable) } |
28 |
scope :visible, ->(user) { where(user: User.visible(user)) } |
|
28 | 29 | |
29 | 30 |
# Represents reaction details for a reactable object |
30 | 31 |
Detail = Struct.new( |
31 |
# Total number of reactions |
|
32 |
:reaction_count, |
|
33 | 32 |
# Users who reacted and are visible to the target user |
34 | 33 |
:visible_users, |
35 | 34 |
# Reaction of the target user |
36 | 35 |
:user_reaction |
37 | 36 |
) do |
38 |
def initialize(reaction_count: 0, visible_users: [], user_reaction: nil)
|
|
37 |
def initialize(visible_users: [], user_reaction: nil) |
|
39 | 38 |
super |
40 | 39 |
end |
40 | ||
41 |
def reaction_count = visible_users.size |
|
41 | 42 |
end |
42 | 43 | |
43 | 44 |
def self.build_detail_map_for(reactables, user) |
44 |
reactions = preload(:user)
|
|
45 |
reactions = visible(user)
|
|
45 | 46 |
.for_reactable(reactables) |
47 |
.preload(:user) |
|
46 | 48 |
.select(:id, :reactable_id, :user_id) |
47 | 49 |
.order(id: :desc) |
48 | 50 | |
49 |
# Prepare IDs of users who reacted and are visible to the user |
|
50 |
visible_user_ids = User.visible(user) |
|
51 |
.joins(:reactions) |
|
52 |
.where(reactions: for_reactable(reactables)) |
|
53 |
.pluck(:id).to_set |
|
54 | ||
55 | 51 |
reactions.each_with_object({}) do |reaction, m| |
56 | 52 |
m[reaction.reactable_id] ||= Detail.new |
57 | 53 | |
58 | 54 |
m[reaction.reactable_id].then do |detail| |
59 |
detail.reaction_count += 1 |
|
60 |
detail.visible_users << reaction.user if visible_user_ids.include?(reaction.user.id) |
|
55 |
detail.visible_users << reaction.user |
|
61 | 56 |
detail.user_reaction = reaction if reaction.user == user |
62 | 57 |
end |
63 | 58 |
end |
test/functional/issues_controller_test.rb | ||
---|---|---|
3347 | 3347 |
# The current_user can only see members who belong to projects that the current_user has access to. |
3348 | 3348 |
# Since the Redmine Admin user does not belong to any projects visible to the current_user, |
3349 | 3349 |
# the Redmine Admin user's name is not displayed in the reaction user list. Instead, "1 other" is shown. |
3350 |
assert_select 'a.reaction-button[title=?]', 'Dave Lopper, John Smith, and 1 other' do
|
|
3351 |
assert_select 'span.icon-label', '3'
|
|
3350 |
assert_select 'a.reaction-button[title=?]', 'Dave Lopper and John Smith' do
|
|
3351 |
assert_select 'span.icon-label', '2'
|
|
3352 | 3352 |
end |
3353 | 3353 |
end |
3354 | 3354 |
test/helpers/reactions_helper_test.rb | ||
---|---|---|
106 | 106 |
assert_select_in result, 'a.reaction-button[title=?]', expected_tooltip |
107 | 107 |
end |
108 | 108 | |
109 |
test 'reaction_button displays non-visible users as "X other" in the tooltip' do
|
|
109 |
test 'reaction_button should not count and display non-visible users' do
|
|
110 | 110 |
issue2 = issues(:issues_002) |
111 | 111 | |
112 | 112 |
issue2.reaction_detail = Reaction::Detail.new( |
113 |
# The remaining 3 users are non-visible users |
|
114 |
reaction_count: 5, |
|
115 | 113 |
visible_users: users(:users_002, :users_003) |
116 | 114 |
) |
117 | 115 | |
... | ... | |
119 | 117 |
reaction_button(issue2) |
120 | 118 |
end |
121 | 119 | |
122 |
assert_select_in result, 'a.reaction-button[title=?]', 'John Smith, Dave Lopper, and 3 others'
|
|
120 |
assert_select_in result, 'a.reaction-button[title=?]', 'John Smith and Dave Lopper'
|
|
123 | 121 | |
124 | 122 |
# When all users are non-visible users |
125 | 123 |
issue2.reaction_detail = Reaction::Detail.new( |
126 |
reaction_count: 2, |
|
127 | 124 |
visible_users: [] |
128 | 125 |
) |
129 | 126 | |
... | ... | |
131 | 128 |
reaction_button(issue2) |
132 | 129 |
end |
133 | 130 | |
134 |
assert_select_in result, 'a.reaction-button[title=?]', '2 others' |
|
131 |
assert_select_in result, 'a.reaction-button[title]', false |
|
132 |
assert_select_in result, 'a.reaction-button' do |
|
133 |
assert_select 'span.icon-label', '0' |
|
134 |
end |
|
135 | 135 |
end |
136 | 136 | |
137 | 137 |
test 'reaction_button formats the tooltip content based on the support.array settings of each locale' do |
test/unit/lib/redmine/reaction_test.rb | ||
---|---|---|
42 | 42 |
Issue.preload_reaction_details([issue1, issue2]) |
43 | 43 | |
44 | 44 |
expected_issue1_reaction_detail = Reaction::Detail.new( |
45 |
reaction_count: 3, |
|
46 | 45 |
visible_users: [users(:users_003), users(:users_002), users(:users_001)], |
47 | 46 |
user_reaction: reactions(:reaction_002) |
48 | 47 |
) |
... | ... | |
53 | 52 | |
54 | 53 |
# Even when an object has no reactions, an empty ReactionDetail is set. |
55 | 54 |
assert_equal Reaction::Detail.new( |
56 |
reaction_count: 0, |
|
57 | 55 |
visible_users: [], |
58 | 56 |
user_reaction: nil |
59 | 57 |
), issue2.reaction_detail |
... | ... | |
107 | 105 |
assert_nil message7.instance_variable_get(:@reaction_detail) |
108 | 106 | |
109 | 107 |
assert_equal Reaction::Detail.new( |
110 |
reaction_count: 1, |
|
111 | 108 |
visible_users: [users(:users_002)], |
112 | 109 |
user_reaction: reactions(:reaction_009) |
113 | 110 |
), message7.reaction_detail |
... | ... | |
122 | 119 |
comment1.load_reaction_detail |
123 | 120 | |
124 | 121 |
assert_equal Reaction::Detail.new( |
125 |
reaction_count: 1, |
|
126 | 122 |
visible_users: [users(:users_002)], |
127 | 123 |
user_reaction: nil |
128 | 124 |
), comment1.reaction_detail |
test/unit/reaction_test.rb | ||
---|---|---|
75 | 75 | |
76 | 76 |
expected = { |
77 | 77 |
1 => Reaction::Detail.new( |
78 |
reaction_count: 3, |
|
79 | 78 |
visible_users: [users(:users_003), users(:users_002), users(:users_001)], |
80 | 79 |
user_reaction: reactions(:reaction_003) |
81 | 80 |
), |
82 | 81 |
6 => Reaction::Detail.new( |
83 |
reaction_count: 1, |
|
84 | 82 |
visible_users: [users(:users_002)], |
85 | 83 |
user_reaction: nil |
86 | 84 |
) |