Skip to content

feat: require internal reasons on reports & shadow bans#2079

Open
NeonGamerBot-QK wants to merge 6 commits intohackclub:mainfrom
NeonGamerBot-QK:req-more-reasonss
Open

feat: require internal reasons on reports & shadow bans#2079
NeonGamerBot-QK wants to merge 6 commits intohackclub:mainfrom
NeonGamerBot-QK:req-more-reasonss

Conversation

@NeonGamerBot-QK
Copy link
Copy Markdown
Member

@NeonGamerBot-QK NeonGamerBot-QK commented Apr 7, 2026

260407_17h30m38s_screenshot 260407_17h30m25s_screenshot 260407_17h17m10s_screenshot fixes #1942

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements the “internal reason required” workflow for resolving project reports and shadow-banning projects, adding new fields to persist those reasons and updating admin/report review UIs to collect them (fixes #1942).

Changes:

  • Add resolution_reason to project_reports and internal_shadow_ban_reason to projects, and wire them into controllers/jobs.
  • Update admin report resolution UI and Slack token report review flow to collect a resolution reason.
  • Require both user-visible and internal reasons when shadow-banning a project, and display the internal reason in admin.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
test/models/project_test.rb Schema annotation update for new project field.
test/fixtures/projects.yml Schema annotation update for new project field.
db/schema.rb Reflect new columns in schema.
db/migrate/20260407212052_add_internal_shadow_ban_reason_to_projects.rb Adds internal_shadow_ban_reason column.
db/migrate/20260407205924_add_resolution_reason_to_project_reports.rb Adds resolution_reason column.
config/routes.rb Routes report review token GETs to a new show action and adds POST endpoints.
app/views/report_reviews/show.html.erb New UI to enter a resolution reason for token-based review/dismiss.
app/views/admin/reports/show.html.erb Add resolution reason input + display of saved resolution reason.
app/views/admin/reports/index.html.erb Remove quick-action buttons (review/dismiss) from index.
app/views/admin/projects/show.html.erb Add internal reason display and require reasons in shadow-ban modal.
app/models/project/report.rb Add validation requiring resolution_reason on reviewed/dismissed reports.
app/models/project.rb Require shadow-ban reasons when shadow_banned?; extend shadow-ban methods.
app/jobs/process_demo_broken_reports_job.rb Populate resolution_reason for auto-resolved reports.
app/controllers/report_reviews_controller.rb Add show action and persist resolution_reason on review/dismiss.
app/controllers/admin/reports_controller.rb Persist resolution_reason and surface validation errors.
app/controllers/admin/projects_controller.rb Pass internal reason into shadow_ban! and set auto-resolution reason for pending reports.
Comments suppressed due to low confidence (1)

app/controllers/report_reviews_controller.rb:62

  • process_token is called with symbols (:reviewed/:dismissed), but action_text compares new_status to the integer 1, so it will always choose the "dismissed" branch. Use a symbol/string comparison (or derive the text from the enum) so the notice matches the action taken.
      if report.update(status: new_status, resolution_reason: params[:resolution_reason]) && @token.update(used_at: Time.current)
        PaperTrail::Version.create!(
          item_type: "Project::Report",
          item_id: report.id,
          event: "update",
          whodunnit: current_user.id.to_s,
          object_changes: {
            status: [ old_status, @token.report.status ]
          }
        )

        action_text = new_status == 1 ? "reviewed" : "dismissed"
        redirect_to root_path, notice: "Report has been #{action_text}"

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +54 to +56
validates :resolution_reason, presence: { message: "is required when reviewing or dismissing a report" },
if: -> { reviewed? || dismissed? }

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue #1942 requires internal reasons to be 5+ words, but the new validation only checks presence. Consider enforcing a minimum word count (server-side) for resolution_reason when reviewed? || dismissed? so short inputs like "ok" are rejected consistently (including for non-browser clients).

Copilot uses AI. Check for mistakes.
Comment thread app/models/project.rb
Comment on lines +150 to +153
validates :shadow_banned_reason, presence: { message: "is required when shadow banning a project" },
if: :shadow_banned?
validates :internal_shadow_ban_reason, presence: { message: "is required when shadow banning a project" },
if: :shadow_banned?
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue #1942 requires internal shadow-ban reasons to be 5+ words, but internal_shadow_ban_reason (and shadow_banned_reason, if it’s also intended to be constrained) are only validated for presence. Add a server-side minimum word-count (or length) validation so the requirement can’t be bypassed via non-HTML clients.

Copilot uses AI. Check for mistakes.
Comment on lines 66 to 100
@@ -95,11 +96,12 @@ def shadow_ban
end
end

@project.shadow_ban!(reason: reason)
@project.shadow_ban!(reason: reason, internal_reason: internal_reason)
end
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shadow_ban! now enforces required reasons via model validations, but this action calls the bang method inside a transaction without handling ActiveRecord::RecordInvalid. If the request is missing either reason (e.g., JS disabled or a manual POST), this will raise and likely 500 the admin page. Consider rescuing validation errors and redirecting back with @project.errors.full_messages.to_sentence (similar to the reports controller).

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +78
form.addEventListener("submit", function(e) {
const reason = textarea.value.trim();
if (!reason) {
e.preventDefault();
textarea.focus();
textarea.classList.add("report-resolution-textarea--error");
return false;
}
hidden.value = reason;
});
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new client-side check only validates that resolution_reason is non-empty, but the feature requirement is 5+ words. Even with server-side validation added, consider updating this submit handler to enforce the same word-count requirement so admins get immediate feedback instead of a redirect error.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +16
<%= f.label :resolution_reason, "Resolution Reason", class: "report-review-label" %>
<%= f.text_area :resolution_reason, rows: 4, required: true, placeholder: "Explain why this report is being #{@action_type == 'review' ? 'reviewed' : 'dismissed'}…", class: "form-control" %>
</div>
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This form marks resolution_reason as required, but the feature request is 5+ words. Consider adding a small client-side hint/validation (and rely on server-side enforcement) so users don’t submit a too-short reason and get bounced by the model validation.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +8
before_action :find_token, only: [ :show, :review, :dismiss ]

def show
@action_type = params[:action_type]
@report = @token.report
end
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Report::ReviewToken actions are only review/dismiss, but find_token currently compares @token.action to action_name. With the updated routes, the GET pages hit #show, so action_name == "show" and tokens will always be rejected (and the redirect doesn’t return, so it may also fall through and attempt to render). Consider allowing #show by validating params[:action_type] against the token action (and using return redirect_to ... to halt the filter).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature]: make shadow bans and report actions require internal reasons.

2 participants