diff --git a/app/models/top_topic.rb b/app/models/top_topic.rb index 908e97797..29f206ea3 100644 --- a/app/models/top_topic.rb +++ b/app/models/top_topic.rb @@ -147,6 +147,15 @@ class TopTopic < ActiveRecord::Base def self.compute_top_score_for(period) + log_views_multiplier = SiteSetting.top_topics_formula_log_views_multiplier.to_f + log_views_multiplier = 2 if log_views_multiplier == 0 + + first_post_likes_multiplier = SiteSetting.top_topics_formula_first_post_likes_multiplier.to_f + first_post_likes_multiplier = 0.5 if first_post_likes_multiplier == 0 + + least_likes_per_post_multiplier = SiteSetting.top_topics_formula_least_likes_per_post_multiplier.to_f + least_likes_per_post_multiplier = 3 if least_likes_per_post_multiplier == 0 + if period == :all top_topics = "( SELECT t.like_count all_likes_count, @@ -167,11 +176,11 @@ class TopTopic < ActiveRecord::Base WITH top AS ( SELECT CASE WHEN #{time_filter} THEN 0 - ELSE log(GREATEST(#{period}_views_count, 1)) * 2 + - #{period}_op_likes_count * 0.5 + + ELSE log(GREATEST(#{period}_views_count, 1)) * #{log_views_multiplier} + + #{period}_op_likes_count * #{first_post_likes_multiplier} + CASE WHEN #{period}_likes_count > 0 AND #{period}_posts_count > 0 THEN - LEAST(#{period}_likes_count / #{period}_posts_count, 3) + LEAST(#{period}_likes_count / #{period}_posts_count, #{least_likes_per_post_multiplier}) ELSE 0 END + CASE WHEN topics.posts_count < 10 THEN diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index ce6753ec8..2e2066448 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -945,6 +945,10 @@ en: verbose_localization: "Show extended localization tips in the UI" previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours" + top_topics_formula_log_views_multiplier: "value of log views multiplier (n) in top topics formula: `log(views_count) * (n) + op_likes_count * 0.5 + LEAST(likes_count / posts_count, 3) + 10 + log(posts_count)`" + top_topics_formula_first_post_likes_multiplier: "value of first post likes multiplier (n) in top topics formula: `log(views_count) * 2 + op_likes_count * (n) + LEAST(likes_count / posts_count, 3) + 10 + log(posts_count)`" + top_topics_formula_least_likes_per_post_multiplier: "value of least likes per post multiplier (n) in top topics formula: `log(views_count) * 2 + op_likes_count * 0.5 + LEAST(likes_count / posts_count, (n)) + 10 + log(posts_count)`" + rate_limit_create_topic: "After creating a topic, users must wait (n) seconds before creating another topic." rate_limit_create_post: "After posting, users must wait (n) seconds before creating another post." rate_limit_new_user_create_topic: "After creating a topic, new users must wait (n) seconds before creating another topic." diff --git a/config/site_settings.yml b/config/site_settings.yml index d7e3ed876..6abeabf8f 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -803,6 +803,15 @@ developer: verbose_localization: default: false client: true + top_topics_formula_log_views_multiplier: + default: 2 + min: 0 + top_topics_formula_first_post_likes_multiplier: + default: 0.5 + min: 0 + top_topics_formula_least_likes_per_post_multiplier: + default: 3 + min: 0 migrate_to_new_scheme: hidden: true default: false diff --git a/spec/models/top_topic_spec.rb b/spec/models/top_topic_spec.rb index 3b90232f1..7c73a98b3 100644 --- a/spec/models/top_topic_spec.rb +++ b/spec/models/top_topic_spec.rb @@ -38,9 +38,108 @@ describe TopTopic do it "should have top topics" do expect(TopTopic.pluck(:topic_id)).to match_array([t1.id, t2.id]) end - end - end + describe "#compute_top_score_for" do + + let(:user) { Fabricate(:user) } + let(:coding_horror) { Fabricate(:coding_horror) } + + let!(:topic_1) { Fabricate(:topic, posts_count: 10, like_count: 28) } + let!(:t1_post_1) { Fabricate(:post, topic: topic_1, like_count: 28, post_number: 1)} + + let!(:topic_2) { Fabricate(:topic, posts_count: 10, like_count: 20) } + let!(:t2_post_1) { Fabricate(:post, topic: topic_2, like_count: 10, post_number: 1) } + let!(:t2_post_2) { Fabricate(:post, topic: topic_2, like_count: 10) } + + let!(:topic_3) { Fabricate(:topic, posts_count: 10) } + let!(:t3_post_1) { Fabricate(:post, topic_id: topic_3.id) } + let!(:t3_view_1) { TopicViewItem.add(topic_3.id, '127.0.0.1', user) } + let!(:t3_view_2) { TopicViewItem.add(topic_3.id, '127.0.0.2', coding_horror) } + + # Note: all topics has 10 posts so we can skip "0 - ((10 - topics.posts_count) / 20) * #{period}_op_likes_count" calculation + + it "should compute top score" do + # Default Formula: log(views_count) * {2} + op_likes_count * {0.5} + LEAST(likes_count / posts_count, {3}) + 10 + log(posts_count) + # + # topic_1 => 0 + 14 + 3 + 10 + 0 => 27 + # topic_2 => 0 + 5 + 3 + 10 + 0.301029995664 => 18.301029995664 + # topic_3 => 0.602059991328 + 0 + 0 + 10 + 0 => 10.602059991328 + + TopTopic.refresh! + top_topics = TopTopic.all + + expect(top_topics.where(topic_id: topic_1.id).pluck(:yearly_score).first).to eq(27) + expect(top_topics.where(topic_id: topic_2.id).pluck(:yearly_score).first).to eq(18.301029995664) + expect(top_topics.where(topic_id: topic_3.id).pluck(:yearly_score).first).to eq(10.602059991328) + + # when 'top_topics_formula_log_views_multiplier' setting is changed + SiteSetting.top_topics_formula_log_views_multiplier = 4 + SiteSetting.top_topics_formula_first_post_likes_multiplier = 0.5 # unchanged + SiteSetting.top_topics_formula_least_likes_per_post_multiplier = 3 # unchanged + + # New Formula: log(views_count) * {4} + op_likes_count * {0.5} + LEAST(likes_count / posts_count, {3}) + 10 + log(posts_count) + # + # topic_1 => 0 + 14 + 3 + 10 + 0 => 27 + # topic_2 => 0 + 5 + 3 + 10 + 0.301029995664 => 18.301029995664 + # topic_3 => 1.2041199826559 + 0 + 0 + 10 + 0 => 11.2041199826559 + + TopTopic.refresh! + top_topics = TopTopic.all + + expect(top_topics.where(topic_id: topic_1.id).pluck(:yearly_score).first).to eq(27) + expect(top_topics.where(topic_id: topic_2.id).pluck(:yearly_score).first).to eq(18.301029995664) + expect(top_topics.where(topic_id: topic_3.id).pluck(:yearly_score).first).to eq(11.2041199826559) + + # when 'top_topics_formula_first_post_likes_multiplier' setting is changed + SiteSetting.top_topics_formula_log_views_multiplier = 2 # unchanged + SiteSetting.top_topics_formula_first_post_likes_multiplier = 2 + SiteSetting.top_topics_formula_least_likes_per_post_multiplier = 3 # unchanged + + # New Formula: log(views_count) * {2} + op_likes_count * {2} + LEAST(likes_count / posts_count, {3}) + 10 + log(posts_count) + # + # topic_1 => 0 + 56 + 3 + 10 + 0 => 69 + # topic_2 => 0 + 20 + 3 + 10 + 0.301029995664 => 33.301029995664 + # topic_3 => 0.602059991328 + 0 + 0 + 10 + 0 => 10.602059991328 + + TopTopic.refresh! + top_topics = TopTopic.all + + expect(top_topics.where(topic_id: topic_1.id).pluck(:yearly_score).first).to eq(69) + expect(top_topics.where(topic_id: topic_2.id).pluck(:yearly_score).first).to eq(33.301029995664) + expect(top_topics.where(topic_id: topic_3.id).pluck(:yearly_score).first).to eq(10.602059991328) + + # when 'top_topics_formula_least_likes_per_post_multiplier' setting is changed + SiteSetting.top_topics_formula_log_views_multiplier = 2 # unchanged + SiteSetting.top_topics_formula_first_post_likes_multiplier = 0.5 # unchanged + SiteSetting.top_topics_formula_least_likes_per_post_multiplier = 6 + + # New Formula: log(views_count) * {2} + op_likes_count * {0.5} + LEAST(likes_count / posts_count, {6}) + 10 + log(posts_count) + # + # topic_1 => 0 + 14 + 6 + 10 + 0 => 30 + # topic_2 => 0 + 5 + 6 + 10 + 0.301029995664 => 21.301029995664 + # topic_3 => 0.602059991328 + 0 + 0 + 10 + 0 => 10.602059991328 + + TopTopic.refresh! + top_topics = TopTopic.all + + expect(top_topics.where(topic_id: topic_1.id).pluck(:yearly_score).first).to eq(30) + expect(top_topics.where(topic_id: topic_2.id).pluck(:yearly_score).first).to eq(21.301029995664) + expect(top_topics.where(topic_id: topic_3.id).pluck(:yearly_score).first).to eq(10.602059991328) + + # handles invalid string value + SiteSetting.top_topics_formula_log_views_multiplier = "not good" + SiteSetting.top_topics_formula_first_post_likes_multiplier = "not good" + SiteSetting.top_topics_formula_least_likes_per_post_multiplier = "not good" + + TopTopic.refresh! + top_topics = TopTopic.all + + expect(top_topics.where(topic_id: topic_1.id).pluck(:yearly_score).first).to eq(27) + expect(top_topics.where(topic_id: topic_2.id).pluck(:yearly_score).first).to eq(18.301029995664) + expect(top_topics.where(topic_id: topic_3.id).pluck(:yearly_score).first).to eq(10.602059991328) + + end + end end