mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
PERF: move 3 more option columns out of the user table
This commit is contained in:
@@ -141,13 +141,11 @@ const User = RestModel.extend({
|
||||
|
||||
save() {
|
||||
const data = this.getProperties(
|
||||
'auto_track_topics_after_msecs',
|
||||
'bio_raw',
|
||||
'website',
|
||||
'location',
|
||||
'name',
|
||||
'locale',
|
||||
'new_topic_duration_minutes',
|
||||
'custom_fields',
|
||||
'user_fields',
|
||||
'muted_usernames',
|
||||
@@ -165,7 +163,9 @@ const User = RestModel.extend({
|
||||
'enable_quoting',
|
||||
'disable_jump_reply',
|
||||
'automatically_unpin_topics',
|
||||
'digest_after_days'
|
||||
'digest_after_days',
|
||||
'new_topic_duration_minutes',
|
||||
'auto_track_topics_after_msecs'
|
||||
].forEach(s => {
|
||||
data[s] = this.get(`user_option.${s}`);
|
||||
});
|
||||
|
||||
@@ -201,12 +201,12 @@
|
||||
|
||||
<div class="controls controls-dropdown">
|
||||
<label>{{i18n 'user.new_topic_duration.label'}}</label>
|
||||
{{combo-box valueAttribute="value" content=considerNewTopicOptions value=model.new_topic_duration_minutes}}
|
||||
{{combo-box valueAttribute="value" content=considerNewTopicOptions value=model.user_option.new_topic_duration_minutes}}
|
||||
</div>
|
||||
|
||||
<div class="controls controls-dropdown">
|
||||
<label>{{i18n 'user.auto_track_topics'}}</label>
|
||||
{{combo-box valueAttribute="value" content=autoTrackDurations value=model.auto_track_topics_after_msecs}}
|
||||
{{combo-box valueAttribute="value" content=autoTrackDurations value=model.user_option.auto_track_topics_after_msecs}}
|
||||
</div>
|
||||
|
||||
{{preference-checkbox labelKey="user.external_links_in_new_tab" checked=model.user_option.external_links_in_new_tab}}
|
||||
|
||||
@@ -4,7 +4,7 @@ module Jobs
|
||||
|
||||
def execute(args)
|
||||
if user = User.find_by(id: args[:user_id])
|
||||
user.update_column(:last_redirected_to_top_at, args[:redirected_at])
|
||||
user.user_option.update_column(:last_redirected_to_top_at, args[:redirected_at])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -104,9 +104,9 @@ class TopicTrackingState
|
||||
|
||||
def self.treat_as_new_topic_clause
|
||||
User.where("GREATEST(CASE
|
||||
WHEN COALESCE(u.new_topic_duration_minutes, :default_duration) = :always THEN u.created_at
|
||||
WHEN COALESCE(u.new_topic_duration_minutes, :default_duration) = :last_visit THEN COALESCE(u.previous_visit_at,u.created_at)
|
||||
ELSE (:now::timestamp - INTERVAL '1 MINUTE' * COALESCE(u.new_topic_duration_minutes, :default_duration))
|
||||
WHEN COALESCE(uo.new_topic_duration_minutes, :default_duration) = :always THEN u.created_at
|
||||
WHEN COALESCE(uo.new_topic_duration_minutes, :default_duration) = :last_visit THEN COALESCE(u.previous_visit_at,u.created_at)
|
||||
ELSE (:now::timestamp - INTERVAL '1 MINUTE' * COALESCE(uo.new_topic_duration_minutes, :default_duration))
|
||||
END, us.new_since, :min_date)",
|
||||
now: DateTime.now,
|
||||
last_visit: User::NewTopicDuration::LAST_VISIT,
|
||||
@@ -169,6 +169,7 @@ class TopicTrackingState
|
||||
FROM topics
|
||||
JOIN users u on u.id = :user_id
|
||||
JOIN user_stats AS us ON us.user_id = u.id
|
||||
JOIN user_options AS uo ON uo.user_id = u.id
|
||||
JOIN categories c ON c.id = topics.category_id
|
||||
LEFT JOIN topic_users tu ON tu.topic_id = topics.id AND tu.user_id = u.id
|
||||
WHERE u.id = :user_id AND
|
||||
|
||||
@@ -104,7 +104,7 @@ class TopicUser < ActiveRecord::Base
|
||||
|
||||
if rows == 0
|
||||
now = DateTime.now
|
||||
auto_track_after = User.select(:auto_track_topics_after_msecs).find_by(id: user_id).try(:auto_track_topics_after_msecs)
|
||||
auto_track_after = UserOption.where(user_id: user_id).pluck(:auto_track_topics_after_msecs).first
|
||||
auto_track_after ||= SiteSetting.default_other_auto_track_topics_after_msecs
|
||||
|
||||
if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed].to_i || 0)
|
||||
@@ -140,6 +140,31 @@ class TopicUser < ActiveRecord::Base
|
||||
|
||||
# Update the last read and the last seen post count, but only if it doesn't exist.
|
||||
# This would be a lot easier if psql supported some kind of upsert
|
||||
UPDATE_TOPIC_USER_SQL = "UPDATE topic_users
|
||||
SET
|
||||
last_read_post_number = GREATEST(:post_number, tu.last_read_post_number),
|
||||
highest_seen_post_number = t.highest_post_number,
|
||||
total_msecs_viewed = LEAST(tu.total_msecs_viewed + :msecs,86400000),
|
||||
notification_level =
|
||||
case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
|
||||
coalesce(uo.auto_track_topics_after_msecs,:threshold) and
|
||||
coalesce(uo.auto_track_topics_after_msecs, :threshold) >= 0 then
|
||||
:tracking
|
||||
else
|
||||
tu.notification_level
|
||||
end
|
||||
FROM topic_users tu
|
||||
join topics t on t.id = tu.topic_id
|
||||
join users u on u.id = :user_id
|
||||
join user_options uo on uo.user_id = :user_id
|
||||
WHERE
|
||||
tu.topic_id = topic_users.topic_id AND
|
||||
tu.user_id = topic_users.user_id AND
|
||||
tu.topic_id = :topic_id AND
|
||||
tu.user_id = :user_id
|
||||
RETURNING
|
||||
topic_users.notification_level, tu.notification_level old_level, tu.last_read_post_number
|
||||
"
|
||||
def update_last_read(user, topic_id, post_number, msecs, opts={})
|
||||
return if post_number.blank?
|
||||
msecs = 0 if msecs.to_i < 0
|
||||
@@ -160,31 +185,7 @@ class TopicUser < ActiveRecord::Base
|
||||
# ... user visited the topic but did not read the posts
|
||||
#
|
||||
# 86400000 = 1 day
|
||||
rows = exec_sql("UPDATE topic_users
|
||||
SET
|
||||
last_read_post_number = GREATEST(:post_number, tu.last_read_post_number),
|
||||
highest_seen_post_number = t.highest_post_number,
|
||||
total_msecs_viewed = LEAST(tu.total_msecs_viewed + :msecs,86400000),
|
||||
notification_level =
|
||||
case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
|
||||
coalesce(u.auto_track_topics_after_msecs,:threshold) and
|
||||
coalesce(u.auto_track_topics_after_msecs, :threshold) >= 0 then
|
||||
:tracking
|
||||
else
|
||||
tu.notification_level
|
||||
end
|
||||
FROM topic_users tu
|
||||
join topics t on t.id = tu.topic_id
|
||||
join users u on u.id = :user_id
|
||||
WHERE
|
||||
tu.topic_id = topic_users.topic_id AND
|
||||
tu.user_id = topic_users.user_id AND
|
||||
tu.topic_id = :topic_id AND
|
||||
tu.user_id = :user_id
|
||||
RETURNING
|
||||
topic_users.notification_level, tu.notification_level old_level, tu.last_read_post_number
|
||||
",
|
||||
args).values
|
||||
rows = exec_sql(UPDATE_TOPIC_USER_SQL,args).values
|
||||
|
||||
if rows.length == 1
|
||||
before = rows[0][1].to_i
|
||||
@@ -206,7 +207,7 @@ class TopicUser < ActiveRecord::Base
|
||||
if rows.length == 0
|
||||
# The user read at least one post in a topic that they haven't viewed before.
|
||||
args[:new_status] = notification_levels[:regular]
|
||||
if (user.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs) == 0
|
||||
if (user.user_option.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs) == 0
|
||||
args[:new_status] = notification_levels[:tracking]
|
||||
end
|
||||
TopicTrackingState.publish_read(topic_id, post_number, user.id, args[:new_status])
|
||||
|
||||
@@ -77,8 +77,6 @@ class User < ActiveRecord::Base
|
||||
|
||||
after_initialize :add_trust_level
|
||||
|
||||
before_create :set_default_user_preferences
|
||||
|
||||
after_create :create_email_token
|
||||
after_create :create_user_stat
|
||||
after_create :create_user_option
|
||||
@@ -90,7 +88,6 @@ class User < ActiveRecord::Base
|
||||
before_save :update_username_lower
|
||||
before_save :ensure_password_is_hashed
|
||||
|
||||
after_save :update_tracked_topics
|
||||
after_save :clear_global_notice_if_needed
|
||||
after_save :refresh_avatar
|
||||
after_save :badge_grant
|
||||
@@ -626,20 +623,6 @@ class User < ActiveRecord::Base
|
||||
Promotion.new(self).change_trust_level!(level, opts)
|
||||
end
|
||||
|
||||
def treat_as_new_topic_start_date
|
||||
duration = new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes.to_i
|
||||
times = [case duration
|
||||
when User::NewTopicDuration::ALWAYS
|
||||
created_at
|
||||
when User::NewTopicDuration::LAST_VISIT
|
||||
previous_visit_at || user_stat.new_since
|
||||
else
|
||||
duration.minutes.ago
|
||||
end, user_stat.new_since, Time.at(SiteSetting.min_new_topics_time).to_datetime]
|
||||
|
||||
times.max
|
||||
end
|
||||
|
||||
def readable_name
|
||||
return "#{name} (#{username})" if name.present? && name != username
|
||||
username
|
||||
@@ -730,51 +713,6 @@ class User < ActiveRecord::Base
|
||||
.exists?
|
||||
end
|
||||
|
||||
def should_be_redirected_to_top
|
||||
redirected_to_top.present?
|
||||
end
|
||||
|
||||
def redirected_to_top
|
||||
# redirect is enabled
|
||||
return unless SiteSetting.redirect_users_to_top_page
|
||||
# top must be in the top_menu
|
||||
return unless SiteSetting.top_menu =~ /(^|\|)top(\||$)/i
|
||||
# not enough topics
|
||||
return unless period = SiteSetting.min_redirected_to_top_period
|
||||
|
||||
if !seen_before? || (trust_level == 0 && !redirected_to_top_yet?)
|
||||
update_last_redirected_to_top!
|
||||
return {
|
||||
reason: I18n.t('redirected_to_top_reasons.new_user'),
|
||||
period: period
|
||||
}
|
||||
elsif last_seen_at < 1.month.ago
|
||||
update_last_redirected_to_top!
|
||||
return {
|
||||
reason: I18n.t('redirected_to_top_reasons.not_seen_in_a_month'),
|
||||
period: period
|
||||
}
|
||||
end
|
||||
|
||||
# don't redirect to top
|
||||
nil
|
||||
end
|
||||
|
||||
def redirected_to_top_yet?
|
||||
last_redirected_to_top_at.present?
|
||||
end
|
||||
|
||||
def update_last_redirected_to_top!
|
||||
key = "user:#{id}:update_last_redirected_to_top"
|
||||
delay = SiteSetting.active_user_rate_limit_secs
|
||||
|
||||
# only update last_redirected_to_top_at once every minute
|
||||
return unless $redis.setnx(key, "1")
|
||||
$redis.expire(key, delay)
|
||||
|
||||
# delay the update
|
||||
Jobs.enqueue_in(delay / 2, :update_top_redirection, user_id: self.id, redirected_at: Time.zone.now)
|
||||
end
|
||||
|
||||
def refresh_avatar
|
||||
return if @import_mode
|
||||
@@ -880,11 +818,6 @@ class User < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def update_tracked_topics
|
||||
return unless auto_track_topics_after_msecs_changed?
|
||||
TrackedTopicsUpdater.new(id, auto_track_topics_after_msecs).call
|
||||
end
|
||||
|
||||
def clear_global_notice_if_needed
|
||||
if admin && SiteSetting.has_login_hint
|
||||
SiteSetting.has_login_hint = false
|
||||
@@ -970,14 +903,6 @@ class User < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def set_default_user_preferences
|
||||
set_default_other_new_topic_duration_minutes
|
||||
set_default_other_auto_track_topics_after_msecs
|
||||
|
||||
# needed, otherwise the callback chain is broken...
|
||||
true
|
||||
end
|
||||
|
||||
def set_default_categories_preferences
|
||||
values = []
|
||||
|
||||
@@ -1025,12 +950,6 @@ class User < ActiveRecord::Base
|
||||
end
|
||||
|
||||
|
||||
%w{new_topic_duration_minutes auto_track_topics_after_msecs}.each do |s|
|
||||
define_method("set_default_other_#{s}") do
|
||||
self.send("#{s}=", SiteSetting.send("default_other_#{s}").to_i) if has_attribute?(s)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
@@ -1054,7 +973,6 @@ end
|
||||
# admin :boolean default(FALSE), not null
|
||||
# last_emailed_at :datetime
|
||||
# trust_level :integer not null
|
||||
# email_private_messages :boolean default(TRUE)
|
||||
# approved :boolean default(FALSE), not null
|
||||
# approved_by_id :integer
|
||||
# approved_at :datetime
|
||||
@@ -1062,11 +980,9 @@ end
|
||||
# suspended_at :datetime
|
||||
# suspended_till :datetime
|
||||
# date_of_birth :date
|
||||
# auto_track_topics_after_msecs :integer
|
||||
# views :integer default(0), not null
|
||||
# flag_level :integer default(0), not null
|
||||
# ip_address :inet
|
||||
# new_topic_duration_minutes :integer
|
||||
# moderator :boolean default(FALSE)
|
||||
# blocked :boolean default(FALSE)
|
||||
# title :string(255)
|
||||
@@ -1074,7 +990,6 @@ end
|
||||
# primary_group_id :integer
|
||||
# locale :string(10)
|
||||
# registration_ip_address :inet
|
||||
# last_redirected_to_top_at :datetime
|
||||
# trust_level_locked :boolean default(FALSE), not null
|
||||
# staged :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
@@ -3,6 +3,8 @@ class UserOption < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
before_create :set_defaults
|
||||
|
||||
after_save :update_tracked_topics
|
||||
|
||||
def set_defaults
|
||||
self.email_always = SiteSetting.default_email_always
|
||||
self.mailing_list_mode = SiteSetting.default_email_mailing_list_mode
|
||||
@@ -16,6 +18,9 @@ class UserOption < ActiveRecord::Base
|
||||
self.disable_jump_reply = SiteSetting.default_other_disable_jump_reply
|
||||
self.edit_history_public = SiteSetting.default_other_edit_history_public
|
||||
|
||||
self.new_topic_duration_minutes = SiteSetting.default_other_new_topic_duration_minutes
|
||||
self.auto_track_topics_after_msecs = SiteSetting.default_other_auto_track_topics_after_msecs
|
||||
|
||||
|
||||
if SiteSetting.default_email_digest_frequency.to_i <= 0
|
||||
self.email_digests = false
|
||||
@@ -26,4 +31,70 @@ class UserOption < ActiveRecord::Base
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def update_tracked_topics
|
||||
return unless auto_track_topics_after_msecs_changed?
|
||||
TrackedTopicsUpdater.new(id, auto_track_topics_after_msecs).call
|
||||
end
|
||||
|
||||
def redirected_to_top_yet?
|
||||
last_redirected_to_top_at.present?
|
||||
end
|
||||
|
||||
def update_last_redirected_to_top!
|
||||
key = "user:#{id}:update_last_redirected_to_top"
|
||||
delay = SiteSetting.active_user_rate_limit_secs
|
||||
|
||||
# only update last_redirected_to_top_at once every minute
|
||||
return unless $redis.setnx(key, "1")
|
||||
$redis.expire(key, delay)
|
||||
|
||||
# delay the update
|
||||
Jobs.enqueue_in(delay / 2, :update_top_redirection, user_id: self.id, redirected_at: Time.zone.now)
|
||||
end
|
||||
|
||||
def should_be_redirected_to_top
|
||||
redirected_to_top.present?
|
||||
end
|
||||
|
||||
def redirected_to_top
|
||||
# redirect is enabled
|
||||
return unless SiteSetting.redirect_users_to_top_page
|
||||
# top must be in the top_menu
|
||||
return unless SiteSetting.top_menu =~ /(^|\|)top(\||$)/i
|
||||
# not enough topics
|
||||
return unless period = SiteSetting.min_redirected_to_top_period
|
||||
|
||||
if !user.seen_before? || (user.trust_level == 0 && !redirected_to_top_yet?)
|
||||
update_last_redirected_to_top!
|
||||
return {
|
||||
reason: I18n.t('redirected_to_top_reasons.new_user'),
|
||||
period: period
|
||||
}
|
||||
elsif user.last_seen_at < 1.month.ago
|
||||
update_last_redirected_to_top!
|
||||
return {
|
||||
reason: I18n.t('redirected_to_top_reasons.not_seen_in_a_month'),
|
||||
period: period
|
||||
}
|
||||
end
|
||||
|
||||
# don't redirect to top
|
||||
nil
|
||||
end
|
||||
|
||||
def treat_as_new_topic_start_date
|
||||
duration = new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes.to_i
|
||||
times = [case duration
|
||||
when User::NewTopicDuration::ALWAYS
|
||||
user.created_at
|
||||
when User::NewTopicDuration::LAST_VISIT
|
||||
user.previous_visit_at || user.user_stat.new_since
|
||||
else
|
||||
duration.minutes.ago
|
||||
end, user.user_stat.new_since, Time.at(SiteSetting.min_new_topics_time).to_datetime]
|
||||
|
||||
times.max
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -70,6 +70,14 @@ class CurrentUserSerializer < BasicUserSerializer
|
||||
object.user_option.automatically_unpin_topics
|
||||
end
|
||||
|
||||
def should_be_redirected_to_top
|
||||
object.user_option.should_be_redirected_to_top
|
||||
end
|
||||
|
||||
def redirected_to_top
|
||||
object.user_option.redirected_to_top
|
||||
end
|
||||
|
||||
def site_flagged_posts_count
|
||||
PostAction.flagged_posts_count
|
||||
end
|
||||
@@ -103,7 +111,7 @@ class CurrentUserSerializer < BasicUserSerializer
|
||||
end
|
||||
|
||||
def include_redirected_to_top?
|
||||
object.redirected_to_top.present?
|
||||
object.user_option.redirected_to_top.present?
|
||||
end
|
||||
|
||||
def custom_fields
|
||||
|
||||
@@ -45,7 +45,7 @@ class ListableTopicSerializer < BasicTopicSerializer
|
||||
def seen
|
||||
return true if !scope || !scope.user
|
||||
return true if object.user_data && !object.user_data.last_read_post_number.nil?
|
||||
return true if object.created_at < scope.user.treat_as_new_topic_start_date
|
||||
return true if object.created_at < scope.user.user_option.treat_as_new_topic_start_date
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
@@ -11,10 +11,21 @@ class UserOptionSerializer < ApplicationSerializer
|
||||
:disable_jump_reply,
|
||||
:digest_after_days,
|
||||
:automatically_unpin_topics,
|
||||
:edit_history_public
|
||||
:edit_history_public,
|
||||
:auto_track_topics_after_msecs,
|
||||
:new_topic_duration_minutes
|
||||
|
||||
|
||||
def include_edit_history_public?
|
||||
!SiteSetting.edit_history_visible_to_public
|
||||
end
|
||||
|
||||
def auto_track_topics_after_msecs
|
||||
object.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs
|
||||
end
|
||||
|
||||
def new_topic_duration_minutes
|
||||
object.new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -82,8 +82,6 @@ class UserSerializer < BasicUserSerializer
|
||||
:can_delete_all_posts
|
||||
|
||||
private_attributes :locale,
|
||||
:auto_track_topics_after_msecs,
|
||||
:new_topic_duration_minutes,
|
||||
:muted_category_ids,
|
||||
:tracked_category_ids,
|
||||
:watched_category_ids,
|
||||
@@ -253,14 +251,6 @@ class UserSerializer < BasicUserSerializer
|
||||
### PRIVATE ATTRIBUTES
|
||||
###
|
||||
|
||||
def auto_track_topics_after_msecs
|
||||
object.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs
|
||||
end
|
||||
|
||||
def new_topic_duration_minutes
|
||||
object.new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes
|
||||
end
|
||||
|
||||
def muted_category_ids
|
||||
CategoryUser.lookup(object, :muted).pluck(:category_id)
|
||||
end
|
||||
|
||||
@@ -18,7 +18,9 @@ class UserUpdater
|
||||
:disable_jump_reply,
|
||||
:edit_history_public,
|
||||
:automatically_unpin_topics,
|
||||
:digest_after_days
|
||||
:digest_after_days,
|
||||
:new_topic_duration_minutes,
|
||||
:auto_track_topics_after_msecs
|
||||
]
|
||||
|
||||
def initialize(actor, user)
|
||||
@@ -38,14 +40,6 @@ class UserUpdater
|
||||
user.name = attributes.fetch(:name) { user.name }
|
||||
user.locale = attributes.fetch(:locale) { user.locale }
|
||||
|
||||
if attributes[:auto_track_topics_after_msecs]
|
||||
user.auto_track_topics_after_msecs = attributes[:auto_track_topics_after_msecs].to_i
|
||||
end
|
||||
|
||||
if attributes[:new_topic_duration_minutes]
|
||||
user.new_topic_duration_minutes = attributes[:new_topic_duration_minutes].to_i
|
||||
end
|
||||
|
||||
if guardian.can_grant_title?(user)
|
||||
user.title = attributes.fetch(:title) { user.title }
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user