mirror of
https://github.com/discourse/discourse.git
synced 2025-06-26 15:01:48 +08:00

`SiteSerializer#post_action_types` and `SiteSerializer#topic_flag_types` both call the `Flag.used_flag_ids` method which executes queries that becomes very slow on sites with alot of records in either the `post_actions` or `reviewable_scores` table. On the `post_actions` table, we execute `SELECT DISTINCT post_action_type_id FROM post_actions`. On the `reviewable_scores` table, we execute `SELECT DISTINCT reviewable_scope_type FROM reviewable_scopes`. The problem with both queries is that it requires the PG planner to scan through every single row in those tables. For our use case, all we actually need is to check if a flag is being referenced by a record in either the `post_actions` or `reviewable_scores` tables. This commit updates the `Flag.used_flag_ids` to accept a `flag_ids` argument and use the argument to check whether the flag ids are referenced in the `post_action_type_id` or `reviewable_scope_type` foreign keys.
128 lines
3.9 KiB
Ruby
128 lines
3.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class ReviewableScore < ActiveRecord::Base
|
|
belongs_to :reviewable
|
|
belongs_to :user
|
|
belongs_to :reviewed_by, class_name: "User"
|
|
belongs_to :meta_topic, class_name: "Topic"
|
|
|
|
enum :status, { pending: 0, agreed: 1, disagreed: 2, ignored: 3 }
|
|
|
|
# To keep things simple the types correspond to `PostActionType` for backwards
|
|
# compatibility, but we can add extra reasons for scores.
|
|
def self.types
|
|
PostActionType.flag_types.merge(PostActionType.score_types).merge(@api_types || {})
|
|
end
|
|
|
|
def self.type_title(type)
|
|
I18n.t("post_action_types.#{type}.title", default: nil) ||
|
|
I18n.t("reviewable_score_types.#{type}.title", default: nil) ||
|
|
PostActionType.names[types[type]]
|
|
end
|
|
|
|
# When extending post action flags, we need to call this method in order to
|
|
# get the latests flags.
|
|
def self.reload_types
|
|
@api_types = nil
|
|
types
|
|
end
|
|
|
|
def self.add_new_types(type_names)
|
|
@api_types ||= {}
|
|
next_id = types.values.max + 1
|
|
|
|
type_names.each_with_index { |name, idx| @api_types[name] = next_id + idx }
|
|
end
|
|
|
|
def self.score_transitions
|
|
{ approved: statuses[:agreed], rejected: statuses[:disagreed], ignored: statuses[:ignored] }
|
|
end
|
|
|
|
def score_type
|
|
Reviewable::Collection::Item.new(reviewable_score_type)
|
|
end
|
|
|
|
def took_action?
|
|
take_action_bonus > 0
|
|
end
|
|
|
|
def self.calculate_score(user, type_bonus, take_action_bonus)
|
|
score = user_flag_score(user) + type_bonus + take_action_bonus
|
|
score > 0 ? score : 0
|
|
end
|
|
|
|
# A user's flag score is:
|
|
# 1.0 + trust_level + user_accuracy_bonus
|
|
# (trust_level is 5 for staff)
|
|
def self.user_flag_score(user)
|
|
1.0 + (user.staff? ? 5.0 : user.trust_level.to_f) + user_accuracy_bonus(user)
|
|
end
|
|
|
|
# A user's accuracy bonus is:
|
|
# if 5 or less flags => 0.0
|
|
# if > 5 flags => (agreed flags / total flags) * 5.0
|
|
def self.user_accuracy_bonus(user)
|
|
user_stat = user&.user_stat
|
|
return 0.0 if user_stat.blank? || user.bot?
|
|
|
|
calc_user_accuracy_bonus(user_stat.flags_agreed, user_stat.flags_disagreed)
|
|
end
|
|
|
|
def self.calc_user_accuracy_bonus(agreed, disagreed)
|
|
agreed ||= 0
|
|
disagreed ||= 0
|
|
|
|
total = (agreed + disagreed).to_f
|
|
return 0.0 if total <= 5
|
|
accuracy_axis = 0.7
|
|
|
|
percent_correct = agreed / total
|
|
positive_accuracy = percent_correct >= accuracy_axis
|
|
|
|
bottom = positive_accuracy ? accuracy_axis : 0.0
|
|
top = positive_accuracy ? 1.0 : accuracy_axis
|
|
|
|
absolute_distance = positive_accuracy ? percent_correct - bottom : top - percent_correct
|
|
|
|
axis_distance_multiplier = 1.0 / (top - bottom)
|
|
positivity_multiplier = positive_accuracy ? 1.0 : -1.0
|
|
|
|
(
|
|
absolute_distance * axis_distance_multiplier * positivity_multiplier *
|
|
(Math.log(total, 4) * 5.0)
|
|
).round(2)
|
|
end
|
|
|
|
def reviewable_conversation
|
|
return if meta_topic.blank?
|
|
Reviewable::Conversation.new(meta_topic)
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: reviewable_scores
|
|
#
|
|
# id :bigint not null, primary key
|
|
# reviewable_id :integer not null
|
|
# user_id :integer not null
|
|
# reviewable_score_type :integer not null
|
|
# status :integer not null
|
|
# score :float default(0.0), not null
|
|
# take_action_bonus :float default(0.0), not null
|
|
# reviewed_by_id :integer
|
|
# reviewed_at :datetime
|
|
# meta_topic_id :integer
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# reason :string
|
|
# user_accuracy_bonus :float default(0.0), not null
|
|
# context :string
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_reviewable_scores_on_reviewable_id (reviewable_id)
|
|
# index_reviewable_scores_on_reviewable_score_type (reviewable_score_type)
|
|
# index_reviewable_scores_on_user_id (user_id)
|
|
#
|