The "Best Of" mode uses a percentage ranking of posts.

This commit is contained in:
Robin Ward 2013-03-22 15:43:57 -04:00
parent ab412dd8b4
commit 9c38c13ac5
8 changed files with 49 additions and 20 deletions

View File

@ -157,7 +157,7 @@ class Post < ActiveRecord::Base
end end
def self.best_of def self.best_of
where("(post_number = 1) or (score >= ?)", SiteSetting.best_of_score_threshold) where(["(post_number = 1) or (percent_rank <= ?)", SiteSetting.best_of_percent_filter.to_f / 100.0])
end end
def update_flagged_posts_count def update_flagged_posts_count

View File

@ -106,6 +106,7 @@ class SiteSetting < ActiveRecord::Base
setting(:best_of_score_threshold, 15) setting(:best_of_score_threshold, 15)
setting(:best_of_posts_required, 50) setting(:best_of_posts_required, 50)
setting(:best_of_likes_required, 1) setting(:best_of_likes_required, 1)
setting(:best_of_percent_filter, 5)
# we need to think of a way to force users to enter certain settings, this is a minimal config thing # we need to think of a way to force users to enter certain settings, this is a minimal config thing
setting(:notification_email, 'info@discourse.org') setting(:notification_email, 'info@discourse.org')

View File

@ -329,6 +329,7 @@ en:
best_of_score_threshold: "The minimum score of a post to be included in the 'best of'" best_of_score_threshold: "The minimum score of a post to be included in the 'best of'"
best_of_posts_required: "Minimum posts in a topic before 'best of' mode is enabled" best_of_posts_required: "Minimum posts in a topic before 'best of' mode is enabled"
best_of_likes_required: "Minimum likes in a topic before the 'best of' mode will be enabled" best_of_likes_required: "Minimum likes in a topic before the 'best of' mode will be enabled"
best_of_percent_filter: "When a user clicks best of, show the top % of posts"
enable_private_messages: "Allow trust level 1 users to create private messages and conversations" enable_private_messages: "Allow trust level 1 users to create private messages and conversations"
enable_long_polling: "Message bus used for notification can use long polling" enable_long_polling: "Message bus used for notification can use long polling"

View File

@ -171,7 +171,7 @@ Discourse::Application.routes.draw do
get 'threads/:topic_id/:post_number/avatar' => 'topics#avatar', :constraints => {:topic_id => /\d+/, :post_number => /\d+/} get 'threads/:topic_id/:post_number/avatar' => 'topics#avatar', :constraints => {:topic_id => /\d+/, :post_number => /\d+/}
# Topic routes # Topic routes
get 't/:slug/:topic_id/best_of' => 'topics#show', :constraints => {:topic_id => /\d+/, :post_number => /\d+/} get 't/:slug/:topic_id/best_of' => 'topics#show', :defaults => {best_of: true}, :constraints => {:topic_id => /\d+/, :post_number => /\d+/}
get 't/:topic_id/best_of' => 'topics#show', :constraints => {:topic_id => /\d+/, :post_number => /\d+/} get 't/:topic_id/best_of' => 'topics#show', :constraints => {:topic_id => /\d+/, :post_number => /\d+/}
put 't/:slug/:topic_id' => 'topics#update', :constraints => {:topic_id => /\d+/} put 't/:slug/:topic_id' => 'topics#update', :constraints => {:topic_id => /\d+/}
put 't/:slug/:topic_id/star' => 'topics#star', :constraints => {:topic_id => /\d+/} put 't/:slug/:topic_id/star' => 'topics#star', :constraints => {:topic_id => /\d+/}

View File

@ -0,0 +1,12 @@
class AddPercentRankToPosts < ActiveRecord::Migration
def change
add_column :posts, :percent_rank, :float, default: 1.0
execute "UPDATE posts SET percent_rank = x.percent_rank
FROM (SELECT id, percent_rank()
OVER (PARTITION BY topic_id ORDER BY SCORE DESC)
FROM posts) AS x
WHERE x.id = posts.id"
end
end

View File

