FIX: select appropriate period when redirecting to top

This commit is contained in:
Régis Hanol 2015-09-21 20:28:20 +02:00
parent b49e9fb174
commit fe656fb04d
11 changed files with 68 additions and 44 deletions

View file

@ -10,7 +10,7 @@ const controllerOpts = {
canStar: Em.computed.alias('controllers.discovery/topics.currentUser.id'), canStar: Em.computed.alias('controllers.discovery/topics.currentUser.id'),
showTopicPostBadges: Em.computed.not('controllers.discovery/topics.new'), showTopicPostBadges: Em.computed.not('controllers.discovery/topics.new'),
redirectedReason: Em.computed.alias('currentUser.redirected_to_top_reason'), redirectedReason: Em.computed.alias('currentUser.redirected_to_top.reason'),
order: 'default', order: 'default',
ascending: false, ascending: false,

View file

@ -24,7 +24,7 @@ export default {
willTransition: function() { willTransition: function() {
this._super(); this._super();
Discourse.User.currentProp("should_be_redirected_to_top", false); Discourse.User.currentProp("should_be_redirected_to_top", false);
Discourse.User.currentProp("redirected_to_top_reason", null); Discourse.User.currentProp("redirected_to_top.reason", null);
return true; return true;
} }
} }

View file

@ -15,7 +15,8 @@ const DiscoveryRoute = Discourse.Route.extend(OpenComposer, {
transition.targetName.indexOf("discovery.top") === -1 && transition.targetName.indexOf("discovery.top") === -1 &&
Discourse.User.currentProp("should_be_redirected_to_top")) { Discourse.User.currentProp("should_be_redirected_to_top")) {
Discourse.User.currentProp("should_be_redirected_to_top", false); Discourse.User.currentProp("should_be_redirected_to_top", false);
this.replaceWith("discovery.top"); const period = Discourse.User.currentProp("redirect_to_top.period") || "all";
this.replaceWith(`discovery.top${period.capitalize()}`);
} }
}, },

View file

@ -3,8 +3,7 @@ module Jobs
class UpdateTopRedirection < Jobs::Base class UpdateTopRedirection < Jobs::Base
def execute(args) def execute(args)
user = User.find_by(id: args[:user_id]) if user = User.find_by(id: args[:user_id])
if user
user.update_column(:last_redirected_to_top_at, args[:redirected_at]) user.update_column(:last_redirected_to_top_at, args[:redirected_at])
end end
end end

View file

@ -88,17 +88,6 @@ class SiteSetting < ActiveRecord::Base
use_https? ? "https" : "http" use_https? ? "https" : "http"
end end
def self.has_enough_topics_to_redirect_to_top
TopTopic.periods.each do |period|
topics_per_period = TopTopic.where("#{period}_score > 0")
.limit(SiteSetting.topics_per_period_in_top_page)
.count
return true if topics_per_period >= SiteSetting.topics_per_period_in_top_page
end
# nothing
false
end
def self.default_categories_selected def self.default_categories_selected
[ [
SiteSetting.default_categories_watching.split("|"), SiteSetting.default_categories_watching.split("|"),
@ -107,6 +96,15 @@ class SiteSetting < ActiveRecord::Base
].flatten.to_set ].flatten.to_set
end end
def self.min_redirected_to_top_period
TopTopic.sorted_periods.each do |p|
period = p[0]
return period if TopTopic.topics_per_period(period) >= SiteSetting.topics_per_period_in_top_page
end
# not enough topics
nil
end
end end
# == Schema Information # == Schema Information

View file

@ -1,7 +1,15 @@
require_dependency "distributed_memoizer"
class TopTopic < ActiveRecord::Base class TopTopic < ActiveRecord::Base
belongs_to :topic belongs_to :topic
def self.topics_per_period(period)
DistributedMemoizer.memoize("#{Discourse.current_hostname}_topics_per_period_#{period}", 1.day) do
TopTopic.where("#{period}_score > 0").count
end.to_i
end
# The top topics we want to refresh often # The top topics we want to refresh often
def self.refresh_daily! def self.refresh_daily!
transaction do transaction do
@ -35,6 +43,10 @@ class TopTopic < ActiveRecord::Base
@@periods ||= [:all, :yearly, :quarterly, :monthly, :weekly, :daily].freeze @@periods ||= [:all, :yearly, :quarterly, :monthly, :weekly, :daily].freeze
end end
def self.sorted_periods
ascending_periods ||= Enum.new(:daily, :weekly, :monthly, :quarterly, :yearly, :all)
end
def self.sort_orders def self.sort_orders
@@sort_orders ||= [:posts, :views, :likes, :op_likes].freeze @@sort_orders ||= [:posts, :views, :likes, :op_likes].freeze
end end

