Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/models/certification/permanent_rejection_nomination.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def self.nominate!(review:, reviewer:, reason:)
end
nomination.save!
review.update!(reviewer: nil, claimed_at: nil, claim_expires_at: nil)
nomination.decide!(admin: reviewer, approve: true) if reviewer.admin?
nomination.decide!(admin: reviewer, approve: true) if reviewer.can_manage_hardware_permanent_rejections?
end
end
nomination
Expand All @@ -68,7 +68,7 @@ def self.nominate!(review:, reviewer:, reason:)
# Lock in the same order as nomination/verdicts: review, then project. A
# repeated or competing decision must not send another builder notification.
def decide!(admin:, approve:)
raise Pundit::NotAuthorizedError unless admin&.admin? && !project.memberships.exists?(user_id: admin.id)
raise Pundit::NotAuthorizedError unless Admin::Certification::PermanentRejectionNominationPolicy.new(admin, self).approve?

reviewable.with_lock do
project.with_lock do
Expand Down Expand Up @@ -171,6 +171,7 @@ def post_nomination_to_staff!
project_url: routes.project_url(project, **url_opts),
nomination_url: routes.admin_certification_permanent_rejection_nomination_url(self, **url_opts),
reviewer_name: reviewer.display_name,
reviewer_admin: reviewer.admin?,
review_stage: review_stage,
reason: reason,
automatically_approved: approved?
Expand Down
1 change: 1 addition & 0 deletions app/models/user/role.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class User
new(2, :fraud_dept, "Can issue negative payouts, cancel grants & shop orders, but not reject or ban users; access to Blazer; access to read-only admin User w/o PII"),
new(13, :fraud_fraud_squad_squad, "Everything fraud_dept can do, plus the unified fraud review dashboard: integrity checks, shop orders and fraud flags, per person"),
new(3, :project_certifier, "Approve/reject if project work meets Shipwright standards"),
new(14, :hardware_permanent_rejection_manager, "Auto-approve own hardware permanent rejection nominations and approve/deny other reviewers' nominations; requires existing review access to nominate"),
new(4, :guardian_of_integrity, "Can approve/reject projects for YSWS DB"),
new(5, :fulfillment_person, "Can approve/reject/on-hold shop orders, fulfill them, and see addresses; access to read-only admin User w/ pII"),
new(6, :helper, "Support team with read-only access to users (no PII), projects, shop orders and vote data"),
Expand Down
2 changes: 2 additions & 0 deletions app/models/user/roles.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def admin? = has_role?(:admin) || has_role?(:super_admin)

def can_review? = admin? || has_role?(:project_certifier)

def can_manage_hardware_permanent_rejections? = admin? || hardware_permanent_rejection_manager?

def can_nominate_super_star? = can_review? || has_role?(:guardian_of_integrity)

def highest_role
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Admin::Certification::PermanentRejectionNominationPolicy < ApplicationPolicy
def index? = user&.admin?
def index? = user&.can_manage_hardware_permanent_rejections?
def show? = index?

def create?
Expand All @@ -14,10 +14,10 @@ def create?
end

def approve?
user&.admin? && !record.project.memberships.exists?(user_id: user.id)
index? && !record.project.memberships.exists?(user_id: user.id)
end

def deny? = approve?