@ -21,6 +21,15 @@ class ScoreCalculator
# First update the scores of the posts # First update the scores of the posts
exec_sql(post_score_sql, @weightings) exec_sql(post_score_sql, @weightings)
# Update the percent rankings of the posts
exec_sql("UPDATE posts SET percent_rank = x.percent_rank
FROM (SELECT id, percent_rank()
OVER (PARTITION BY topic_id ORDER BY SCORE DESC) as percent_rank
FROM posts) AS x
WHERE x.id = posts.id")
# Update the best of flag # Update the best of flag
exec_sql " exec_sql "
UPDATE topics SET has_best_of = UPDATE topics SET has_best_of =
@ -41,7 +50,7 @@ class ScoreCalculator
private private
def exec_sql(sql, params) def exec_sql(sql, params=nil)
ActiveRecord::Base.exec_sql(sql, params) ActiveRecord::Base.exec_sql(sql, params)
end end

View File

@ -3,19 +3,25 @@ require 'score_calculator'
describe ScoreCalculator do describe ScoreCalculator do
before do let!(:post) { Fabricate(:post, reads: 111) }
@post = Fabricate(:post, reads: 111) let!(:another_post) { Fabricate(:post, topic: post.topic, reads: 222) }
@topic = @post.topic let(:topic) { post.topic }
end
context 'with weightings' do context 'with weightings' do
before do before do
ScoreCalculator.new(reads: 3).calculate ScoreCalculator.new(reads: 3).calculate
@post.reload post.reload
another_post.reload
end end
it 'takes the supplied weightings into effect' do it 'takes the supplied weightings into effect' do
@post.score.should == 333 post.score.should == 333
another_post.score.should == 666
end
it "creates the percent_ranks" do
another_post.percent_rank.should == 0.0
post.percent_rank.should == 1.0
end end
end end
@ -23,15 +29,15 @@ describe ScoreCalculator do
it "won't update the site settings when the site settings don't match" do it "won't update the site settings when the site settings don't match" do
ScoreCalculator.new(reads: 3).calculate ScoreCalculator.new(reads: 3).calculate
@topic.reload topic.reload
@topic.has_best_of.should be_false topic.has_best_of.should be_false
end end
it "removes the best_of flag if the topic no longer qualifies" do it "removes the best_of flag if the topic no longer qualifies" do
@topic.update_column(:has_best_of, true) topic.update_column(:has_best_of, true)
ScoreCalculator.new(reads: 3).calculate ScoreCalculator.new(reads: 3).calculate
@topic.reload topic.reload
@topic.has_best_of.should be_false topic.has_best_of.should be_false
end end
it "won't update the site settings when the site settings don't match" do it "won't update the site settings when the site settings don't match" do
@ -40,8 +46,8 @@ describe ScoreCalculator do
SiteSetting.expects(:best_of_score_threshold).returns(100) SiteSetting.expects(:best_of_score_threshold).returns(100)
ScoreCalculator.new(reads: 3).calculate ScoreCalculator.new(reads: 3).calculate
@topic.reload topic.reload
@topic.has_best_of.should be_true topic.has_best_of.should be_true
end end
end end

View File

@ -628,12 +628,12 @@ describe Post do
end end
context 'best_of' do context 'best_of' do
let!(:p1) { Fabricate(:post, post_args.merge(score: 4)) } let!(:p1) { Fabricate(:post, post_args.merge(score: 4, percent_rank: 0.33)) }
let!(:p2) { Fabricate(:post, post_args.merge(score: 10)) } let!(:p2) { Fabricate(:post, post_args.merge(score: 10, percent_rank: 0.66)) }
let!(:p3) { Fabricate(:post, post_args.merge(score: 5)) } let!(:p3) { Fabricate(:post, post_args.merge(score: 5, percent_rank: 0.99)) }
it "returns the OP and posts above the threshold in best of mode" do it "returns the OP and posts above the threshold in best of mode" do
SiteSetting.stubs(:best_of_score_threshold).returns(10) SiteSetting.stubs(:best_of_percent_filter).returns(66)
Post.best_of.order(:post_number).should == [p1, p2] Post.best_of.order(:post_number).should == [p1, p2]
end end