diff --git a/app/assets/javascripts/discourse/controllers/list_topics_controller.js b/app/assets/javascripts/discourse/controllers/list_topics_controller.js
index 8d666f67f04..f7ad3045918 100644
--- a/app/assets/javascripts/discourse/controllers/list_topics_controller.js
+++ b/app/assets/javascripts/discourse/controllers/list_topics_controller.js
@@ -7,7 +7,8 @@
@module Discourse
**/
Discourse.ListTopicsController = Discourse.ObjectController.extend({
- needs: ['list', 'composer'],
+ needs: ['list', 'composer', 'modal'],
+
// If we're changing our channel
previousChannel: null,
@@ -50,6 +51,14 @@ Discourse.ListTopicsController = Discourse.ObjectController.extend({
topic.toggleStar();
},
+ // Show rank details
+ showRankDetails: function(topic) {
+ var modalController = this.get('controllers.modal');
+ if (modalController) {
+ modalController.show(Discourse.TopicRankDetailsView.create({ topic: topic }));
+ }
+ },
+
createTopic: function() {
this.get('controllers.list').createTopic();
},
diff --git a/app/assets/javascripts/discourse/helpers/application_helpers.js b/app/assets/javascripts/discourse/helpers/application_helpers.js
index fcc9857d4e3..866cdf5a17d 100644
--- a/app/assets/javascripts/discourse/helpers/application_helpers.js
+++ b/app/assets/javascripts/discourse/helpers/application_helpers.js
@@ -180,6 +180,30 @@ Handlebars.registerHelper('editDate', function(property, options) {
}
});
+/**
+ Displays a percentile based on a `percent_rank` field
+
+ @method percentile
+ @for Ember.Handlebars
+**/
+Ember.Handlebars.registerHelper('percentile', function(property, options) {
+ var percentile = Ember.Handlebars.get(this, property, options);
+ return Math.round((1.0 - percentile) * 100)
+});
+
+/**
+ Displays a float nicely
+
+ @method float
+ @for Ember.Handlebars
+**/
+Ember.Handlebars.registerHelper('float', function(property, options) {
+ var x = Ember.Handlebars.get(this, property, options);
+ if (!x) return "0";
+ if (Math.round(x) === x) return x;
+ return x.toFixed(3)
+});
+
/**
Display logic for numbers.
diff --git a/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars b/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars
index b63c19e70d1..93bd48db894 100644
--- a/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars
@@ -17,7 +17,12 @@
{{#if unseen}}
{{/if}}
+
+ {{#if rank_details}}
+
+ {{/if}}
+
{{categoryLink category}}
|
diff --git a/app/assets/javascripts/discourse/templates/modal/topic_rank_details.js.handlebars b/app/assets/javascripts/discourse/templates/modal/topic_rank_details.js.handlebars
new file mode 100644
index 00000000000..411d3fb62be
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/modal/topic_rank_details.js.handlebars
@@ -0,0 +1,46 @@
+{{#with view.topic.rank_details}}
+
+
+
+
+
+
+ hot topic type |
+
+ {{hot_topic_type}}
+ |
+
+
+ random bias |
+ {{float random_bias}} |
+
+
+ random multiplier |
+ {{float random_multiplier}} |
+
+
+ days ago bias |
+ {{float days_ago_bias}} |
+
+
+ days ago multiplier |
+ {{float days_ago_multiplier}} |
+
+
+ ranking formula |
+
+ = (random_bias * random_multiplier) +
+ (days_ago_bias * days_ago_multiplier)
+ = ({{float random_bias}} * {{float random_multiplier}}) + ({{float days_ago_bias}} * {{float days_ago_multiplier}})
+ |
+
+
+ ranking score |
+ {{float ranking_score}} |
+
+
+
+
+
+{{/with}}
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/views/modal/topic_rank_details_view.js b/app/assets/javascripts/discourse/views/modal/topic_rank_details_view.js
new file mode 100644
index 00000000000..39095330989
--- /dev/null
+++ b/app/assets/javascripts/discourse/views/modal/topic_rank_details_view.js
@@ -0,0 +1,13 @@
+/**
+ A modal view for displaying the ranking details of a topic
+
+ @class TopicRankDetailsView
+ @extends Discourse.ModalBodyView
+ @namespace Discourse
+ @module Discourse
+**/
+Discourse.TopicRankDetailsView = Discourse.ModalBodyView.extend({
+ templateName: 'modal/topic_rank_details',
+ title: Em.String.i18n('rank_details.title')
+
+});
diff --git a/app/assets/stylesheets/application/modal.css.scss b/app/assets/stylesheets/application/modal.css.scss
index 0ec27a48205..3ce7872b037 100644
--- a/app/assets/stylesheets/application/modal.css.scss
+++ b/app/assets/stylesheets/application/modal.css.scss
@@ -151,6 +151,8 @@
.archetype-option {
margin-bottom: 20px;
}
+
+
}
.password-confirmation {
display: none;
diff --git a/app/assets/stylesheets/application/topic-list.css.scss b/app/assets/stylesheets/application/topic-list.css.scss
index 7c99dfc2a31..2bbb6fa998a 100755
--- a/app/assets/stylesheets/application/topic-list.css.scss
+++ b/app/assets/stylesheets/application/topic-list.css.scss
@@ -97,7 +97,20 @@
.main-link {
width: 515px;
font-size: 16px;
+
+ &:hover i.score {
+ display: inline-block;
+ }
+
+ i.score {
+ color: green;
+ cursor: pointer;
+ display: none;
+ }
}
+
+
+
@include medium-width {
.main-link {
width: 400px;
diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb
index 773e05c89cc..951c4e871ce 100644
--- a/app/controllers/list_controller.rb
+++ b/app/controllers/list_controller.rb
@@ -28,7 +28,6 @@ class ListController < ApplicationController
end
def category
-
query = TopicQuery.new(current_user, page: params[:page])
list = nil
diff --git a/app/models/hot_topic.rb b/app/models/hot_topic.rb
index 8a231ea62ce..c4ef81774a6 100644
--- a/app/models/hot_topic.rb
+++ b/app/models/hot_topic.rb
@@ -18,9 +18,23 @@ class HotTopic < ActiveRecord::Base
no_old_in_first_x_rows = 8 # don't show old results in the first x rows
# Include all sticky uncategorized on Hot
- exec_sql("INSERT INTO hot_topics (topic_id, score)
- SELECT t.id, RANDOM()
+ exec_sql("INSERT INTO hot_topics (topic_id,
+ random_bias,
+ random_multiplier,
+ days_ago_bias,
+ days_ago_multiplier,
+ score,
+ hot_topic_type)
+ SELECT t.id,
+ calc.random_bias,
+ 1.0,
+ 0,
+ 1.0,
+ calc.random_bias,
+ 1
FROM topics AS t
+ INNER JOIN (SELECT id, RANDOM() as random_bias
+ FROM topics) AS calc ON calc.id = t.id
WHERE t.deleted_at IS NULL
AND t.visible
AND (NOT t.archived)
@@ -28,12 +42,27 @@ class HotTopic < ActiveRecord::Base
AND t.category_id IS NULL")
# Include high percentile recent topics
- inserted_count = exec_sql("INSERT INTO hot_topics (topic_id, category_id, score)
+ inserted_count = exec_sql("INSERT INTO hot_topics (topic_id,
+ category_id,
+ random_bias,
+ random_multiplier,
+ days_ago_bias,
+ days_ago_multiplier,
+ score,
+ hot_topic_type)
SELECT t.id,
t.category_id,
- ((1.0 - (EXTRACT(EPOCH FROM CURRENT_TIMESTAMP-t.created_at)/86400) / :days_ago) * 0.95) +
- (RANDOM() * 0.05)
+ calc.random_bias,
+ 0.05,
+ calc.days_ago_bias,
+ 0.95,
+ (calc.random_bias * 0.05) + (days_ago_bias * 0.95),
+ 2
FROM topics AS t
+ INNER JOIN (SELECT id,
+ RANDOM() as random_bias,
+ ((1.0 - (EXTRACT(EPOCH FROM CURRENT_TIMESTAMP-created_at)/86400) / :days_ago) * 0.95) AS days_ago_bias
+ FROM topics) AS calc ON calc.id = t.id
WHERE t.deleted_at IS NULL
AND t.visible
AND (NOT t.closed)
@@ -56,16 +85,26 @@ class HotTopic < ActiveRecord::Base
max_old_score = HotTopic.order('score desc').limit(no_old_in_first_x_rows).last.score
end
-
-
-
-
# Add a sprinkling of random older topics
- exec_sql("INSERT INTO hot_topics (topic_id, category_id, score)
+ exec_sql("INSERT INTO hot_topics (topic_id,
+ category_id,
+ random_bias,
+ random_multiplier,
+ days_ago_bias,
+ days_ago_multiplier,
+ score,
+ hot_topic_type)
SELECT t.id,
t.category_id,
- RANDOM() * :max_old_score
+ calc.random_bias,
+ :max_old_score,
+ 0,
+ 1.0,
+ calc.random_bias * :max_old_score,
+ 3
FROM topics AS t
+ INNER JOIN (SELECT id, RANDOM() as random_bias
+ FROM topics) AS calc ON calc.id = t.id
WHERE t.deleted_at IS NULL
AND t.visible
AND (NOT t.closed)
diff --git a/app/models/topic.rb b/app/models/topic.rb
index adbc2c84cbb..af082c77126 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -55,6 +55,7 @@ class Topic < ActiveRecord::Base
# When we want to temporarily attach some data to a forum topic (usually before serialization)
attr_accessor :user_data
attr_accessor :posters # TODO: can replace with posters_summary once we remove old list code
+ attr_accessor :topic_list
# The regular order
diff --git a/app/models/topic_list.rb b/app/models/topic_list.rb
index c5f9d448ccb..4fc096d0ff1 100644
--- a/app/models/topic_list.rb
+++ b/app/models/topic_list.rb
@@ -3,9 +3,14 @@ require_dependency 'avatar_lookup'
class TopicList
include ActiveModel::Serialization
- attr_accessor :more_topics_url, :draft, :draft_key, :draft_sequence
+ attr_accessor :more_topics_url,
+ :draft,
+ :draft_key,
+ :draft_sequence,
+ :filter
- def initialize(current_user, topics)
+ def initialize(filter, current_user, topics)
+ @filter = filter
@current_user = current_user
@topics_input = topics
end
@@ -30,6 +35,7 @@ class TopicList
@topics.each do |ft|
ft.user_data = @topic_lookup[ft.id] if @topic_lookup.present?
ft.posters = ft.posters_summary(ft.user_data, @current_user, avatar_lookup: avatar_lookup)
+ ft.topic_list = self
end
return @topics
diff --git a/app/serializers/topic_list_item_serializer.rb b/app/serializers/topic_list_item_serializer.rb
index eeed912515f..e30f8e4e9d2 100644
--- a/app/serializers/topic_list_item_serializer.rb
+++ b/app/serializers/topic_list_item_serializer.rb
@@ -10,7 +10,8 @@ class TopicListItemSerializer < ListableTopicSerializer
:archived,
:starred,
:has_best_of,
- :archetype
+ :archetype,
+ :rank_details
has_one :category
has_many :posters, serializer: TopicPosterSerializer, embed: :objects
@@ -20,6 +21,35 @@ class TopicListItemSerializer < ListableTopicSerializer
end
alias :include_starred? :seen
+
+ # This is for debugging / tweaking the hot topic rankings.
+ # We will likely remove it after we are happier with things.
+ def rank_details
+
+ hot_topic_type = case object.hot_topic.hot_topic_type
+ when 1 then 'sticky'
+ when 2 then 'recent high scoring'
+ when 3 then 'old high scoring'
+ end
+
+ {topic_score: object.score,
+ percent_rank: object.percent_rank,
+ random_bias: object.hot_topic.random_bias,
+ random_multiplier: object.hot_topic.random_multiplier,
+ days_ago_bias: object.hot_topic.days_ago_bias,
+ days_ago_multiplier: object.hot_topic.days_ago_multiplier,
+ ranking_score: object.hot_topic.score,
+ hot_topic_type: hot_topic_type}
+ end
+
+ def include_rank_details?
+ return false unless object.topic_list.present?
+ return false unless scope.user.present?
+ return false unless scope.user.admin?
+
+ object.topic_list.filter == :hot
+ end
+
def posters
object.posters || []
end
@@ -28,4 +58,5 @@ class TopicListItemSerializer < ListableTopicSerializer
PinnedCheck.new(object, object.user_data).pinned?
end
+
end
diff --git a/app/serializers/topic_list_serializer.rb b/app/serializers/topic_list_serializer.rb
index a4c5cb39ea5..f08b4d373b4 100644
--- a/app/serializers/topic_list_serializer.rb
+++ b/app/serializers/topic_list_serializer.rb
@@ -1,6 +1,11 @@
class TopicListSerializer < ApplicationSerializer
- attributes :can_create_topic, :more_topics_url, :filter_summary, :draft, :draft_key, :draft_sequence
+ attributes :can_create_topic,
+ :more_topics_url,
+ :filter_summary,
+ :draft,
+ :draft_key,
+ :draft_sequence
has_many :topics, serializer: TopicListItemSerializer, embed: :objects
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index bde01d8efa4..4213783efc3 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -386,6 +386,10 @@ en:
favorited: "There are no more favorited topics to read."
category: "There are no more {{category}} topics."
+ rank_details:
+ show: show topic rank details
+ title: Topic Rank Details
+
topic:
create_in: 'Create {{categoryName}} Topic'
create: 'Create Topic'
@@ -407,6 +411,7 @@ en:
description: "Sorry, we couldn't find that topic. Perhaps it was removed by a moderator?"
unread_posts: "you have {{unread}} unread old posts in this topic"
new_posts: "there are {{new_posts}} new posts in this topic since you last read it"
+
likes:
one: "there is 1 like in this topic"
other: "there are {{count}} likes in this topic"
diff --git a/db/migrate/20130402210723_add_values_to_hot_topics.rb b/db/migrate/20130402210723_add_values_to_hot_topics.rb
new file mode 100644
index 00000000000..dab4598a6c6
--- /dev/null
+++ b/db/migrate/20130402210723_add_values_to_hot_topics.rb
@@ -0,0 +1,9 @@
+class AddValuesToHotTopics < ActiveRecord::Migration
+ def change
+ add_column :hot_topics, :random_bias, :float
+ add_column :hot_topics, :random_multiplier, :float
+ add_column :hot_topics, :days_ago_bias, :float
+ add_column :hot_topics, :days_ago_multiplier, :float
+ add_column :hot_topics, :hot_topic_type, :integer
+ end
+end
diff --git a/lib/topic_query.rb b/lib/topic_query.rb
index 9f69ff1126b..e091b99dbb3 100644
--- a/lib/topic_query.rb
+++ b/lib/topic_query.rb
@@ -81,7 +81,7 @@ class TopicQuery
# If not logged in, return some random results, preferably in this category
if @user.blank?
- return TopicList.new(@user, random_suggested_results_for(topic, SiteSetting.suggested_topics, exclude_topic_ids))
+ return TopicList.new(:suggested, @user, random_suggested_results_for(topic, SiteSetting.suggested_topics, exclude_topic_ids))
end
results = unread_results(per_page: SiteSetting.suggested_topics)
@@ -118,49 +118,45 @@ class TopicQuery
end
end
- TopicList.new(@user, results)
+ TopicList.new(:suggested, @user, results)
end
# The latest view of topics
def list_latest
- TopicList.new(@user, default_list)
+ create_list(:latest)
end
# The favorited topics
def list_favorited
- return_list do |list|
- list.where('tu.starred')
- end
+ create_list(:favorited) {|topics| topics.where('tu.starred') }
end
def list_read
- return_list(unordered: true) do |list|
- list.order('COALESCE(tu.last_visited_at, topics.bumped_at) DESC')
+ create_list(:read, unordered: true) do |topics|
+ topics.order('COALESCE(tu.last_visited_at, topics.bumped_at) DESC')
end
end
def list_hot
- return_list(unordered: true) do |list|
- # Find hot topics
- list = list.joins(:hot_topic)
- .order(TopicQuery.order_hotness)
+ create_list(:hot, unordered: true) do |topics|
+ topics.joins(:hot_topic).order(TopicQuery.order_hotness)
end
end
def list_new
- TopicList.new(@user, new_results)
+ TopicList.new(:new, @user, new_results)
end
def list_unread
- TopicList.new(@user, unread_results)
+ TopicList.new(:unread, @user, unread_results)
end
def list_posted
- return_list {|l| l.where('tu.user_id IS NOT NULL') }
+ create_list(:posted) {|l| l.where('tu.user_id IS NOT NULL') }
end
def list_uncategorized
- return_list(unordered: true) do |list|
+ create_list(:uncategorized, unordered: true) do |list|
list = list.where(category_id: nil)
if @user_id.present?
@@ -172,7 +168,7 @@ class TopicQuery
end
def list_category(category)
- return_list(unordered: true) do |list|
+ create_list(:category, unordered: true) do |list|
list = list.where(category_id: category.id)
if @user_id.present?
list.order(TopicQuery.order_with_pinned_sql)
@@ -191,13 +187,15 @@ class TopicQuery
end
def list_new_in_category(category)
- return_list {|l| l.where(category_id: category.id).by_newest.first(25)}
+ create_list(:new_in_category) {|l| l.where(category_id: category.id).by_newest.first(25)}
end
protected
- def return_list(list_opts={})
- TopicList.new(@user, yield(default_list(list_opts)))
+ def create_list(filter, list_opts={})
+ topics = default_list(list_opts)
+ topics = yield(topics) if block_given?
+ TopicList.new(filter, @user, topics)
end
# Create a list based on a bunch of detault options
@@ -233,7 +231,6 @@ class TopicQuery
end
def new_results(list_opts={})
-
default_list(list_opts)
.where("topics.created_at >= :created_at", created_at: @user.treat_as_new_topic_start_date)
.where("tu.last_read_post_number IS NULL")
@@ -252,12 +249,10 @@ class TopicQuery
.where(closed: false, archived: false, visible: true)
if topic.category_id.present?
- results = results.order("CASE WHEN topics.category_id = #{topic.category_id.to_i} THEN 0 ELSE 1 END, RANDOM()")
- else
- results = results.order("RANDOM()")
+ return results.order("CASE WHEN topics.category_id = #{topic.category_id.to_i} THEN 0 ELSE 1 END, RANDOM()")
end
- results
+ results.order("RANDOM()")
end
end