View file

@ -698,26 +698,32 @@ class User < ActiveRecord::Base
end end
def should_be_redirected_to_top def should_be_redirected_to_top
redirected_to_top_reason.present? redirected_to_top.present?
end end
def redirected_to_top_reason def redirected_to_top
# redirect is enabled # redirect is enabled
return unless SiteSetting.redirect_users_to_top_page return unless SiteSetting.redirect_users_to_top_page
# top must be in the top_menu # top must be in the top_menu
return unless SiteSetting.top_menu =~ /top/i return unless SiteSetting.top_menu =~ /(^|\|)top(\||$)/i
# there should be enough topics # not enough topics
return unless SiteSetting.has_enough_topics_to_redirect_to_top return unless period = SiteSetting.min_redirected_to_top_period
if !seen_before? || (trust_level == 0 && !redirected_to_top_yet?) if !seen_before? || (trust_level == 0 && !redirected_to_top_yet?)
update_last_redirected_to_top! update_last_redirected_to_top!
return I18n.t('redirected_to_top_reasons.new_user') return {
reason: I18n.t('redirected_to_top_reasons.new_user'),
period: period
}
elsif last_seen_at < 1.month.ago elsif last_seen_at < 1.month.ago
update_last_redirected_to_top! update_last_redirected_to_top!
return I18n.t('redirected_to_top_reasons.not_seen_in_a_month') return {
reason: I18n.t('redirected_to_top_reasons.not_seen_in_a_month'),
period: period
}
end end
# no reason # don't redirect to top
nil nil
end end

View file

@ -23,7 +23,7 @@ class CurrentUserSerializer < BasicUserSerializer
:no_password, :no_password,
:can_delete_account, :can_delete_account,
:should_be_redirected_to_top, :should_be_redirected_to_top,
:redirected_to_top_reason, :redirected_to_top,
:disable_jump_reply, :disable_jump_reply,
:custom_fields, :custom_fields,
:muted_category_ids, :muted_category_ids,
@ -81,8 +81,8 @@ class CurrentUserSerializer < BasicUserSerializer
true true
end end
def include_redirected_to_top_reason? def include_redirected_to_top?
object.redirected_to_top_reason.present? object.redirected_to_top.present?
end end
def custom_fields def custom_fields

View file

@ -207,7 +207,7 @@ module Discourse
end end
def self.base_url def self.base_url
return base_url_no_prefix + base_uri base_url_no_prefix + base_uri
end end
def self.enable_readonly_mode def self.enable_readonly_mode

View file

@ -30,8 +30,7 @@ class DistributedMemoizer
end end
ensure ensure
# NOTE: delete regardless so next one in does not need to wait MAX_WAIT # NOTE: delete regardless so next one in does not need to wait MAX_WAIT again
# again
redis.del(redis_lock_key) redis.del(redis_lock_key)
end end
end end
@ -49,6 +48,7 @@ class DistributedMemoizer
end end
protected protected
def self.get_lock(redis, redis_lock_key) def self.get_lock(redis, redis_lock_key)
redis.watch(redis_lock_key) redis.watch(redis_lock_key)
current = redis.get(redis_lock_key) current = redis.get(redis_lock_key)
@ -61,6 +61,6 @@ class DistributedMemoizer
end end
redis.unwatch redis.unwatch
return result == ["OK"] result == ["OK"]
end end
end end

View file

