Project

General

Profile

Feature #42630 » 0001-Show-reaction-counts-and-user-names-only-for-reactions-visible-to-the-logged-in-user.patch

Katsuya HIDAKA, 2025-05-14 04:10

View differences:

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
      )
(18-18/26)