mirror of
https://github.com/discourse/discourse.git
synced 2026-03-03 23:54:20 +08:00
Unlisted topics (visible=false) should not appear in hot topic rankings. This change: - Filters out unlisted topics when updating hot scores - Excludes unlisted topics from hottest_topic_ids cache - Applies 50% score reduction to closed topics - Removes unlisted topics from index
254 lines
8.2 KiB
Ruby
254 lines
8.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe TopicHotScore do
|
|
describe ".update_scores" do
|
|
fab!(:user)
|
|
fab!(:user2, :user)
|
|
fab!(:user3, :user)
|
|
|
|
after { Discourse.cache.delete(described_class::CACHE_KEY) }
|
|
|
|
it "also always updates based on recent activity" do
|
|
freeze_time
|
|
|
|
# this will come in with a score
|
|
topic = Fabricate(:topic, created_at: 1.hour.ago, bumped_at: 2.minutes.ago)
|
|
post = Fabricate(:post, topic: topic, created_at: 2.minutes.ago)
|
|
PostActionCreator.like(user, post)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
# this will come in in the batch in score 0
|
|
topic = Fabricate(:topic, created_at: 1.minute.ago, bumped_at: 1.minute.ago)
|
|
post = Fabricate(:post, topic: topic, created_at: 1.minute.ago)
|
|
PostActionCreator.like(user, post)
|
|
|
|
# batch size is 1 so if we do not do something special we only update
|
|
# the high score topic and skip new
|
|
TopicHotScore.update_scores(1)
|
|
|
|
expect(TopicHotScore.find_by(topic_id: topic.id).score).to be_within(0.001).of(0.861)
|
|
end
|
|
|
|
it "can correctly update like counts and post counts and account for activity" do
|
|
freeze_time_safe
|
|
|
|
TopicHotScore.create!(topic_id: -1, score: 0.0, recent_likes: 99, recent_posters: 0)
|
|
|
|
old_post = Fabricate(:post, created_at: 10.months.ago)
|
|
topic = old_post.topic
|
|
|
|
new_reply = Fabricate(:post, user: user, topic: topic, created_at: 4.hours.ago)
|
|
newer_reply = Fabricate(:post, user: user2, topic: topic, created_at: 1.hour.ago)
|
|
Fabricate(:post, user: user2, topic: topic, created_at: 1.minute.ago)
|
|
|
|
freeze_time(1.year.ago)
|
|
PostActionCreator.like(user, old_post)
|
|
freeze_time(1.year.from_now)
|
|
|
|
PostActionCreator.like(user2, new_reply)
|
|
PostActionCreator.like(user, newer_reply)
|
|
|
|
# user 3 likes two posts, but we should only count 1
|
|
# this avoids a single user from trivially inflating hot scores
|
|
PostActionCreator.like(user3, new_reply)
|
|
PostActionCreator.like(user3, newer_reply)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
hot_scoring = TopicHotScore.find_by(topic_id: topic.id)
|
|
|
|
expect(hot_scoring.recent_posters).to eq(2)
|
|
expect(hot_scoring.recent_likes).to eq(3)
|
|
expect(hot_scoring.recent_first_bumped_at).to eq_time(new_reply.created_at)
|
|
expect(hot_scoring.score).to be_within(0.001).of(1.771)
|
|
|
|
expect(TopicHotScore.find_by(topic_id: -1).recent_likes).to eq(0)
|
|
|
|
# make sure we exclude whispers, deleted posts, small posts, etc
|
|
whisper =
|
|
Fabricate(:post, topic: topic, created_at: 1.hour.ago, post_type: Post.types[:whisper])
|
|
PostActionCreator.like(Fabricate(:admin), whisper)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
hot_scoring = TopicHotScore.find_by(topic_id: topic.id)
|
|
|
|
expect(hot_scoring.recent_posters).to eq(2)
|
|
expect(hot_scoring.recent_likes).to eq(3)
|
|
end
|
|
|
|
it "prefers recent_likes to topic like count for recent topics" do
|
|
freeze_time
|
|
|
|
topic = Fabricate(:topic, created_at: 1.hour.ago)
|
|
post = Fabricate(:post, topic: topic, created_at: 1.minute.ago)
|
|
PostActionCreator.like(user, post)
|
|
|
|
TopicHotScore.update_scores
|
|
score = TopicHotScore.find_by(topic_id: topic.id).score
|
|
|
|
topic.update!(like_count: 100)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
expect(TopicHotScore.find_by(topic_id: topic.id).score).to be_within(0.001).of(score)
|
|
end
|
|
|
|
it "can correctly set scores for topics" do
|
|
freeze_time
|
|
|
|
topic1 = Fabricate(:topic, like_count: 3, created_at: 2.weeks.ago)
|
|
topic2 = Fabricate(:topic, like_count: 10, created_at: 2.weeks.ago)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
expect(TopicHotScore.find_by(topic_id: topic1.id).score).to be_within(0.001).of(0.002)
|
|
expect(TopicHotScore.find_by(topic_id: topic2.id).score).to be_within(0.001).of(0.009)
|
|
|
|
freeze_time(6.weeks.from_now)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
expect(TopicHotScore.find_by(topic_id: topic1.id).score).to be_within(0.0001).of(0.0005)
|
|
expect(TopicHotScore.find_by(topic_id: topic2.id).score).to be_within(0.001).of(0.001)
|
|
end
|
|
|
|
it "caches 10% of the hottest topic IDs" do
|
|
freeze_time
|
|
|
|
9.times do
|
|
Fabricate(:topic, like_count: 3, created_at: 2.weeks.ago, last_posted_at: 10.minutes.ago)
|
|
end
|
|
hottest_topic =
|
|
Fabricate(:topic, like_count: 10, created_at: 2.weeks.ago, last_posted_at: 10.minutes.ago)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
expect(TopicHotScore.hottest_topic_ids).to contain_exactly(hottest_topic.id)
|
|
|
|
hottest_topic_2 =
|
|
Fabricate(:topic, like_count: 100, created_at: 2.weeks.ago, last_posted_at: 10.minutes.ago)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
expect(TopicHotScore.hottest_topic_ids).to contain_exactly(hottest_topic_2.id)
|
|
end
|
|
|
|
it "ignores topics in the future" do
|
|
freeze_time
|
|
|
|
topic1 = Fabricate(:topic, like_count: 3, created_at: 2.days.from_now)
|
|
post1 = Fabricate(:post, topic: topic1, created_at: 1.minute.ago)
|
|
PostActionCreator.like(user, post1)
|
|
TopicHotScore.create!(topic_id: topic1.id, score: 0.0, recent_likes: 0, recent_posters: 0)
|
|
|
|
expect { TopicHotScore.update_scores }.not_to change {
|
|
TopicHotScore.where(topic_id: topic1.id).pluck(:recent_likes)
|
|
}
|
|
end
|
|
|
|
it "triggers an event after updating" do
|
|
triggered = false
|
|
blk = Proc.new { triggered = true }
|
|
|
|
begin
|
|
DiscourseEvent.on(:topic_hot_scores_updated, &blk)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
expect(triggered).to eq(true)
|
|
ensure
|
|
DiscourseEvent.off(:topic_hot_scores_updated, &blk)
|
|
end
|
|
end
|
|
|
|
it "excludes category description topics from hot topics list" do
|
|
freeze_time
|
|
|
|
category = Fabricate(:category_with_definition)
|
|
category_topic = category.topic
|
|
category_topic.update!(
|
|
like_count: 100,
|
|
created_at: 2.weeks.ago,
|
|
last_posted_at: 10.minutes.ago,
|
|
)
|
|
|
|
9.times do
|
|
Fabricate(:topic, like_count: 3, created_at: 2.weeks.ago, last_posted_at: 10.minutes.ago)
|
|
end
|
|
|
|
regular_topic =
|
|
Fabricate(:topic, like_count: 10, created_at: 2.weeks.ago, last_posted_at: 10.minutes.ago)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
category_score = TopicHotScore.find_by(topic_id: category_topic.id)
|
|
regular_score = TopicHotScore.find_by(topic_id: regular_topic.id)
|
|
|
|
expect(category_score).to be_present
|
|
expect(category_score.score).to be > regular_score.score
|
|
|
|
hottest_ids = TopicHotScore.hottest_topic_ids
|
|
|
|
expect(hottest_ids).not_to include(category_topic.id)
|
|
end
|
|
|
|
it "applies faster decay to closed topics" do
|
|
freeze_time
|
|
|
|
open_topic = Fabricate(:topic, like_count: 10, created_at: 2.weeks.ago)
|
|
closed_topic = Fabricate(:topic, like_count: 10, created_at: 2.weeks.ago, closed: true)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
open_score = TopicHotScore.find_by(topic_id: open_topic.id).score
|
|
closed_score = TopicHotScore.find_by(topic_id: closed_topic.id).score
|
|
|
|
expect(closed_score).to be < open_score
|
|
expect(closed_score).to be_within(0.0001).of(open_score * 0.5)
|
|
end
|
|
|
|
it "does not create hot scores for unlisted topics" do
|
|
freeze_time
|
|
|
|
unlisted_topic = Fabricate(:topic, visible: false, like_count: 10, created_at: 1.day.ago)
|
|
|
|
TopicHotScore.update_scores
|
|
|
|
expect(TopicHotScore.find_by(topic_id: unlisted_topic.id)).to be_nil
|
|
end
|
|
|
|
it "excludes unlisted topics from hottest_topic_ids cache" do
|
|
freeze_time
|
|
|
|
unlisted_topic =
|
|
Fabricate(
|
|
:topic,
|
|
visible: false,
|
|
like_count: 100,
|
|
created_at: 1.day.ago,
|
|
last_posted_at: 10.minutes.ago,
|
|
)
|
|
TopicHotScore.create!(topic_id: unlisted_topic.id, score: 999.0)
|
|
|
|
TopicHotScore.recreate_hottest_topic_ids_cache
|
|
|
|
expect(TopicHotScore.hottest_topic_ids).not_to include(unlisted_topic.id)
|
|
end
|
|
|
|
it "sets score to 0 for unlisted topics during update" do
|
|
freeze_time
|
|
|
|
topic = Fabricate(:topic, like_count: 10, created_at: 2.weeks.ago)
|
|
TopicHotScore.update_scores
|
|
|
|
expect(TopicHotScore.find_by(topic_id: topic.id).score).to be > 0
|
|
|
|
topic.update!(visible: false)
|
|
TopicHotScore.update_scores
|
|
|
|
expect(TopicHotScore.find_by(topic_id: topic.id).score).to eq(0)
|
|
end
|
|
end
|
|
end
|