Skip to content

Commit 383c870

Browse files
committed
Allow appeals to be accepted with override
1 parent 8b3ff20 commit 383c870

7 files changed

Lines changed: 348 additions & 112 deletions

File tree

src/olympia/constants/abuse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class DECISION_ACTIONS(EnumChoicesApiDash):
1515
# 4 is unused
1616
AMO_DELETE_RATING = 5, 'Rating delete'
1717
AMO_DELETE_COLLECTION = 6, 'Collection delete'
18-
AMO_APPROVE = 7, 'Approved (no action)'
18+
AMO_APPROVE = 7, 'Approved'
1919
# Rejecting versions is not an available action for moderators in cinder
2020
# - it is only handled by the reviewer tools by AMO Reviewers.
2121
# It should not be sent by the cinder webhook, & does not have an action defined

src/olympia/reviewers/forms.py

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ def create_option(
224224
# label_from_instance() on WidgetRenderedModelMultipleChoiceField returns the
225225
# full object, not a label, this is what makes this work.
226226
obj = label
227-
is_appeal = obj.is_appeal
227+
is_developer_appeal = obj.is_developer_appeal
228+
is_reporter_appeal = not is_developer_appeal and obj.is_appeal
228229
queue_moves = list(obj.queue_moves.order_by('-created'))
229230
requeued_decisions = list(
230231
obj.decisions.filter(action=DECISION_ACTIONS.AMO_REQUEUE).order_by(
@@ -261,10 +262,9 @@ def create_option(
261262
for appealed_decision in obj.appealed_decisions.all()
262263
for appeal_obj in appealed_decision.appeals.all()
263264
)
264-
if is_appeal
265+
if is_reporter_appeal or is_developer_appeal
265266
else ()
266267
)
267-
is_developer_appeal = is_appeal and obj.is_developer_appeal
268268
subtexts_gen = [
269269
*internal_notes,
270270
*(
@@ -278,7 +278,7 @@ def create_option(
278278
'<details><summary>Show detail on {} reports</summary><ul>{}</ul>'
279279
'</details>',
280280
format_datetime(obj.created),
281-
'[Appeal] ' if is_appeal else '',
281+
'[Appeal] ' if (is_reporter_appeal or is_developer_appeal) else '',
282282
format_html('[Forwarded on {}] ', format_datetime(forwarded))
283283
if forwarded
284284
else '',
@@ -292,21 +292,25 @@ def create_option(
292292
)
293293

294294
attrs = attrs or {}
295-
# Reviewers shouldn't use resolve_appeal_job to resolve "regular" jobs,
295+
# Reviewers shouldn't use appeal_deny to resolve "regular" jobs,
296296
# and conversely shouldn't use resolve_reports_job to resolve appeals,
297297
# as resolving appeals is a bit more involved.
298298
# On top of that, they shouldn't resolve *developer* appeals when
299299
# rejecting versions: that would cause the rejection to be no-op and
300300
# that's not always what we want.
301301
# The parent element will have `data-toggle-hide`, so data-value is
302302
# used to hide actions that are not supposed to be used for this job.
303-
hide_for_these_actions = [
304-
'resolve_appeal_job' if not is_appeal else 'resolve_reports_job'
305-
]
306-
if is_developer_appeal:
307-
hide_for_these_actions.extend(
308-
('review_with_policy', 'reject', 'reject_multiple_versions')
309-
)
303+
if is_reporter_appeal:
304+
hide_for_these_actions = ['appeal_override', 'resolve_reports_job']
305+
elif is_developer_appeal:
306+
hide_for_these_actions = [
307+
'review_with_policy',
308+
'reject',
309+
'reject_multiple_versions',
310+
'resolve_reports_job',
311+
]
312+
else:
313+
hide_for_these_actions = ['appeal_deny', 'appeal_override']
310314
attrs['data-value'] = ' '.join(hide_for_these_actions)
311315
return super().create_option(
312316
name, value, label, selected, index, subindex, attrs
@@ -584,7 +588,7 @@ def is_valid(self):
584588
else:
585589
# we no longer strictly require comments with cinder policies
586590
self.fields['comments'].required = False
587-
if selected_action == 'resolve_appeal_job':
591+
if selected_action == 'appeal_deny':
588592
self.fields['appeal_action'].required = True
589593
result = super().is_valid()
590594
if result:
@@ -600,27 +604,43 @@ def clean(self):
600604
selected_action = self.cleaned_data.get('action')
601605
selected_definition = self.helper.actions.get(selected_action, {})
602606
# If the user select a different type of job before changing actions there could
603-
# be non-appeal jobs selected as cinder_jobs_to_resolve under resolve_appeal_job
607+
# be non-appeal jobs selected as cinder_jobs_to_resolve under appeal_deny
604608
# action, or appeal jobs under resolve_reports_job/a reject action. So filter
605609
# them out.
606-
if selected_action == 'resolve_appeal_job':
610+
require_jobs = False
611+
if selected_action == 'appeal_deny':
607612
self.cleaned_data['cinder_jobs_to_resolve'] = [
608613
job
609614
for job in self.cleaned_data.get('cinder_jobs_to_resolve', ())
610615
if job.is_appeal
611616
]
617+
require_jobs = True
618+
elif selected_action == 'appeal_override':
619+
self.cleaned_data['cinder_jobs_to_resolve'] = [
620+
job
621+
for job in self.cleaned_data.get('cinder_jobs_to_resolve', ())
622+
if job.is_developer_appeal
623+
]
624+
require_jobs = True
612625
elif selected_action == 'resolve_reports_job':
613626
self.cleaned_data['cinder_jobs_to_resolve'] = [
614627
job
615628
for job in self.cleaned_data.get('cinder_jobs_to_resolve', ())
616629
if not job.is_appeal
617630
]
618-
elif selected_action in ('reject', 'reject_multiple_versions'):
631+
require_jobs = True
632+
elif selected_action in (
633+
'reject',
634+
'reject_multiple_versions',
635+
'review_with_policy',
636+
):
619637
self.cleaned_data['cinder_jobs_to_resolve'] = [
620638
job
621639
for job in self.cleaned_data.get('cinder_jobs_to_resolve', ())
622640
if not job.is_developer_appeal
623641
]
642+
if require_jobs and not self.cleaned_data.get('cinder_jobs_to_resolve'):
643+
self.add_error('cinder_jobs_to_resolve', 'This field is required.')
624644
is_policy_enforcement = selected_definition.get('policy_enforcement')
625645
if (
626646
is_policy_enforcement or self.cleaned_data.get('cinder_jobs_to_resolve')
@@ -648,23 +668,33 @@ def clean(self):
648668
'cinder_policies',
649669
'No policies selected with an associated cinder action.',
650670
)
651-
elif not is_policy_enforcement and len(all_primary_actions) > 1:
652-
self.add_error(
653-
'cinder_policies',
654-
'Multiple policies selected with different cinder actions.',
655-
)
656671
elif any(len(actions) != 1 for actions in all_primary_actions):
657672
self.add_error(
658673
'cinder_policies',
659674
'Invalid policies selected with more than one primary enforcement '
660675
'action.',
661676
)
662677

663-
if is_policy_enforcement:
664-
# get the most aggressive negative policy
665-
# TODO: if we expand to support non-negative policies, we'll need to
666-
# change this logic (e.g. most_postive_actions, or something).
667-
sorted_actions = sorted(
678+
if not is_policy_enforcement:
679+
if len(all_primary_actions) > 1:
680+
self.add_error(
681+
'cinder_policies',
682+
'Multiple policies selected with different cinder actions.',
683+
)
684+
else:
685+
negative_primary_actions = {
686+
ea
687+
for ea in all_primary_actions
688+
if len(ea) and ea[0] in DECISION_ACTIONS.ADDON_NEGATIVE_SORTED
689+
}
690+
if not negative_primary_actions and len(all_primary_actions) > 1:
691+
self.add_error(
692+
'cinder_policies',
693+
'Selecting multiple policies selected with different '
694+
'non-negative cinder actions is not supported.',
695+
)
696+
697+
srtd_actions = sorted(
668698
(
669699
filter_enforcement_actions(
670700
policy.split_enforcement_actions, Addon
@@ -675,11 +705,9 @@ def clean(self):
675705
reverse=True,
676706
)
677707

678-
if sorted_actions:
679-
self.cleaned_data['most_aggressive_policy_actions'] = (
680-
sorted_actions[0]
681-
)
682-
if self.cleaned_data['most_aggressive_policy_actions'].primary[
708+
if srtd_actions:
709+
self.cleaned_data['most_important_policy_actions'] = srtd_actions[0]
710+
if self.cleaned_data['most_important_policy_actions'].primary[
683711
0
684712
] in DECISION_ACTIONS.VERSION_SPECIFIC and selected_definition.get(
685713
'multiple_versions'
@@ -691,10 +719,6 @@ def clean(self):
691719
# otherwise, don't confuse logging with versions
692720
self.cleaned_data['versions'] = []
693721

694-
# TODO: once we support non-negative policies, we'll need to check that
695-
# the policy selection is consistent - e.g. you shouldn't be able to
696-
# select a positive and negative policy together
697-
698722
if selected_definition.get('delayable'):
699723
delayed_rejection = self.cleaned_data.get('delayed_rejection')
700724
delayed_rejection_date = self.cleaned_data.get('delayed_rejection_date')

src/olympia/reviewers/templates/reviewers/review.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ <h4>Enforcement actions:</h4>
226226
</div>
227227
</div>
228228
<div class="data-toggle"
229-
data-value="resolve_appeal_job">
229+
data-value="appeal_deny">
230230
<label for="id_appeal_action">{{ form.appeal_action.label }}</label>
231231
<div class="review-actions-policies-select">
232232
{{ form.appeal_action }}

0 commit comments

Comments
 (0)