0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 13:08:40 +08:00
discourse/app/models/topic_posters_summary.rb
Elliot Temple a9f4ec5367
FIX: Select better featured posters (#39691)
There was a bug where the frequent posters were reordered by the date of
their most recent post in the topic, with older posts preferred. This
hid posters who should have been displayed (e.g. someone with the most
posts and the second most recent post). This PR fixes the bug and
improves the selection with better visibility for recent discussion and
new participants.

Bug report showing the issue with screenshots:
https://meta.discourse.org/t/frequent-posters-shown-on-latest-page/235281

New display logic: The topic list shows up to five posters in this
order: the OP, two frequent posters, and two recent posters. Recent
posters are excluded from frequent posters, and the OP is excluded from
both. The latest poster is handled separately and replaces a recent
poster unless the OP is the latest poster.

---------

Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
2026-07-24 16:10:46 -04:00

110 lines
2.8 KiB
Ruby
Vendored

# frozen_string_literal: true
# This is used in topic lists
class TopicPostersSummary
# localization is fast, but this allows us to avoid
# calling it in a loop which adds up
def self.translations
{
original_poster: I18n.t(:original_poster),
most_recent_poster: I18n.t(:most_recent_poster),
frequent_poster: I18n.t(:frequent_poster),
recent_poster: I18n.t(:recent_poster),
joiner: I18n.t(:poster_description_joiner),
}
end
attr_reader :topic, :options
def initialize(topic, options = {})
@topic = topic
@options = options
@translations = options[:translations] || TopicPostersSummary.translations
end
def summary
sorted_top_posters.compact.map(&method(:new_topic_poster_for))
end
private
def new_topic_poster_for(user)
topic_poster = TopicPoster.new
topic_poster.user = user
topic_poster.description = descriptions_for(user)
topic_poster.primary_group = user_lookup.primary_groups[user.id]
topic_poster.flair_group = user_lookup.flair_groups[user.id]
if topic.last_post_user_id == user.id
topic_poster.extras = +"latest"
topic_poster.extras << " single" if user_ids.uniq.size == 1
end
topic_poster
end
def descriptions_by_id(ids: nil)
@descriptions_by_id ||=
begin
result = {}
ids = ids || user_ids
if id = ids.shift
result[id] ||= []
result[id] << @translations[:original_poster]
end
if id = ids.shift
result[id] ||= []
result[id] << @translations[:most_recent_poster]
end
while id = ids.shift
result[id] ||= []
description =
if recent_poster_user_ids.include?(id)
@translations[:recent_poster]
else
@translations[:frequent_poster]
end
result[id] << description
end
result
end
end
def descriptions_for(user)
descriptions_by_id[user.id].join(@translations[:joiner])
end
def shuffle_last_poster_to_back_in(summary)
unless last_poster_is_topic_creator?
summary.reject! { |u| u.id == topic.last_post_user_id }
summary << user_lookup[topic.last_post_user_id]
end
summary
end
def last_poster_is_topic_creator?
topic.user_id == topic.last_post_user_id
end
def sorted_top_posters
shuffle_last_poster_to_back_in top_posters
end
def top_posters
user_ids.map { |id| user_lookup[id] }.compact.uniq.take(5)
end
def user_ids
[topic.user_id, topic.last_post_user_id, *topic.featured_user_ids]
end
def recent_poster_user_ids
@recent_poster_user_ids ||= topic.featured_users.recent_user_ids
end
def user_lookup
@user_lookup ||= options[:user_lookup] || UserLookup.new(user_ids)
end
end