Skip to content

Commit 7f8c0c2

Browse files
committed
track queue moves as a decision
1 parent e9c6f4f commit 7f8c0c2

File tree

5 files changed

+108
-61
lines changed

5 files changed

+108
-61
lines changed

src/olympia/abuse/models.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,13 @@ def unresolved(self):
5959
Q(decisions__isnull=True)
6060
| Q(
6161
# i.e. the latest decision is a requeue
62-
decisions__action=DECISION_ACTIONS.AMO_REQUEUE,
62+
decisions__action__in=(
63+
DECISION_ACTIONS.AMO_REQUEUE,
64+
DECISION_ACTIONS.AMO_ESCALATE_ADDON,
65+
),
6366
decisions__overridden_by__isnull=True,
6467
)
65-
)
68+
).distinct()
6669

6770
def resolvable_in_reviewer_tools(self):
6871
return self.filter(resolvable_in_reviewer_tools=True)
@@ -333,8 +336,15 @@ def process_decision(
333336
decision.send_notifications()
334337

335338
def process_queue_move(self, *, new_queue, notes):
336-
CinderQueueMove.objects.create(cinder_job=self, notes=notes, to_queue=new_queue)
337339
if new_queue == CinderAddonHandledByReviewers(self.target).queue:
340+
ContentDecision.objects.create(
341+
addon=self.target_addon,
342+
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
343+
override_of=self.decision,
344+
action_date=datetime.now(),
345+
private_notes=notes,
346+
cinder_job=self,
347+
)
338348
# now escalated
339349
entity_helper = CinderJob.get_entity_helper(
340350
self.target, resolved_in_reviewer_tools=True
@@ -369,20 +379,20 @@ def clear_needs_human_review_flags(self):
369379
.unresolved()
370380
.resolvable_in_reviewer_tools()
371381
)
372-
if (
373-
(decision_actions := tuple(self.decisions.values_list('action', flat=True)))
374-
# i.e. was the previous decision before the current a requeue
375-
and len(decision_actions) >= 2
376-
and decision_actions[-2] == DECISION_ACTIONS.AMO_REQUEUE
377-
):
382+
# the previous decision before this new decision
383+
penultimate_action = tuple(self.decisions.values_list('action', flat=True))[
384+
-2:-1
385+
]
386+
if penultimate_action == (DECISION_ACTIONS.AMO_REQUEUE,):
378387
has_unresolved_jobs_with_similar_reason = base_unresolved_jobs_qs.filter(
379388
decisions__action=DECISION_ACTIONS.AMO_REQUEUE,
380389
decisions__overridden_by__isnull=True,
381390
).exists()
382391
reasons = {NeedsHumanReview.REASONS.SECOND_LEVEL_REQUEUE}
383-
elif self.queue_moves.exists():
392+
elif penultimate_action == (DECISION_ACTIONS.AMO_ESCALATE_ADDON,):
384393
has_unresolved_jobs_with_similar_reason = base_unresolved_jobs_qs.filter(
385-
queue_moves__id__gt=0
394+
decisions__action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
395+
decisions__overridden_by__isnull=True,
386396
).exists()
387397
reasons = {
388398
NeedsHumanReview.REASONS.CINDER_ESCALATION,
@@ -1477,11 +1487,3 @@ class CinderAppeal(ModelBase):
14771487
reporter_report = models.OneToOneField(
14781488
to=AbuseReport, on_delete=models.CASCADE, null=True
14791489
)
1480-
1481-
1482-
class CinderQueueMove(ModelBase):
1483-
cinder_job = models.ForeignKey(
1484-
to=CinderJob, on_delete=models.CASCADE, related_name='queue_moves'
1485-
)
1486-
notes = models.TextField(max_length=1000, blank=True)
1487-
to_queue = models.CharField(max_length=128)

src/olympia/abuse/tests/test_models.py

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262
CinderAppeal,
6363
CinderJob,
6464
CinderPolicy,
65-
CinderQueueMove,
6665
ContentDecision,
6766
)
6867

@@ -777,8 +776,11 @@ def test_reviewer_handled(self):
777776
assert list(qs) == [job, appeal_job]
778777

779778
not_policy_report.cinder_job.update(resolvable_in_reviewer_tools=True)
780-
CinderQueueMove.objects.create(
781-
cinder_job=not_policy_report.cinder_job, to_queue='?'
779+
ContentDecision.objects.create(
780+
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
781+
action_date=datetime.now(),
782+
cinder_job=not_policy_report.cinder_job,
783+
addon=not_policy_report.target,
782784
)
783785
qs = CinderJob.objects.resolvable_in_reviewer_tools()
784786
assert list(qs) == [not_policy_report.cinder_job, job, appeal_job]
@@ -1401,9 +1403,10 @@ def test_process_queue_move_into_reviewer_handled(self):
14011403
nhr = NeedsHumanReview.objects.get()
14021404
assert nhr.reason == NeedsHumanReview.REASONS.CINDER_ESCALATION
14031405
assert nhr.version == addon.current_version
1404-
assert CinderQueueMove.objects.filter(
1405-
cinder_job=cinder_job, to_queue='amo-env-addon-infringement', notes='notes!'
1406-
).exists()
1406+
assert cinder_job.decision.private_notes == 'notes!'
1407+
assert cinder_job.decision.action == DECISION_ACTIONS.AMO_ESCALATE_ADDON
1408+
assert cinder_job.decision.cinder_job == cinder_job
1409+
assert cinder_job.decision.addon == cinder_job.target_addon
14071410

14081411
def test_process_queue_move_out_of_reviewer_handled(self):
14091412
# Not yet implemented, so just check it's silently ignored
@@ -1421,9 +1424,7 @@ def test_process_queue_move_out_of_reviewer_handled(self):
14211424
assert cinder_job.resolvable_in_reviewer_tools is True
14221425
assert len(mail.outbox) == 0
14231426
assert NeedsHumanReview.objects.count() == 1
1424-
assert CinderQueueMove.objects.filter(
1425-
cinder_job=cinder_job, to_queue='amo-env-listings', notes='out'
1426-
).exists()
1427+
assert not cinder_job.decision
14271428

14281429
def test_process_queue_move_other_queue_movement(self):
14291430
# we don't need to about these other queue moves, so just check it's silently
@@ -1436,9 +1437,7 @@ def test_process_queue_move_other_queue_movement(self):
14361437
assert not cinder_job.resolvable_in_reviewer_tools
14371438
assert len(mail.outbox) == 0
14381439
assert NeedsHumanReview.objects.count() == 0
1439-
assert CinderQueueMove.objects.filter(
1440-
cinder_job=cinder_job, to_queue='amo-env-some-other-queue', notes='?'
1441-
).exists()
1440+
assert not cinder_job.decision
14421441

14431442
@override_switch('dsa-cinder-forwarded-review', active=True)
14441443
def test_process_queue_move_with_addon_already_moderated(self):
@@ -1476,7 +1475,11 @@ def test_process_queue_move_with_addon_already_moderated(self):
14761475
assert mail.outbox[0].to == ['some@email.com']
14771476
assert 'already assessed' in mail.outbox[0].body
14781477
assert ContentDecision.objects.exists()
1479-
decision = ContentDecision.objects.get()
1478+
assert ContentDecision.objects.count() == 2
1479+
assert ContentDecision.objects.first().action == (
1480+
DECISION_ACTIONS.AMO_ESCALATE_ADDON
1481+
)
1482+
decision = ContentDecision.objects.last()
14801483
assert decision.action == DECISION_ACTIONS.AMO_CLOSED_NO_ACTION
14811484
self.assertCloseToNow(decision.action_date)
14821485
assert decision.cinder_job == CinderJob.objects.get()
@@ -1612,14 +1615,24 @@ def test_clear_needs_human_review_flags_abuse(self):
16121615
def test_clear_needs_human_review_flags_forwarded_moved_queue(self):
16131616
job = self._setup_clear_needs_human_review_flags()
16141617
# if the job is forwarded, we make sure that there are no other forwarded jobs
1615-
CinderQueueMove.objects.create(cinder_job=job, to_queue='wherever')
1618+
job.decision.update(action=DECISION_ACTIONS.AMO_ESCALATE_ADDON)
1619+
ContentDecision.objects.create(
1620+
action=DECISION_ACTIONS.AMO_APPROVE,
1621+
addon=job.target_addon,
1622+
cinder_job=job,
1623+
override_of=job.final_decision,
1624+
)
16161625

16171626
other_forward = CinderJob.objects.create(
16181627
job_id='3',
16191628
target_addon=job.target_addon,
16201629
resolvable_in_reviewer_tools=True,
16211630
)
1622-
CinderQueueMove.objects.create(cinder_job=other_forward, to_queue='whoever')
1631+
ContentDecision.objects.create(
1632+
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
1633+
addon=job.target_addon,
1634+
cinder_job=other_forward,
1635+
)
16231636
job.clear_needs_human_review_flags()
16241637
assert self._nhr_exists(NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION)
16251638
assert self._nhr_exists(NeedsHumanReview.REASONS.CINDER_ESCALATION)
@@ -1632,6 +1645,7 @@ def test_clear_needs_human_review_flags_forwarded_moved_queue(self):
16321645
action=DECISION_ACTIONS.AMO_APPROVE,
16331646
addon=job.target_addon,
16341647
cinder_job=other_forward,
1648+
override_of=other_forward.decision,
16351649
)
16361650
job.clear_needs_human_review_flags()
16371651
assert self._nhr_exists(NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION)
@@ -3507,17 +3521,24 @@ def test_send_notifications_without_notifying_owners(self):
35073521
def test_resolve_job_forwarded(self):
35083522
addon_developer = user_factory()
35093523
addon = addon_factory(users=[addon_developer])
3524+
cinder_job = CinderJob.objects.create(job_id='999')
3525+
ContentDecision.objects.create(
3526+
addon=addon,
3527+
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
3528+
action_date=datetime.now(),
3529+
private_notes='why moved',
3530+
cinder_job=cinder_job,
3531+
)
35103532
decision = ContentDecision.objects.create(
35113533
addon=addon,
35123534
action=DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON,
35133535
action_date=datetime.now(),
35143536
reasoning='some review text',
35153537
reviewer_user=self.reviewer_user,
3538+
cinder_job=cinder_job,
35163539
)
35173540
policies = [CinderPolicy.objects.create(name='policy', uuid='12345678')]
35183541
decision.policies.set(policies)
3519-
cinder_job = CinderJob.objects.create(job_id='999', decision=decision)
3520-
CinderQueueMove.objects.create(cinder_job=cinder_job, to_queue='wherever')
35213542
NeedsHumanReview.objects.create(
35223543
version=addon.current_version,
35233544
reason=NeedsHumanReview.REASONS.CINDER_ESCALATION,
@@ -3563,14 +3584,20 @@ def test_resolve_job_forwarded(self):
35633584
def test_execute_action_forwarded_to_legal(self):
35643585
addon_developer = user_factory()
35653586
addon = addon_factory(users=[addon_developer])
3587+
cinder_job = CinderJob.objects.create(job_id='999')
3588+
ContentDecision.objects.create(
3589+
addon=addon,
3590+
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
3591+
private_notes='why moved',
3592+
cinder_job=cinder_job,
3593+
)
35663594
decision = ContentDecision.objects.create(
35673595
addon=addon,
35683596
action=DECISION_ACTIONS.AMO_LEGAL_FORWARD,
35693597
reasoning='some reasoning',
35703598
reviewer_user=self.reviewer_user,
3599+
cinder_job=cinder_job,
35713600
)
3572-
cinder_job = CinderJob.objects.create(job_id='999', decision=decision)
3573-
CinderQueueMove.objects.create(cinder_job=cinder_job, to_queue='wherever')
35743601
NeedsHumanReview.objects.create(
35753602
version=addon.current_version,
35763603
reason=NeedsHumanReview.REASONS.CINDER_ESCALATION,
@@ -3598,8 +3625,8 @@ def test_execute_action_forwarded_to_legal(self):
35983625
decision.execute_action()
35993626

36003627
cinder_job.reload()
3601-
assert cinder_job.decision.action == DECISION_ACTIONS.AMO_LEGAL_FORWARD
3602-
self.assertCloseToNow(cinder_job.decision.action_date)
3628+
assert cinder_job.final_decision.action == DECISION_ACTIONS.AMO_LEGAL_FORWARD
3629+
self.assertCloseToNow(cinder_job.final_decision.action_date)
36033630
assert not NeedsHumanReview.objects.filter(
36043631
is_active=True, reason=NeedsHumanReview.REASONS.CINDER_ESCALATION
36053632
).exists()

src/olympia/constants/abuse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
('AMO_BAN_USER', 1, 'User ban'),
99
('AMO_DISABLE_ADDON', 2, 'Add-on disable'),
1010
# Used to indicate the job has been forwarded to AMO
11-
('AMO_ESCALATE_ADDON', 3, '(Obsolete) Forward add-on to reviewers'),
11+
('AMO_ESCALATE_ADDON', 3, 'Forward add-on to reviewers'),
1212
# 4 is unused
1313
('AMO_DELETE_RATING', 5, 'Rating delete'),
1414
('AMO_DELETE_COLLECTION', 6, 'Collection delete'),

src/olympia/reviewers/forms.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -246,15 +246,34 @@ def create_option(
246246
# label_from_instance() on WidgetRenderedModelMultipleChoiceField returns the
247247
# full object, not a label, this is what makes this work.
248248
obj = label
249+
250+
forwarded_or_requeued_decisions = list(
251+
obj.decisions.filter(
252+
action__in=(
253+
DECISION_ACTIONS.AMO_REQUEUE,
254+
DECISION_ACTIONS.AMO_ESCALATE_ADDON,
255+
)
256+
).order_by('-created')
257+
)
258+
249259
is_appeal = obj.is_appeal
250-
queue_moves = list(obj.queue_moves.order_by('-created'))
251-
requeued_decisions = list(
252-
obj.decisions.filter(action=DECISION_ACTIONS.AMO_REQUEUE).order_by(
253-
'-created'
254-
)
260+
forwarded = next(
261+
(
262+
decision
263+
for decision in forwarded_or_requeued_decisions
264+
if decision.action == DECISION_ACTIONS.AMO_ESCALATE_ADDON
265+
),
266+
None,
255267
)
256-
forwarded = queue_moves[0].created if queue_moves else None
257-
requeued = requeued_decisions[0].created if requeued_decisions else None
268+
requeued = next(
269+
(
270+
decision
271+
for decision in forwarded_or_requeued_decisions
272+
if decision.action == DECISION_ACTIONS.AMO_REQUEUE
273+
),
274+
None,
275+
)
276+
258277
reports = obj.all_abuse_reports
259278
reasons_set = {
260279
(report.REASONS.for_value(report.reason).display,) for report in reports
@@ -266,13 +285,12 @@ def create_option(
266285
)
267286
for report in reports
268287
)
269-
forwarded_or_requeued_notes = [
270-
*(move.notes for move in queue_moves),
271-
*(decision.private_notes for decision in requeued_decisions),
272-
]
288+
internal_notes_gen = (
289+
decision.private_notes for decision in forwarded_or_requeued_decisions
290+
)
273291
internal_notes = (
274-
((f'Reasoning: {"; ".join(forwarded_or_requeued_notes)}',),)
275-
if forwarded_or_requeued_notes
292+
((f'Reasoning: {"; ".join(internal_notes_gen)}',),)
293+
if forwarded_or_requeued_decisions
276294
else ()
277295
)
278296
appeals = (
@@ -294,10 +312,10 @@ def create_option(
294312
'</details>',
295313
format_datetime(obj.created),
296314
'[Appeal] ' if is_appeal else '',
297-
format_html('[Forwarded on {}] ', format_datetime(forwarded))
315+
format_html('[Forwarded on {}] ', format_datetime(forwarded.created))
298316
if forwarded
299317
else '',
300-
format_html('[Requeued on {}] ', format_datetime(requeued))
318+
format_html('[Requeued on {}] ', format_datetime(requeued.created))
301319
if requeued
302320
else '',
303321
format_html_join(', ', '"{}"', reasons_set),

src/olympia/reviewers/tests/test_forms.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
CinderAppeal,
1515
CinderJob,
1616
CinderPolicy,
17-
CinderQueueMove,
1817
ContentDecision,
1918
)
2019
from olympia.addons.models import Addon
@@ -1338,11 +1337,12 @@ def test_cinder_jobs_to_resolve_choices(self):
13381337
addon=self.addon,
13391338
cinder_job=cinder_job_forwarded,
13401339
)
1341-
CinderQueueMove.objects.create(
1340+
ContentDecision.objects.create(
13421341
created=datetime(2025, 5, 22, 11, 42, 5, 541216),
1342+
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
1343+
private_notes='Zee de zee',
1344+
addon=self.addon,
13431345
cinder_job=cinder_job_forwarded,
1344-
notes='Zee de zee',
1345-
to_queue='amo-env-content-infringment',
13461346
)
13471347
AbuseReport.objects.create(
13481348
**{**abuse_kw, 'location': AbuseReport.LOCATION.AMO},
@@ -1391,7 +1391,7 @@ def test_cinder_jobs_to_resolve_choices(self):
13911391
'[Forwarded on May 22, 2025, 11:42 a.m.] '
13921392
'[Requeued on May 23, 2025, 10:54 p.m.] '
13931393
'"DSA: It violates Mozilla\'s Add-on Policies"\n'
1394-
'Reasoning: Zee de zee; Why o why\n\n'
1394+
'Reasoning: Why o why; Zee de zee\n\n'
13951395
'Show detail on 1 reports\n'
13961396
'v[<script>alert()</script>]: ddd'
13971397
)

0 commit comments

Comments
 (0)