mirror of
https://github.com/discourse/discourse.git
synced 2026-08-07 03:35:32 -05:00
DEV: Centralise user action definitions for reviewables. (#34279)
This change centralises how user actions are defined. ### How It Works `ReviewableActionBuilder::build_user_actions_bundle` defines all of the user actions that a reviewable should need. `ReviewableActionBuilder` also now includes matching `perform_*` methods, to go with the actions defined in `build_user_actions_bundle`. There's also an overridable `target_user` method. It defaults to `target_created_by`, since that's the most common case, but it will allow different reviewable types to define a different user to target: for example, `ReviewableUser` would want to have it return `target`. ### Supporting Changes `Reviewable::build_actions` is temporarily overridden in `ReviewableActionBuilder`. Because we need `build_actions` to define both the old and new actions, I've chosen to split these into separate methods (`build_legacy_combined_actions` and `build_new_separated_actions`) that reviewables will temporarily need to define. This allows minimal code churn of the old logic, while we can build the new logic within its own method. `Reviewable::build_actions` will ultimately be moved to `ReviewableActionBuilder`. `Reviewable::create_result` has been copied to `ReviewableActionBuilder`, and expanded to include the flag-handling functionality of `ReviewablePost::successful_transition`.
This commit is contained in:
@@ -3,6 +3,89 @@
|
||||
module ReviewableActionBuilder
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Standard user-actions bundle and default user actions.
|
||||
#
|
||||
# @param actions [Reviewable::Actions] Actions instance to add the bundle to.
|
||||
# @param guardian [Guardian] Guardian instance to check permissions.
|
||||
#
|
||||
# @return [Reviewable::Actions::Bundle] The created user actions bundle.
|
||||
def build_user_actions_bundle(actions, guardian)
|
||||
bundle =
|
||||
actions.add_bundle(
|
||||
"#{id}-user-actions",
|
||||
label: "reviewables.actions.user_actions.bundle_title",
|
||||
)
|
||||
|
||||
# Always include the no-op action
|
||||
build_action(actions, :no_action_user, bundle: bundle)
|
||||
|
||||
return bundle unless target_user
|
||||
|
||||
if guardian.can_silence_user?(target_user)
|
||||
build_action(actions, :silence_user, bundle: bundle, client_action: "silence")
|
||||
end
|
||||
|
||||
if guardian.can_suspend?(target_user)
|
||||
build_action(actions, :suspend_user, bundle: bundle, client_action: "suspend")
|
||||
end
|
||||
|
||||
if guardian.can_delete_user?(target_user)
|
||||
build_action(actions, :delete_user, bundle: bundle)
|
||||
build_action(actions, :delete_and_block_user, bundle: bundle)
|
||||
end
|
||||
|
||||
bundle
|
||||
end
|
||||
|
||||
# Build actions for the reviewable based on the current state and guardian permissions.
|
||||
#
|
||||
# @TODO (reviewable-refresh) Replace this method with {Reviewable#build_actions} once the new UI is fully implemented.
|
||||
#
|
||||
# @param actions [Reviewable::Actions] Actions instance to add the bundle to.
|
||||
# @param guardian [Guardian] Guardian instance to check permissions.
|
||||
# @param args [Hash] Additional arguments for building actions.
|
||||
#
|
||||
# @return [void]
|
||||
def build_actions(actions, guardian, args)
|
||||
if guardian.can_see_reviewable_ui_refresh?
|
||||
build_new_separated_actions(actions, guardian, args)
|
||||
else
|
||||
build_legacy_combined_actions(actions, guardian, args)
|
||||
end
|
||||
end
|
||||
|
||||
# Build legacy combined actions for the reviewable.
|
||||
#
|
||||
# Classes that include this module should implement this method to define
|
||||
# the legacy combined actions for their specific reviewable type.
|
||||
#
|
||||
# @TODO (reviewable-refresh) Remove this method once the new UI is fully implemented.
|
||||
#
|
||||
# @param actions [Reviewable::Actions] Actions instance to add the bundle to.
|
||||
# @param guardian [Guardian] Guardian instance to check permissions.
|
||||
# @param args [Hash] Additional arguments for building actions.
|
||||
#
|
||||
# @return [void]
|
||||
def build_legacy_combined_actions(actions, guardian, args)
|
||||
raise NotImplementedError, "Including class must implement #build_legacy_combined_actions"
|
||||
end
|
||||
|
||||
# Build new separated actions for the reviewable.
|
||||
#
|
||||
# Classes that include this module should implement this method to define
|
||||
# the new separated actions for their specific reviewable type.
|
||||
#
|
||||
# @TODO (reviewable-refresh) Remove this method once the new UI is fully implemented.
|
||||
#
|
||||
# @param actions [Reviewable::Actions] Actions instance to add the bundle to.
|
||||
# @param guardian [Guardian] Guardian instance to check permissions.
|
||||
# @param args [Hash] Additional arguments for building actions.
|
||||
#
|
||||
# @return [void]
|
||||
def build_new_separated_actions(actions, guardian, args)
|
||||
raise NotImplementedError, "Including class must implement #build_new_separated_actions"
|
||||
end
|
||||
|
||||
# Build a single reviewable action and add it to the provided actions list.
|
||||
# This is the canonical API used by both the legacy and refreshed UI code paths.
|
||||
#
|
||||
@@ -38,4 +121,93 @@ module ReviewableActionBuilder
|
||||
action.require_reject_reason = require_reject_reason
|
||||
end
|
||||
end
|
||||
|
||||
def perform_no_action_user(performed_by, args)
|
||||
create_result(:success, :approved)
|
||||
end
|
||||
|
||||
def perform_silence_user(performed_by, args)
|
||||
create_result(:success, :rejected)
|
||||
end
|
||||
|
||||
def perform_suspend_user(performed_by, args)
|
||||
create_result(:success, :rejected)
|
||||
end
|
||||
|
||||
def perform_delete_user(performed_by, args, &)
|
||||
delete_user(target_user, delete_opts, performed_by) if target_user
|
||||
create_result(:success, :rejected, [], recalculate_score: false, &)
|
||||
end
|
||||
|
||||
def perform_delete_and_block_user(performed_by, args, &)
|
||||
delete_options = delete_opts
|
||||
delete_options.merge!(block_email: true, block_ip: true) if Rails.env.production?
|
||||
|
||||
delete_user(target_user, delete_options, performed_by) if target_user
|
||||
create_result(:success, :rejected, [], recalculate_score: false, &)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns the user associated with the reviewable, if applicable.
|
||||
# For most reviewables, this will be the user who created the reviewable, though some
|
||||
# reviewables may need to implement this method differently (for example, ReviewableUser).
|
||||
#
|
||||
# @return [User] The user associated with the reviewable.
|
||||
def target_user
|
||||
try(:target_created_by)
|
||||
end
|
||||
|
||||
# Options for deleting a user, used by perform_delete_user and perform_delete_and_block_user.
|
||||
def delete_opts
|
||||
{
|
||||
delete_posts: true,
|
||||
prepare_for_destroy: true,
|
||||
block_urls: true,
|
||||
delete_as_spammer: true,
|
||||
context: "review",
|
||||
}
|
||||
end
|
||||
|
||||
def delete_user(user, delete_options, performed_by)
|
||||
email = user.email
|
||||
|
||||
UserDestroyer.new(performed_by).destroy(user, delete_options)
|
||||
|
||||
message = UserNotifications.account_deleted(email, self)
|
||||
Email::Sender.new(message, :account_deleted).send
|
||||
end
|
||||
|
||||
def map_reviewable_status_to_flag_status(status)
|
||||
case status
|
||||
when :approved
|
||||
:agreed
|
||||
when :rejected
|
||||
:disagreed
|
||||
else
|
||||
status
|
||||
end
|
||||
end
|
||||
|
||||
# Create a result object.
|
||||
#
|
||||
# @param status [Symbol] The status of the result.
|
||||
# @param transition_to [Symbol] The state to transition to.
|
||||
# @param recalculate_score [Boolean] Whether to recalculate the score.
|
||||
# @yield [result] The result object.
|
||||
#
|
||||
# @return [Reviewable::PerformResult] The created result object.
|
||||
def create_result(status, transition_to = nil, flagging_user_ids = [], recalculate_score = true)
|
||||
result = Reviewable::PerformResult.new(self, status)
|
||||
result.transition_to = transition_to
|
||||
if flagging_user_ids.any?
|
||||
result.update_flag_stats = {
|
||||
status: map_reviewable_status_to_flag_status(transition_to),
|
||||
user_ids: flagging_user_ids,
|
||||
}
|
||||
result.recalculate_score = recalculate_score
|
||||
end
|
||||
yield result if block_given?
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
@@ -631,6 +631,7 @@ class Reviewable < ActiveRecord::Base
|
||||
@@serializers[type] ||= lookup_serializer_for(type)
|
||||
end
|
||||
|
||||
# @TODO (reviewable-refresh) This can be deprecated/removed once all reviewable types have been migrated, it now lives in ReviewableActionBuilder.
|
||||
def create_result(status, transition_to = nil)
|
||||
result = PerformResult.new(self, status)
|
||||
result.transition_to = transition_to
|
||||
@@ -740,6 +741,7 @@ class Reviewable < ActiveRecord::Base
|
||||
self.score
|
||||
end
|
||||
|
||||
# TODO (reviewable-refresh) This can be deprecated/removed once all reviewable types have been migrated.
|
||||
def delete_user_actions(actions, bundle = nil, require_reject_reason: false)
|
||||
bundle ||=
|
||||
actions.add_bundle(
|
||||
|
||||
@@ -14,6 +14,7 @@ class ReviewableFlaggedPost < Reviewable
|
||||
agree_and_edit: :agree_and_keep,
|
||||
disagree_and_restore: :disagree,
|
||||
ignore_and_do_nothing: :ignore,
|
||||
delete_user_block: :delete_and_block_user, # legacy name mapped to concern method
|
||||
}
|
||||
end
|
||||
|
||||
@@ -46,7 +47,12 @@ class ReviewableFlaggedPost < Reviewable
|
||||
def build_actions(actions, guardian, args)
|
||||
return unless pending?
|
||||
return if post.blank?
|
||||
super
|
||||
end
|
||||
|
||||
# TODO (reviewable-refresh): Remove legacy method once new UI fully deployed
|
||||
def build_legacy_combined_actions(actions, guardian, args)
|
||||
# existing combined logic
|
||||
agree_bundle =
|
||||
actions.add_bundle("#{id}-agree", icon: "thumbs-up", label: "reviewables.actions.agree.title")
|
||||
|
||||
@@ -142,6 +148,11 @@ class ReviewableFlaggedPost < Reviewable
|
||||
end
|
||||
end
|
||||
|
||||
# TODO (reviewable-refresh): Merge into build_actions post rollout.
|
||||
def build_new_separated_actions(actions, guardian, args)
|
||||
build_user_actions_bundle(actions, guardian)
|
||||
end
|
||||
|
||||
def perform_ignore(performed_by, args)
|
||||
perform_ignore_and_do_nothing(performed_by, args)
|
||||
end
|
||||
@@ -173,9 +184,7 @@ class ReviewableFlaggedPost < Reviewable
|
||||
DiscourseEvent.trigger(:flag_deferred, actions.first)
|
||||
end
|
||||
|
||||
create_result(:success, :ignored) do |result|
|
||||
result.update_flag_stats = { status: :ignored, user_ids: actions.map(&:user_id) }
|
||||
end
|
||||
create_result(:success, :ignored, actions.map(&:user_id), false)
|
||||
end
|
||||
|
||||
def perform_agree_and_keep(performed_by, args)
|
||||
@@ -183,15 +192,12 @@ class ReviewableFlaggedPost < Reviewable
|
||||
end
|
||||
|
||||
def perform_delete_user(performed_by, args)
|
||||
delete_user(post.user, delete_opts, performed_by)
|
||||
super
|
||||
agree(performed_by, args)
|
||||
end
|
||||
|
||||
def perform_delete_user_block(performed_by, args)
|
||||
delete_options = delete_opts
|
||||
delete_options.merge!(block_email: true, block_ip: true) if Rails.env.production?
|
||||
|
||||
delete_user(post.user, delete_options, performed_by)
|
||||
def perform_delete_and_block_user(performed_by, args)
|
||||
super
|
||||
agree(performed_by, args)
|
||||
end
|
||||
|
||||
@@ -245,9 +251,7 @@ class ReviewableFlaggedPost < Reviewable
|
||||
UserSilencer.unsilence(post.user) if UserSilencer.was_silenced_for?(post)
|
||||
end
|
||||
|
||||
create_result(:success, :rejected) do |result|
|
||||
result.update_flag_stats = { status: :disagreed, user_ids: actions.map(&:user_id) }
|
||||
end
|
||||
create_result(:success, :rejected, actions.map(&:user_id), false)
|
||||
end
|
||||
|
||||
def perform_delete_and_ignore(performed_by, args)
|
||||
@@ -307,10 +311,7 @@ class ReviewableFlaggedPost < Reviewable
|
||||
yield(actions.first) if block_given?
|
||||
end
|
||||
|
||||
create_result(:success, :approved) do |result|
|
||||
result.update_flag_stats = { status: :agreed, user_ids: actions.map(&:user_id) }
|
||||
result.recalculate_score = true
|
||||
end
|
||||
create_result(:success, :approved, actions.map(&:user_id), false)
|
||||
end
|
||||
|
||||
def unassign_topic(performed_by, post)
|
||||
@@ -341,25 +342,6 @@ class ReviewableFlaggedPost < Reviewable
|
||||
|
||||
private
|
||||
|
||||
def delete_user(user, delete_options, performed_by)
|
||||
email = user.email
|
||||
|
||||
UserDestroyer.new(performed_by).destroy(user, delete_options)
|
||||
|
||||
message = UserNotifications.account_deleted(email, self)
|
||||
Email::Sender.new(message, :account_deleted).send
|
||||
end
|
||||
|
||||
def delete_opts
|
||||
{
|
||||
delete_posts: true,
|
||||
prepare_for_destroy: true,
|
||||
block_urls: true,
|
||||
delete_as_spammer: true,
|
||||
context: "review",
|
||||
}
|
||||
end
|
||||
|
||||
def destroyer(performed_by, post)
|
||||
PostDestroyer.new(performed_by, post, reviewable: self)
|
||||
end
|
||||
|
||||
@@ -34,7 +34,11 @@ class ReviewablePost < Reviewable
|
||||
|
||||
def build_actions(actions, guardian, args)
|
||||
return unless pending?
|
||||
super
|
||||
end
|
||||
|
||||
# TODO (reviewable-refresh): Remove this method when fully migrated to new UI
|
||||
def build_legacy_combined_actions(actions, guardian, args)
|
||||
if post.trashed? && guardian.can_recover_post?(post)
|
||||
build_action(actions, :approve_and_restore, icon: "check")
|
||||
elsif post.hidden?
|
||||
@@ -74,48 +78,48 @@ class ReviewablePost < Reviewable
|
||||
end
|
||||
end
|
||||
|
||||
# TODO (reviewable-refresh): Merge this method into build_actions when fully migrated to new UI
|
||||
def build_new_separated_actions(actions, guardian, args)
|
||||
build_user_actions_bundle(actions, guardian)
|
||||
end
|
||||
|
||||
# TODO (reviewable-refresh): Remove combined actions below when fully migrated to new UI
|
||||
def perform_approve(performed_by, _args)
|
||||
successful_transition :approved, recalculate_score: false
|
||||
create_result(:success, :approved, [created_by_id], false)
|
||||
end
|
||||
|
||||
def perform_reject_and_keep_deleted(performed_by, _args)
|
||||
successful_transition :rejected, recalculate_score: false
|
||||
create_result(:success, :rejected, [created_by_id], false)
|
||||
end
|
||||
|
||||
def perform_approve_and_restore(performed_by, _args)
|
||||
PostDestroyer.new(performed_by, post).recover
|
||||
|
||||
successful_transition :approved, recalculate_score: false
|
||||
create_result(:success, :approved, [created_by_id], false)
|
||||
end
|
||||
|
||||
def perform_approve_and_unhide(performed_by, _args)
|
||||
post.unhide!
|
||||
|
||||
successful_transition :approved, recalculate_score: false
|
||||
create_result(:success, :approved, [created_by_id], false)
|
||||
end
|
||||
|
||||
def perform_reject_and_delete(performed_by, _args)
|
||||
PostDestroyer.new(performed_by, post, reviewable: self).destroy
|
||||
|
||||
successful_transition :rejected, recalculate_score: false
|
||||
create_result(:success, :rejected, [created_by_id], false)
|
||||
end
|
||||
|
||||
def perform_reject_and_suspend(performed_by, _args)
|
||||
successful_transition :rejected, recalculate_score: false
|
||||
create_result(:success, :rejected, [created_by_id], false)
|
||||
end
|
||||
# TODO (reviewable-refresh): Remove combined actions above when fully migrated to new UI
|
||||
|
||||
private
|
||||
|
||||
def post
|
||||
@post ||= (target || Post.with_deleted.find_by(id: target_id))
|
||||
end
|
||||
|
||||
def successful_transition(to_state, recalculate_score: true)
|
||||
create_result(:success, to_state) do |result|
|
||||
result.recalculate_score = recalculate_score
|
||||
result.update_flag_stats = { status: to_state, user_ids: [created_by_id] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
||||
@@ -4,7 +4,7 @@ class ReviewableQueuedPost < Reviewable
|
||||
include ReviewableActionBuilder
|
||||
|
||||
def self.action_aliases
|
||||
{ discard_post: :reject_post }
|
||||
{ discard_post: :reject_post, delete_user_block: :delete_and_block_user }
|
||||
end
|
||||
|
||||
after_create do
|
||||
@@ -38,7 +38,8 @@ class ReviewableQueuedPost < Reviewable
|
||||
reviewable_scores.pending.or(reviewable_scores.disagreed)
|
||||
end
|
||||
|
||||
def build_actions(actions, guardian, args)
|
||||
# TODO (reviewable-refresh): Remove this method once new UI is fully deployed
|
||||
def build_legacy_combined_actions(actions, guardian, args)
|
||||
unless approved?
|
||||
if topic&.closed?
|
||||
build_action(actions, :approve_post_closed, icon: "check", confirm: true)
|
||||
@@ -70,6 +71,10 @@ class ReviewableQueuedPost < Reviewable
|
||||
build_action(actions, :delete) if guardian.can_delete?(self)
|
||||
end
|
||||
|
||||
def build_new_separated_actions(actions, guardian, args)
|
||||
build_user_actions_bundle(actions, guardian) if pending?
|
||||
end
|
||||
|
||||
def build_editable_fields(fields, guardian, args)
|
||||
if pending?
|
||||
# We can edit category / title if it's a new topic
|
||||
@@ -185,28 +190,21 @@ class ReviewableQueuedPost < Reviewable
|
||||
end
|
||||
|
||||
def perform_delete_user(performed_by, args)
|
||||
delete_user(performed_by, delete_opts)
|
||||
reviewable_ids = Reviewable.where(created_by: target_created_by).pluck(:id)
|
||||
result = super { |r| r.remove_reviewable_ids += reviewable_ids }
|
||||
update_column(:target_created_by_id, nil)
|
||||
result
|
||||
end
|
||||
|
||||
def perform_delete_user_block(performed_by, args)
|
||||
delete_options = delete_opts
|
||||
|
||||
delete_options.merge!(block_email: true, block_ip: true) if Rails.env.production?
|
||||
|
||||
delete_user(performed_by, delete_options)
|
||||
def perform_delete_and_block_user(performed_by, args)
|
||||
reviewable_ids = Reviewable.where(created_by: target_created_by).pluck(:id)
|
||||
result = super { |r| r.remove_reviewable_ids += reviewable_ids }
|
||||
update_column(:target_created_by_id, nil)
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_user(performed_by, delete_options)
|
||||
reviewable_ids = Reviewable.where(created_by: target_created_by).pluck(:id)
|
||||
|
||||
UserDestroyer.new(performed_by).destroy(target_created_by, delete_options)
|
||||
update_column(:target_created_by_id, nil)
|
||||
|
||||
create_result(:success, :rejected) { |r| r.remove_reviewable_ids += reviewable_ids }
|
||||
end
|
||||
|
||||
def delete_opts
|
||||
{
|
||||
context: I18n.t("reviewables.actions.delete_user.reason"),
|
||||
|
||||
@@ -13,12 +13,19 @@ class ReviewableUser < Reviewable
|
||||
|
||||
def build_actions(actions, guardian, args)
|
||||
return unless pending?
|
||||
super
|
||||
end
|
||||
|
||||
def build_legacy_combined_actions(actions, guardian, args)
|
||||
build_action(actions, :approve_user, icon: "user-plus") if guardian.can_approve?(target)
|
||||
|
||||
delete_user_actions(actions, require_reject_reason: !is_a_suspect_user?)
|
||||
end
|
||||
|
||||
def build_new_separated_actions(actions, guardian, args)
|
||||
build_legacy_combined_actions(actions, guardian, args)
|
||||
end
|
||||
|
||||
def perform_approve_user(performed_by, args)
|
||||
ReviewableUser.set_approved_fields!(target, performed_by)
|
||||
target.save!
|
||||
|
||||
@@ -5844,9 +5844,32 @@ en:
|
||||
approve_and_unhide:
|
||||
title: "Approve and Unhide post"
|
||||
complete: "Post approved and unhidden"
|
||||
# New separated user actions
|
||||
user_actions:
|
||||
bundle_title: "What do you want to do with the user?"
|
||||
no_action_user:
|
||||
title: "No action"
|
||||
description: "Take no action against the user"
|
||||
complete: "No action taken against user"
|
||||
silence_user:
|
||||
title: "Silence user"
|
||||
description: "Prevent the user from posting"
|
||||
complete: "User silenced"
|
||||
reason: "User silenced via review queue"
|
||||
suspend_user:
|
||||
title: "Suspend user"
|
||||
description: "Suspend the user account"
|
||||
complete: "User suspended"
|
||||
reason: "User suspended via review queue"
|
||||
delete_user:
|
||||
title: "Delete user"
|
||||
description: "Delete this user from the forum"
|
||||
complete: "User deleted"
|
||||
reason: "Deleted via review queue"
|
||||
complete: "User deleted."
|
||||
delete_and_block_user:
|
||||
title: "Delete & block"
|
||||
description: "Delete the user and block their IP/email"
|
||||
complete: "User deleted and blocked"
|
||||
|
||||
email_style:
|
||||
html_missing_placeholder: "The html template must include %{placeholder}"
|
||||
|
||||
@@ -5,6 +5,46 @@ RSpec.describe ReviewableActionBuilder do
|
||||
fab!(:guardian) { Guardian.new(admin) }
|
||||
fab!(:user)
|
||||
|
||||
describe "#build_user_actions_bundle" do
|
||||
fab!(:post) { Fabricate(:post, user: user) }
|
||||
fab!(:reviewable_post) do
|
||||
ReviewablePost.needs_review!(target: post, created_by: admin, potential_spam: false)
|
||||
end
|
||||
fab!(:post_actions) { Reviewable::Actions.new(reviewable_post, guardian) }
|
||||
|
||||
it "creates a user bundle with standard actions when allowed" do
|
||||
bundle = reviewable_post.build_user_actions_bundle(post_actions, guardian)
|
||||
|
||||
# bundle id and label
|
||||
expect(bundle.id).to eq("#{reviewable_post.id}-user-actions")
|
||||
expect(bundle.label).to eq("reviewables.actions.user_actions.bundle_title")
|
||||
|
||||
# action ids are prefixed with target type (post-...)
|
||||
action_ids = bundle.actions.map(&:id)
|
||||
expect(action_ids).to include("post-no_action_user")
|
||||
expect(action_ids).to include("post-silence_user")
|
||||
expect(action_ids).to include("post-suspend_user")
|
||||
expect(action_ids).to include("post-delete_user")
|
||||
expect(action_ids).to include("post-delete_and_block_user")
|
||||
|
||||
# client_action is set for moderation actions
|
||||
silence = bundle.actions.find { |a| a.id == "post-silence_user" }
|
||||
suspend = bundle.actions.find { |a| a.id == "post-suspend_user" }
|
||||
expect(silence.client_action).to eq("silence")
|
||||
expect(suspend.client_action).to eq("suspend")
|
||||
end
|
||||
|
||||
it "includes only the no-op action when user is nil" do
|
||||
allow(reviewable_post).to receive(:target_created_by).and_return(nil)
|
||||
|
||||
bundle = reviewable_post.build_user_actions_bundle(post_actions, guardian)
|
||||
server_actions = bundle.actions.map(&:server_action)
|
||||
|
||||
expect(server_actions).to include("no_action_user")
|
||||
expect(server_actions - ["no_action_user"]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "#build_action" do
|
||||
fab!(:reviewable_user) { ReviewableUser.create_for(user) }
|
||||
|
||||
|
||||
@@ -313,6 +313,28 @@ RSpec.describe ReviewableFlaggedPost, type: :model do
|
||||
expect(post.user_deleted?).to eq(false)
|
||||
expect(post.hidden?).to eq(false)
|
||||
end
|
||||
context "when reviewable_ui_refresh enabled (separated bundles)" do
|
||||
before do
|
||||
# Stub guardian check on reviewable to simulate feature flag on
|
||||
allow_any_instance_of(Guardian).to receive(:can_see_reviewable_ui_refresh?).and_return(true)
|
||||
end
|
||||
|
||||
it "builds user actions bundle with moderation actions" do
|
||||
actions = reviewable.actions_for(guardian)
|
||||
user_bundle = actions.bundles.find { |b| b.id.ends_with?("-user-actions") }
|
||||
expect(user_bundle).to be_present
|
||||
expect(actions.has?(:silence_user)).to eq(true)
|
||||
expect(actions.has?(:suspend_user)).to eq(true)
|
||||
expect(actions.has?(:delete_user)).to eq(true)
|
||||
end
|
||||
|
||||
it "omits user deletion when reviewer cannot delete user" do
|
||||
allow(guardian).to receive(:can_delete_user?).and_return(false)
|
||||
actions = reviewable.actions_for(guardian)
|
||||
expect(actions.has?(:delete_user)).to eq(false)
|
||||
expect(actions.has?(:delete_and_block_user)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "pending count" do
|
||||
|
||||
@@ -4,6 +4,7 @@ RSpec.describe ReviewablePost do
|
||||
fab!(:admin)
|
||||
|
||||
describe "#build_actions" do
|
||||
# TODO (reviewable-refresh): Remove the tests below when the legacy combined actions are removed
|
||||
let(:post) { Fabricate.build(:post) }
|
||||
let(:reviewable) { ReviewablePost.new(target: post) }
|
||||
let(:guardian) { Guardian.new }
|
||||
@@ -59,6 +60,66 @@ RSpec.describe ReviewablePost do
|
||||
|
||||
actions
|
||||
end
|
||||
# TODO (reviewable-refresh): Remove the tests above when the legacy combined actions are removed
|
||||
|
||||
context "with new UI (separated post and user actions)" do
|
||||
let(:post) { Fabricate.build(:post) }
|
||||
let(:reviewable) { ReviewablePost.new(target: post, target_created_by: post.user) }
|
||||
let(:guardian) { Guardian.new }
|
||||
let(:admin_guardian) { Guardian.new(admin) }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(Guardian).to receive(:can_see_reviewable_ui_refresh?).and_return(true)
|
||||
end
|
||||
|
||||
it "Does not return available actions when the reviewable is no longer pending" do
|
||||
available_actions =
|
||||
(Reviewable.statuses.keys - ["pending"]).reduce([]) do |actions, status|
|
||||
reviewable.status = status
|
||||
actions.concat reviewable_actions(guardian).to_a
|
||||
end
|
||||
|
||||
expect(available_actions).to be_empty
|
||||
end
|
||||
|
||||
it "includes user actions when target_created_by is present" do
|
||||
actions = reviewable_actions(guardian)
|
||||
|
||||
expect(actions.has?(:no_action_user)).to eq(true)
|
||||
end
|
||||
|
||||
it "includes suspend and silence actions for admins" do
|
||||
actions = reviewable_actions(admin_guardian)
|
||||
|
||||
expect(actions.has?(:suspend_user)).to eq(true)
|
||||
expect(actions.has?(:silence_user)).to eq(true)
|
||||
end
|
||||
|
||||
it "includes delete user actions for admins" do
|
||||
actions = reviewable_actions(admin_guardian)
|
||||
|
||||
expect(actions.has?(:delete_user)).to eq(true)
|
||||
expect(actions.has?(:delete_and_block_user)).to eq(true)
|
||||
end
|
||||
|
||||
it "includes a minimal user actions bundle when no target_created_by" do
|
||||
reviewable.target_created_by = nil
|
||||
actions = reviewable_actions(guardian)
|
||||
|
||||
expect(actions.has?(:no_action_user)).to eq(true)
|
||||
expect(actions.has?(:silence_user)).to eq(false)
|
||||
expect(actions.has?(:suspend_user)).to eq(false)
|
||||
expect(actions.has?(:delete_user)).to eq(false)
|
||||
expect(actions.has?(:delete_and_block_user)).to eq(false)
|
||||
end
|
||||
|
||||
def reviewable_actions(guardian)
|
||||
actions = Reviewable::Actions.new(reviewable, guardian, {})
|
||||
reviewable.build_actions(actions, guardian, {})
|
||||
|
||||
actions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Performing actions" do
|
||||
@@ -67,6 +128,7 @@ RSpec.describe ReviewablePost do
|
||||
|
||||
before { reviewable.created_new! }
|
||||
|
||||
# TODO (reviewable-refresh): Remove the tests below when the legacy combined actions are removed
|
||||
describe "#perform_approve" do
|
||||
it "transitions to the approved state" do
|
||||
result = reviewable.perform admin, :approve
|
||||
@@ -124,5 +186,36 @@ RSpec.describe ReviewablePost do
|
||||
expect(Post.where(id: post.id).exists?).to eq(false)
|
||||
end
|
||||
end
|
||||
# TODO (reviewable-refresh): Remove the tests above when the legacy combined actions are removed
|
||||
|
||||
context "with new separated actions" do
|
||||
before do
|
||||
allow_any_instance_of(Guardian).to receive(:can_see_reviewable_ui_refresh?).and_return(true)
|
||||
end
|
||||
|
||||
describe "#perform_silence_user" do
|
||||
it "transitions to rejected" do
|
||||
result = reviewable.perform admin, :silence_user
|
||||
|
||||
expect(result.transition_to).to eq :rejected
|
||||
end
|
||||
end
|
||||
|
||||
describe "#perform_suspend_user" do
|
||||
it "transitions to rejected" do
|
||||
result = reviewable.perform admin, :suspend_user
|
||||
|
||||
expect(result.transition_to).to eq :rejected
|
||||
end
|
||||
end
|
||||
|
||||
describe "#perform_no_action_user" do
|
||||
it "transitions to approved" do
|
||||
result = reviewable.perform admin, :no_action_user
|
||||
|
||||
expect(result.transition_to).to eq :approved
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -296,8 +296,7 @@ RSpec.describe ReviewableQueuedPost, type: :model do
|
||||
|
||||
it "creates a topic with staff tag when approved" do
|
||||
hidden_tag = Fabricate(:tag)
|
||||
staff_tag_group =
|
||||
Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [hidden_tag.name])
|
||||
Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [hidden_tag.name])
|
||||
reviewable.payload["tags"] += [hidden_tag.name]
|
||||
|
||||
result = reviewable.perform(moderator, :approve_post)
|
||||
@@ -322,7 +321,7 @@ RSpec.describe ReviewableQueuedPost, type: :model do
|
||||
end
|
||||
|
||||
it "remaps tags with synonyms when approved" do
|
||||
syn_tag = Fabricate(:tag, name: "syntag", target_tag: Fabricate(:tag, name: "maintag"))
|
||||
Fabricate(:tag, name: "syntag", target_tag: Fabricate(:tag, name: "maintag"))
|
||||
reviewable.payload["tags"] += ["syntag"]
|
||||
|
||||
result = reviewable.perform(moderator, :approve_post)
|
||||
@@ -370,4 +369,91 @@ RSpec.describe ReviewableQueuedPost, type: :model do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "separated actions UI" do
|
||||
fab!(:admin)
|
||||
fab!(:user)
|
||||
let(:reviewable) { Fabricate(:reviewable_queued_post, target_created_by: user) }
|
||||
|
||||
context "when reviewable_ui_refresh feature is enabled" do
|
||||
before do
|
||||
allow_any_instance_of(Guardian).to receive(:can_see_reviewable_ui_refresh?).and_return(true)
|
||||
end
|
||||
|
||||
it "includes user actions in the user bundle" do
|
||||
actions = reviewable.actions_for(Guardian.new(admin))
|
||||
|
||||
expect(actions.has?(:no_action_user)).to eq(true)
|
||||
expect(actions.has?(:silence_user)).to eq(true)
|
||||
expect(actions.has?(:suspend_user)).to eq(true)
|
||||
expect(actions.has?(:delete_user)).to eq(true)
|
||||
expect(actions.has?(:delete_and_block_user)).to eq(true)
|
||||
end
|
||||
|
||||
it "includes a minimal user bundle when target_created_by is nil" do
|
||||
reviewable.update!(target_created_by: nil)
|
||||
actions = reviewable.actions_for(Guardian.new(admin))
|
||||
|
||||
expect(actions.has?(:no_action_user)).to eq(true)
|
||||
expect(actions.has?(:silence_user)).to eq(false)
|
||||
expect(actions.has?(:suspend_user)).to eq(false)
|
||||
expect(actions.has?(:delete_user)).to eq(false)
|
||||
expect(actions.has?(:delete_and_block_user)).to eq(false)
|
||||
end
|
||||
|
||||
describe "perform methods" do
|
||||
it "performs no_action_user successfully" do
|
||||
result = reviewable.perform(admin, :no_action_user)
|
||||
expect(result.success?).to eq(true)
|
||||
end
|
||||
|
||||
it "performs silence_user successfully" do
|
||||
expect(user.silenced?).to eq(false)
|
||||
result = reviewable.perform(admin, :silence_user)
|
||||
expect(result.success?).to eq(true)
|
||||
end
|
||||
|
||||
it "performs suspend_user successfully" do
|
||||
expect(user.suspended?).to eq(false)
|
||||
result = reviewable.perform(admin, :suspend_user)
|
||||
expect(result.success?).to eq(true)
|
||||
end
|
||||
|
||||
it "performs delete_and_block_user successfully" do
|
||||
result = reviewable.perform(admin, :delete_and_block_user)
|
||||
expect(result.success?).to eq(true)
|
||||
expect(User.find_by(id: user.id)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# TODO (reviewable-refresh): Remove the tests below when the legacy combined actions are removed
|
||||
context "when reviewable_ui_refresh feature is disabled" do
|
||||
before do
|
||||
allow_any_instance_of(Guardian).to receive(:can_see_reviewable_ui_refresh?).and_return(
|
||||
false,
|
||||
)
|
||||
end
|
||||
|
||||
it "uses legacy bundle structure" do
|
||||
actions = reviewable.actions_for(Guardian.new(admin))
|
||||
bundle_ids = actions.bundles.map(&:id)
|
||||
|
||||
expect(bundle_ids).to include("#{reviewable.id}-reject")
|
||||
expect(bundle_ids).not_to include("#{reviewable.id}-post-actions")
|
||||
expect(bundle_ids).not_to include("#{reviewable.id}-user-actions")
|
||||
end
|
||||
|
||||
it "includes legacy actions" do
|
||||
actions = reviewable.actions_for(Guardian.new(admin))
|
||||
action_ids = actions.to_a.map(&:id).map(&:to_s)
|
||||
|
||||
expect(action_ids).to include("approve_post")
|
||||
expect(action_ids).to include("discard_post")
|
||||
expect(action_ids).to include("revise_and_reject_post")
|
||||
expect(action_ids).to include("delete_user")
|
||||
expect(action_ids).to include("delete_user_block")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user