mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
Previously, suspect-user reviewables ("user needs approval" flags) and
queued-post reviewables could only be resolved by deleting the user or
rejecting the post outright — a reviewer mistake on an automated false
positive was irreversible, and sites with no-deletion policies had to
resolve the flag and then manually suspend the user from their admin
page. Requested in [meta t/408891](https://meta.discourse.org/t/408891)
and [meta t/225660](https://meta.discourse.org/t/225660).
This change adds guardian-gated **Silence user** / **Suspend user**
resolutions to both queues via a shared `build_penalty_actions` helper,
using the same penalize-modal flow the flagged-post and
review-every-post queues already use (staff log linked back to the
reviewable). Along the way it:
- hides penalty actions that are already active everywhere (previously a
resolve-then-409 dead end), and fixes the penalize modal's unawaited
`before()` race so the penalty is only applied once the reviewable
action succeeded;
- narrows the rejected-user **scrub** affordance to records whose
identity snapshot is the last remaining copy (user deleted, or
renamed/anonymized after a failed deletion), admin-gated to match the
endpoint;
- shows an active penalty (localized end date and reason) on the
reviewable user card, so it's clear why a penalty option is absent;
- preloads `anonymous_user_master` on the queue since the new
`silenced?` gates would otherwise lazy-load it per row;
- rewrites the action descriptions in one consistent voice.
<img width="460" height="313" alt="2026-07-31 @ 07 46 57"
src="https://github.com/user-attachments/assets/87d5bf49-97e0-47a4-939e-16da3f55c6e1"
/>
135 lines
3.2 KiB
Ruby
Vendored
135 lines
3.2 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
require "faker"
|
|
|
|
Fabricator(:reviewable) do
|
|
reviewable_by_moderator true
|
|
type "ReviewableUser"
|
|
created_by { Fabricate(:user) }
|
|
target_id { Fabricate(:user).id }
|
|
target_type "User"
|
|
target_created_by { Fabricate(:user) }
|
|
category
|
|
score 1.23
|
|
payload { { list: [1, 2, 3], name: "bandersnatch" } }
|
|
status { :pending }
|
|
end
|
|
|
|
Fabricator(:reviewable_queued_post_topic, class_name: :reviewable_queued_post) do
|
|
reviewable_by_moderator true
|
|
type "ReviewableQueuedPost"
|
|
created_by { Fabricate(:user) }
|
|
target_created_by { Fabricate(:user) }
|
|
category
|
|
payload do
|
|
{
|
|
raw: "hello world post contents.",
|
|
title: "queued post title",
|
|
tags: %w[cool neat],
|
|
extra: "some extra data",
|
|
archetype: "regular",
|
|
}
|
|
end
|
|
end
|
|
|
|
Fabricator(:suspect_user_reviewable, class_name: :reviewable_user) do
|
|
reviewable_by_moderator true
|
|
created_by { Discourse.system_user }
|
|
target { Fabricate(:user, approved: false) }
|
|
payload do |attrs|
|
|
{
|
|
"username" => attrs[:target].username,
|
|
"name" => attrs[:target].name,
|
|
"email" => attrs[:target].email,
|
|
}
|
|
end
|
|
status { :pending }
|
|
|
|
after_create do |reviewable|
|
|
reviewable.add_score(
|
|
Discourse.system_user,
|
|
ReviewableScore.types[:needs_approval],
|
|
reason: :suspect_user,
|
|
force_review: true,
|
|
)
|
|
end
|
|
end
|
|
|
|
Fabricator(:reviewable_queued_post) do
|
|
reviewable_by_moderator true
|
|
type "ReviewableQueuedPost"
|
|
created_by { Fabricate(:user) }
|
|
target_created_by { Fabricate(:user) }
|
|
topic
|
|
payload do
|
|
{
|
|
raw: "hello world post contents.",
|
|
reply_to_post_number: 1,
|
|
via_email: true,
|
|
raw_email: "store_me",
|
|
auto_track: true,
|
|
custom_fields: {
|
|
hello: "world",
|
|
},
|
|
cooking_options: {
|
|
cat: "hat",
|
|
},
|
|
cook_method: Post.cook_methods[:raw_html],
|
|
image_sizes: {
|
|
"http://foo.bar/image.png" => {
|
|
"width" => 0,
|
|
"height" => 222,
|
|
},
|
|
},
|
|
}
|
|
end
|
|
end
|
|
|
|
Fabricator(:reviewable_queued_long_post, from: :reviewable_queued_post) do
|
|
reviewable_by_moderator true
|
|
type "ReviewableQueuedPost"
|
|
created_by { Fabricate(:user) }
|
|
target_created_by { Fabricate(:user) }
|
|
topic
|
|
payload do
|
|
{
|
|
raw: Faker::DiscourseMarkdown.sandwich(sentences: 6, repeat: 3),
|
|
reply_to_post_number: 1,
|
|
via_email: true,
|
|
raw_email: "store_me",
|
|
auto_track: true,
|
|
custom_fields: {
|
|
hello: "world",
|
|
},
|
|
cooking_options: {
|
|
cat: "hat",
|
|
},
|
|
cook_method: Post.cook_methods[:raw_html],
|
|
image_sizes: {
|
|
"http://foo.bar/image.png" => {
|
|
"width" => 0,
|
|
"height" => 222,
|
|
},
|
|
},
|
|
}
|
|
end
|
|
end
|
|
|
|
Fabricator(:reviewable_flagged_post) do
|
|
reviewable_by_moderator true
|
|
type "ReviewableFlaggedPost"
|
|
created_by { Fabricate(:user) }
|
|
target_created_by { Fabricate(:user) }
|
|
topic
|
|
target_type "Post"
|
|
target { |attrs| Fabricate(:post, topic: attrs[:topic]) }
|
|
reviewable_scores { |attrs| [Fabricate.build(:reviewable_score, reviewable_id: attrs[:id])] }
|
|
end
|
|
|
|
Fabricator(:reviewable_user) do
|
|
reviewable_by_moderator true
|
|
type "ReviewableUser"
|
|
created_by { Fabricate(:user) }
|
|
target_type "User"
|
|
target { Fabricate(:user) }
|
|
end
|