@ -994,23 +994,23 @@ describe User do
let!(:user) { Fabricate(:user) } let!(:user) { Fabricate(:user) }
it "should be redirected to top when there is a reason to" do it "should be redirected to top when there is a reason to" do
user.expects(:redirected_to_top_reason).returns("42") user.expects(:redirected_to_top).returns({ reason: "42" })
expect(user.should_be_redirected_to_top).to eq(true) expect(user.should_be_redirected_to_top).to eq(true)
end end
it "should not be redirected to top when there is no reason to" do it "should not be redirected to top when there is no reason to" do
user.expects(:redirected_to_top_reason).returns(nil) user.expects(:redirected_to_top).returns(nil)
expect(user.should_be_redirected_to_top).to eq(false) expect(user.should_be_redirected_to_top).to eq(false)
end end
end end
describe ".redirected_to_top_reason" do describe ".redirected_to_top" do
let!(:user) { Fabricate(:user) } let!(:user) { Fabricate(:user) }
it "should have no reason when `SiteSetting.redirect_users_to_top_page` is disabled" do it "should have no reason when `SiteSetting.redirect_users_to_top_page` is disabled" do
SiteSetting.expects(:redirect_users_to_top_page).returns(false) SiteSetting.expects(:redirect_users_to_top_page).returns(false)
expect(user.redirected_to_top_reason).to eq(nil) expect(user.redirected_to_top).to eq(nil)
end end
context "when `SiteSetting.redirect_users_to_top_page` is enabled" do context "when `SiteSetting.redirect_users_to_top_page` is enabled" do
@ -1018,19 +1018,20 @@ describe User do
it "should have no reason when top is not in the `SiteSetting.top_menu`" do it "should have no reason when top is not in the `SiteSetting.top_menu`" do
SiteSetting.expects(:top_menu).returns("latest") SiteSetting.expects(:top_menu).returns("latest")
expect(user.redirected_to_top_reason).to eq(nil) expect(user.redirected_to_top).to eq(nil)
end end
context "and when top is in the `SiteSetting.top_menu`" do context "and when top is in the `SiteSetting.top_menu`" do
before { SiteSetting.expects(:top_menu).returns("latest|top") } before { SiteSetting.expects(:top_menu).returns("latest|top") }
it "should have no reason when there aren't enough topics" do it "should have no reason when there are not enough topics" do
SiteSetting.expects(:has_enough_topics_to_redirect_to_top).returns(false) SiteSetting.expects(:min_redirected_to_top_period).returns(nil)
expect(user.redirected_to_top_reason).to eq(nil) expect(user.redirected_to_top).to eq(nil)
end end
context "and when there are enough topics" do context "and there are enough topics" do
before { SiteSetting.expects(:has_enough_topics_to_redirect_to_top).returns(true) }
before { SiteSetting.expects(:min_redirected_to_top_period).returns(:monthly) }
describe "a new user" do describe "a new user" do
before do before do
@ -1042,14 +1043,17 @@ describe User do
user.expects(:last_redirected_to_top_at).returns(nil) user.expects(:last_redirected_to_top_at).returns(nil)
user.expects(:update_last_redirected_to_top!).once user.expects(:update_last_redirected_to_top!).once
expect(user.redirected_to_top_reason).to eq(I18n.t('redirected_to_top_reasons.new_user')) expect(user.redirected_to_top).to eq({
reason: I18n.t('redirected_to_top_reasons.new_user'),
period: :monthly
})
end end
it "should not have a reason for next visits" do it "should not have a reason for next visits" do
user.expects(:last_redirected_to_top_at).returns(10.minutes.ago) user.expects(:last_redirected_to_top_at).returns(10.minutes.ago)
user.expects(:update_last_redirected_to_top!).never user.expects(:update_last_redirected_to_top!).never
expect(user.redirected_to_top_reason).to eq(nil) expect(user.redirected_to_top).to eq(nil)
end end
end end
@ -1060,8 +1064,12 @@ describe User do
user.last_seen_at = 2.months.ago user.last_seen_at = 2.months.ago
user.expects(:update_last_redirected_to_top!).once user.expects(:update_last_redirected_to_top!).once
expect(user.redirected_to_top_reason).to eq(I18n.t('redirected_to_top_reasons.not_seen_in_a_month')) expect(user.redirected_to_top).to eq({
reason: I18n.t('redirected_to_top_reasons.not_seen_in_a_month'),
period: :monthly
})
end end
end end
end end