def undo? = approve? && record.approved?
def undo? = user&.admin? && approve? && record.approved?
end
2 changes: 2 additions & 0 deletions app/views/admin/application/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<%= admin_page_button "Ship Certifications", admin_certification_ships_path,
enabled: policy(Certification::Ship).index?,
count: "ship_certifications" %>
<%= admin_page_button "Hardware Permanent Rejections", admin_certification_permanent_rejection_nominations_path,
enabled: policy(Certification::PermanentRejectionNomination).index? %>
<%= admin_page_button "YSWS Reviews", admin_certification_ysws_reviews_path,
enabled: policy(Certification::Ysws).index?,
count: "ysws_reviews" %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</span>
</div>
<div class="ship-queue__header-actions">
<% if current_user.admin? %>
<% if policy([:admin, Certification::PermanentRejectionNomination]).index? %>
<%= link_to "Permanent reject nominations", admin_certification_permanent_rejection_nominations_path,
class: "action-btn action-btn--small action-btn--secondary" %>
<% end %>
Expand Down
4 changes: 2 additions & 2 deletions app/views/admin/certification/hardware_reviews/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@
<p><%= nomination.undone? ? "Originally approved" : nomination.status.capitalize %> by <%= nomination.decided_by.display_name %> on <%= l(nomination.decided_at, format: :long) %>.</p>
<% end %>
<p><%= nomination.undone? ? "This rejection was undone. The builder was sent a correction." : nomination.approved? ? "The final decision is visible to the builder." : "This nomination is not visible to the builder." %></p>
<% if current_user.admin? %>
<% if policy([:admin, nomination]).show? %>
<%= link_to "View nomination and decision", admin_certification_permanent_rejection_nomination_path(nomination) %>
<% end %>
</details>
Expand Down Expand Up @@ -406,7 +406,7 @@

<button type="button" class="action-btn action-btn--small action-btn--destructive"
aria-haspopup="dialog" onclick="document.getElementById('hardware-permanent-rejection').showModal()">
<%= current_user.admin? ? "Permanently reject project" : "Nominate for permanent rejection" %>
<%= current_user.can_manage_hardware_permanent_rejections? ? "Permanently reject project" : "Nominate for permanent rejection" %>
</button>
<dialog id="hardware-permanent-rejection" class="hardware-review__fraud-modal"
data-controller="modal" aria-labelledby="permanent-rejection-title">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<% direct = current_user.admin? %>
<% direct = current_user.can_manage_hardware_permanent_rejections? %>
<div class="hardware-review__fraud-modal-card">
<% if local_assigns[:modal] %>
<button type="button" class="hardware-review__fraud-modal-close"
data-action="click->modal#close" aria-label="Close">&times;</button>
<% end %>
<h3 id="permanent-rejection-title" class="hardware-review__fraud-modal-title"><%= direct ? "Permanently reject project" : "Nominate for permanent rejection" %></h3>
<p id="permanent-rejection-reason-help" class="hardware-review__fraud-modal-note">
<%= direct ? "This takes effect immediately and prevents future funding or certification. The builder will receive your justification." : "An admin will decide. Until then, this project leaves the review queue and the builder is not notified. Your justification is shared only if approved." %>
<%= direct ? "This takes effect immediately and prevents future funding or certification. The builder will receive your justification." : "An authorized reviewer will decide. Until then, this project leaves the review queue and the builder is not notified. Your justification is shared only if approved." %>
</p>
<% if nomination.errors.any? %>
<div class="permanent-rejection__warning" role="alert"><%= nomination.errors.full_messages.to_sentence %></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
data: { turbo_confirm: project.deleted? ? "Close this nomination? The deleted project will stay out of the queue and the builder will not be notified." : "Deny this nomination and return the submission to its original queue? The builder will not be notified." } %>
</div>
<% else %>
<p class="permanent-rejections__warning">Another admin must decide this nomination because you belong to the project.</p>
<p class="permanent-rejections__warning">Another authorized reviewer must decide this nomination because you belong to the project.</p>
<% end %>
<% elsif @nomination.undone? %>
<h2 id="decision-title">Permanent rejection undone</h2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ section "#{reviewer_name} nominated <#{project_url}|#{project_title}> (#{review_
section "Justification:"
reason.scan(/.{1,2900}/m).each { |part| section part, markdown: false }
if automatically_approved
section "Automatically approved because the reviewer is an administrator. The builder will receive the final decision through their review notifications."
approval_reason = local_assigns.fetch(:reviewer_admin, true) ? "the reviewer is an administrator" : "they’re Really Special And Awesome :3"
section "Automatically approved because #{approval_reason}. The builder will receive the final decision through their review notifications."
else
section "Removed from the normal review queue and awaiting an administrator's decision. The builder has not been notified. Denying this nomination returns it to the original queue without notifying the builder."
section "Removed from the normal review queue and awaiting an authorized reviewer's decision. The builder has not been notified. Denying this nomination returns it to the original queue without notifying the builder."
end

actions [
Expand Down
Loading