mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: improvements to hot algorithm (#25295)
- Decrease gravity, we come in too hot prioritizing too many new topics - Remove all muted topics / categories and tags from the hot list - Punish topics with zero likes in algorithm
This commit is contained in:
@@ -100,11 +100,11 @@ class TopicHotScore < ActiveRecord::Base
|
|||||||
# we need an extra index for this
|
# we need an extra index for this
|
||||||
DB.exec(<<~SQL, args)
|
DB.exec(<<~SQL, args)
|
||||||
UPDATE topic_hot_scores ths
|
UPDATE topic_hot_scores ths
|
||||||
SET score = topics.like_count /
|
SET score = (topics.like_count - 1) /
|
||||||
(EXTRACT(EPOCH FROM (:now - topics.created_at)) / 3600 + 2) ^ :gravity
|
(EXTRACT(EPOCH FROM (:now - topics.created_at)) / 3600 + 2) ^ :gravity
|
||||||
+
|
+
|
||||||
CASE WHEN ths.recent_first_bumped_at IS NULL THEN 0 ELSE
|
CASE WHEN ths.recent_first_bumped_at IS NULL THEN 0 ELSE
|
||||||
(ths.recent_likes + ths.recent_posters) /
|
(ths.recent_likes + ths.recent_posters - 1) /
|
||||||
(EXTRACT(EPOCH FROM (:now - recent_first_bumped_at)) / 3600 + 2) ^ :gravity
|
(EXTRACT(EPOCH FROM (:now - recent_first_bumped_at)) / 3600 + 2) ^ :gravity
|
||||||
END
|
END
|
||||||
,
|
,
|
||||||
|
|||||||
@@ -3116,7 +3116,7 @@ dashboard:
|
|||||||
default: false
|
default: false
|
||||||
hot_topics_gravity:
|
hot_topics_gravity:
|
||||||
hidden: true
|
hidden: true
|
||||||
default: 1.8
|
default: 1.2
|
||||||
hot_topics_recent_days:
|
hot_topics_recent_days:
|
||||||
hidden: true
|
hidden: true
|
||||||
default: 7
|
default: 7
|
||||||
|
|||||||
@@ -340,6 +340,9 @@ class TopicQuery
|
|||||||
|
|
||||||
def list_hot
|
def list_hot
|
||||||
create_list(:hot, unordered: true) do |topics|
|
create_list(:hot, unordered: true) do |topics|
|
||||||
|
topics = remove_muted_topics(topics, user)
|
||||||
|
topics = remove_muted_categories(topics, user, exclude: options[:category])
|
||||||
|
TopicQuery.remove_muted_tags(topics, user, options)
|
||||||
topics.joins("JOIN topic_hot_scores on topics.id = topic_hot_scores.topic_id").order(
|
topics.joins("JOIN topic_hot_scores on topics.id = topic_hot_scores.topic_id").order(
|
||||||
"topic_hot_scores.score DESC",
|
"topic_hot_scores.score DESC",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -75,6 +75,36 @@ RSpec.describe TopicQuery do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#list_hot" do
|
||||||
|
it "excludes muted categories and topics" do
|
||||||
|
muted_category = Fabricate(:category)
|
||||||
|
muted_topic = Fabricate(:topic, category: muted_category)
|
||||||
|
|
||||||
|
TopicHotScore.create!(topic_id: muted_topic.id, score: 1.0)
|
||||||
|
|
||||||
|
expect(TopicQuery.new(user).list_hot.topics.map(&:id)).to include(muted_topic.id)
|
||||||
|
|
||||||
|
tu =
|
||||||
|
TopicUser.create!(
|
||||||
|
user_id: user.id,
|
||||||
|
topic_id: muted_topic.id,
|
||||||
|
notification_level: TopicUser.notification_levels[:muted],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(TopicQuery.new(user).list_hot.topics.map(&:id)).not_to include(muted_topic.id)
|
||||||
|
|
||||||
|
tu.destroy!
|
||||||
|
|
||||||
|
CategoryUser.create!(
|
||||||
|
user_id: user.id,
|
||||||
|
category_id: muted_category.id,
|
||||||
|
notification_level: CategoryUser.notification_levels[:muted],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(TopicQuery.new(user).list_hot.topics.map(&:id)).not_to include(muted_topic.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#prioritize_pinned_topics" do
|
describe "#prioritize_pinned_topics" do
|
||||||
it "does the pagination correctly" do
|
it "does the pagination correctly" do
|
||||||
num_topics = 15
|
num_topics = 15
|
||||||
@@ -245,7 +275,7 @@ RSpec.describe TopicQuery do
|
|||||||
group = Fabricate(:group)
|
group = Fabricate(:group)
|
||||||
group.add(group_moderator)
|
group.add(group_moderator)
|
||||||
category = Fabricate(:category, reviewable_by_group: group)
|
category = Fabricate(:category, reviewable_by_group: group)
|
||||||
topic = Fabricate(:topic, category: category, deleted_at: 1.year.ago)
|
_topic = Fabricate(:topic, category: category, deleted_at: 1.year.ago)
|
||||||
|
|
||||||
expect(TopicQuery.new(admin, status: "deleted").list_latest.topics.size).to eq(1)
|
expect(TopicQuery.new(admin, status: "deleted").list_latest.topics.size).to eq(1)
|
||||||
expect(TopicQuery.new(moderator, status: "deleted").list_latest.topics.size).to eq(1)
|
expect(TopicQuery.new(moderator, status: "deleted").list_latest.topics.size).to eq(1)
|
||||||
@@ -265,7 +295,7 @@ RSpec.describe TopicQuery do
|
|||||||
it "includes users own pms in regular topic lists" do
|
it "includes users own pms in regular topic lists" do
|
||||||
topic = Fabricate(:topic)
|
topic = Fabricate(:topic)
|
||||||
own_pm = Fabricate(:private_message_topic, user: user)
|
own_pm = Fabricate(:private_message_topic, user: user)
|
||||||
other_pm = Fabricate(:private_message_topic, user: Fabricate(:user))
|
_other_pm = Fabricate(:private_message_topic, user: Fabricate(:user))
|
||||||
|
|
||||||
expect(TopicQuery.new(user).list_latest.topics).to contain_exactly(topic)
|
expect(TopicQuery.new(user).list_latest.topics).to contain_exactly(topic)
|
||||||
expect(TopicQuery.new(admin).list_latest.topics).to contain_exactly(topic)
|
expect(TopicQuery.new(admin).list_latest.topics).to contain_exactly(topic)
|
||||||
@@ -1466,8 +1496,8 @@ RSpec.describe TopicQuery do
|
|||||||
it "should return random topics excluding topics that are muted by user and not older than `suggested_topics_max_days_old` site setting" do
|
it "should return random topics excluding topics that are muted by user and not older than `suggested_topics_max_days_old` site setting" do
|
||||||
topic2 = Fabricate(:topic, user: user)
|
topic2 = Fabricate(:topic, user: user)
|
||||||
topic3 = Fabricate(:topic, user: user)
|
topic3 = Fabricate(:topic, user: user)
|
||||||
topic4 = Fabricate(:topic, user: user, created_at: 8.days.ago)
|
_topic4 = Fabricate(:topic, user: user, created_at: 8.days.ago)
|
||||||
topic5 = Fabricate(:topic).tap { |t| TopicNotifier.new(t).mute!(user) }
|
_topic5 = Fabricate(:topic).tap { |t| TopicNotifier.new(t).mute!(user) }
|
||||||
|
|
||||||
SiteSetting.suggested_topics_max_days_old = 7
|
SiteSetting.suggested_topics_max_days_old = 7
|
||||||
|
|
||||||
@@ -1483,7 +1513,7 @@ RSpec.describe TopicQuery do
|
|||||||
|
|
||||||
fab!(:topic_in_category_that_user_created_and_has_partially_read) do
|
fab!(:topic_in_category_that_user_created_and_has_partially_read) do
|
||||||
Fabricate(:topic, user: user, category:).tap do |t|
|
Fabricate(:topic, user: user, category:).tap do |t|
|
||||||
first_post = Fabricate(:post, topic: t)
|
_first_post = Fabricate(:post, topic: t)
|
||||||
second_post = Fabricate(:post, topic: t)
|
second_post = Fabricate(:post, topic: t)
|
||||||
|
|
||||||
TopicUser.change(
|
TopicUser.change(
|
||||||
@@ -1498,7 +1528,7 @@ RSpec.describe TopicQuery do
|
|||||||
|
|
||||||
fab!(:topic_in_category2_that_user_created_and_has_partially_read) do
|
fab!(:topic_in_category2_that_user_created_and_has_partially_read) do
|
||||||
Fabricate(:topic, user: user, category: category2).tap do |t|
|
Fabricate(:topic, user: user, category: category2).tap do |t|
|
||||||
first_post = Fabricate(:post, topic: t)
|
_first_post = Fabricate(:post, topic: t)
|
||||||
second_post = Fabricate(:post, topic: t)
|
second_post = Fabricate(:post, topic: t)
|
||||||
|
|
||||||
TopicUser.change(
|
TopicUser.change(
|
||||||
@@ -1513,7 +1543,7 @@ RSpec.describe TopicQuery do
|
|||||||
|
|
||||||
fab!(:topic_in_category_that_user_has_partially_read) do
|
fab!(:topic_in_category_that_user_has_partially_read) do
|
||||||
Fabricate(:topic, category:).tap do |t|
|
Fabricate(:topic, category:).tap do |t|
|
||||||
first_post = Fabricate(:post, topic: t)
|
_first_post = Fabricate(:post, topic: t)
|
||||||
second_post = Fabricate(:post, topic: t)
|
second_post = Fabricate(:post, topic: t)
|
||||||
|
|
||||||
TopicUser.change(
|
TopicUser.change(
|
||||||
@@ -1528,7 +1558,7 @@ RSpec.describe TopicQuery do
|
|||||||
|
|
||||||
fab!(:topic_in_category2_that_user_has_partially_read) do
|
fab!(:topic_in_category2_that_user_has_partially_read) do
|
||||||
Fabricate(:topic, category: category2).tap do |t|
|
Fabricate(:topic, category: category2).tap do |t|
|
||||||
first_post = Fabricate(:post, topic: t)
|
_first_post = Fabricate(:post, topic: t)
|
||||||
second_post = Fabricate(:post, topic: t)
|
second_post = Fabricate(:post, topic: t)
|
||||||
|
|
||||||
TopicUser.change(
|
TopicUser.change(
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ RSpec.describe TopicHotScore do
|
|||||||
expect(hot_scoring.recent_likes).to eq(2)
|
expect(hot_scoring.recent_likes).to eq(2)
|
||||||
expect(hot_scoring.recent_posters).to eq(2)
|
expect(hot_scoring.recent_posters).to eq(2)
|
||||||
expect(hot_scoring.recent_first_bumped_at).to eq_time(new_reply.created_at)
|
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.020)
|
expect(hot_scoring.score).to be_within(0.001).of(1.219)
|
||||||
|
|
||||||
expect(TopicHotScore.find_by(topic_id: -1).recent_likes).to eq(0)
|
expect(TopicHotScore.find_by(topic_id: -1).recent_likes).to eq(0)
|
||||||
end
|
end
|
||||||
@@ -44,15 +44,15 @@ RSpec.describe TopicHotScore do
|
|||||||
|
|
||||||
TopicHotScore.update_scores
|
TopicHotScore.update_scores
|
||||||
|
|
||||||
expect(TopicHotScore.find_by(topic_id: topic1.id).score).to be_within(0.001).of(0.415)
|
expect(TopicHotScore.find_by(topic_id: topic1.id).score).to be_within(0.001).of(0.535)
|
||||||
expect(TopicHotScore.find_by(topic_id: topic2.id).score).to be_within(0.001).of(0.551)
|
expect(TopicHotScore.find_by(topic_id: topic2.id).score).to be_within(0.001).of(1.304)
|
||||||
|
|
||||||
freeze_time(2.hours.from_now)
|
freeze_time(2.hours.from_now)
|
||||||
|
|
||||||
TopicHotScore.update_scores
|
TopicHotScore.update_scores
|
||||||
|
|
||||||
expect(TopicHotScore.find_by(topic_id: topic1.id).score).to be_within(0.001).of(0.165)
|
expect(TopicHotScore.find_by(topic_id: topic1.id).score).to be_within(0.001).of(0.289)
|
||||||
expect(TopicHotScore.find_by(topic_id: topic2.id).score).to be_within(0.001).of(0.301)
|
expect(TopicHotScore.find_by(topic_id: topic2.id).score).to be_within(0.001).of(0.871)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user