discourse/app/controllers/reviewables_controller.rb
Robin Ward cbc311e4ed UX: Update the reviewable count before the message bus
In certain edge cases, the message bus won't send the message to the
user about the updated review count and it can go out of sync.

This patch synchronizes the review count every time:

1. The user visits the "Needs Review" page

2. Every time the user performs an action
2019-04-05 10:35:38 -04:00

188 lines
5.5 KiB
Ruby

class ReviewablesController < ApplicationController
requires_login
PER_PAGE = 10
before_action :version_required, only: [:update, :perform]
def index
min_score = params[:min_score].nil? ? SiteSetting.min_score_default_visibility : params[:min_score].to_f
offset = params[:offset].to_i
if params[:type].present?
raise Discourse::InvalidParameter.new(:type) unless Reviewable.valid_type?(params[:type])
end
status = (params[:status] || 'pending').to_sym
raise Discourse::InvalidParameter.new(:status) unless allowed_statuses.include?(status)
topic_id = params[:topic_id] ? params[:topic_id].to_i : nil
category_id = params[:category_id] ? params[:category_id].to_i : nil
filters = {
status: status,
category_id: category_id,
topic_id: topic_id,
min_score: min_score,
username: params[:username],
type: params[:type]
}
total_rows = Reviewable.list_for(current_user, filters).count
reviewables = Reviewable.list_for(current_user, filters.merge(limit: PER_PAGE, offset: offset)).to_a
# This is a bit awkward, but ActiveModel serializers doesn't seem to serialize STI. Note `hash`
# is mutated by the serializer and contains the side loaded records which must be merged in the end.
hash = {}
json = {
reviewables: reviewables.map! do |r|
result = r.serializer.new(r, root: nil, hash: hash, scope: guardian).as_json
hash[:bundled_actions].uniq!
(hash['actions'] || []).uniq!
result
end,
meta: filters.merge(
total_rows_reviewables: total_rows, types: meta_types, reviewable_types: Reviewable.types,
reviewable_count: Reviewable.list_for(current_user).count
)
}
if (offset + PER_PAGE) < total_rows
json[:meta][:load_more_reviewables] = review_path(filters.merge(offset: offset + PER_PAGE))
end
json.merge!(hash)
render_json_dump(json, rest_serializer: true)
end
def topics
topic_ids = Set.new
stats = {}
unique_users = {}
# topics isn't indexed on `reviewable_score` and doesn't know what the current user can see,
# so let's query from the inside out.
pending = Reviewable.viewable_by(current_user).pending
pending = pending.where("score >= ?", SiteSetting.min_score_default_visibility)
pending.each do |r|
topic_ids << r.topic_id
meta = stats[r.topic_id] ||= { count: 0, unique_users: 0 }
users = unique_users[r.topic_id] ||= Set.new
r.reviewable_scores.each do |rs|
users << rs.user_id
meta[:count] += 1
end
meta[:unique_users] = users.size
end
topics = Topic.where(id: topic_ids).order('reviewable_score DESC')
render_serialized(topics, ReviewableTopicSerializer, root: 'reviewable_topics', stats: stats)
end
def show
reviewable = find_reviewable
render_serialized(
reviewable,
reviewable.serializer,
rest_serializer: true,
root: 'reviewable',
meta: {
types: meta_types
}
)
end
def update
reviewable = find_reviewable
editable = reviewable.editable_for(guardian)
raise Discourse::InvalidAccess.new unless editable.present?
# Validate parameters are all editable
edit_params = params[:reviewable] || {}
edit_params.each do |name, value|
if value.is_a?(ActionController::Parameters)
value.each do |pay_name, pay_value|
raise Discourse::InvalidAccess.new unless editable.has?("#{name}.#{pay_name}")
end
else
raise Discourse::InvalidAccess.new unless editable.has?(name)
end
end
begin
if reviewable.update_fields(edit_params, current_user, version: params[:version].to_i)
result = edit_params.merge(version: reviewable.version)
render json: result
else
render_json_error(reviewable.errors)
end
rescue Reviewable::UpdateConflict
return render_json_error(I18n.t('reviewables.conflict'), status: 409)
end
end
def perform
args = { version: params[:version].to_i }
begin
result = find_reviewable.perform(current_user, params[:action_id].to_sym, args)
rescue Reviewable::InvalidAction => e
# Consider InvalidAction an InvalidAccess
raise Discourse::InvalidAccess.new(e.message)
rescue Reviewable::UpdateConflict
return render_json_error(I18n.t('reviewables.conflict'), status: 409)
end
if result.success?
render_serialized(result, ReviewablePerformResultSerializer)
else
render_json_error(result)
end
end
def settings
raise Discourse::InvalidAccess.new unless current_user.admin?
post_action_types = PostActionType.where(id: PostActionType.flag_types.values).order('id')
data = { reviewable_score_types: post_action_types }
if request.put?
params[:bonuses].each do |id, bonus|
PostActionType.where(id: id).update_all(score_bonus: bonus.to_f)
end
end
render_serialized(data, ReviewableSettingsSerializer, rest_serializer: true)
end
protected
def find_reviewable
reviewable = Reviewable.viewable_by(current_user).where(id: params[:reviewable_id]).first
raise Discourse::NotFound.new if reviewable.blank?
reviewable
end
def allowed_statuses
@allowed_statuses ||= (%i[reviewed all] + Reviewable.statuses.keys)
end
def version_required
if params[:version].blank?
render_json_error(I18n.t('reviewables.missing_version'), status: 422)
end
end
def meta_types
{
created_by: 'user',
target_created_by: 'user'
}
end
end