forked from mozilla/addons-server
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Record counts for review queues, including detail per promoted class (m…
- Loading branch information
Showing
14 changed files
with
360 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
from datetime import date | ||
|
||
from olympia.constants.promoted import NOT_PROMOTED, PROMOTED_GROUPS | ||
from olympia.reviewers.models import QueueCount | ||
from olympia.reviewers.utils import PendingManualApprovalQueueTable | ||
from olympia.reviewers.views import reviewer_tables_registry | ||
|
||
|
||
def record_reviewer_queues_counts(): | ||
today = date.today() | ||
# Grab a queryset for each reviewer queue. | ||
querysets = { | ||
queue.name: queue.get_queryset(None) | ||
for queue in reviewer_tables_registry.values() | ||
} | ||
# Also drill down manual review queue by promoted class (there is no real | ||
# queue for each, but we still want that data). | ||
for group in PROMOTED_GROUPS: | ||
if group != NOT_PROMOTED: | ||
querysets[f'{PendingManualApprovalQueueTable.name}/{group.api_name}'] = ( | ||
PendingManualApprovalQueueTable.get_queryset( | ||
None | ||
).filter(promotedaddon__group_id=group.id) | ||
) | ||
|
||
# Execute a count for each queryset and record a QueueCount instance for it | ||
for key, qs in querysets.items(): | ||
QueueCount.objects.get_or_create( | ||
name=key, date=today, defaults={'value': qs.optimized_count()} | ||
) |
28 changes: 28 additions & 0 deletions
28
src/olympia/reviewers/migrations/0039_alter_autoapprovalsummary_verdict_and_more.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Generated by Django 4.2.16 on 2024-12-05 12:00 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('reviewers', '0038_auto_20240909_1647'), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterField( | ||
model_name='autoapprovalsummary', | ||
name='verdict', | ||
field=models.PositiveSmallIntegerField(choices=[(0, 'Would *not* have been auto-approved (dry-run mode was in effect)'), (1, 'Would have been auto-approved (dry-run mode was in effect)'), (2, 'Was auto-approved'), (3, 'Was *not* auto-approved')], default=3), | ||
), | ||
migrations.AlterField( | ||
model_name='needshumanreview', | ||
name='reason', | ||
field=models.SmallIntegerField(choices=[(0, 'Unknown'), (1, 'Hit scanner rule'), (2, 'Was added to a promoted group'), (3, 'Over growth threshold for usage tier'), (4, 'Previous version in channel had needs human review set'), (5, 'Sources provided while pending rejection'), (6, 'Developer replied'), (7, 'Manually set as needing human review by a reviewer'), (8, 'Auto-approved but still had an approval delay set in the past'), (9, 'Over abuse reports threshold for usage tier'), (10, 'Escalated for an abuse report, via cinder'), (11, 'Reported for abuse within the add-on'), (12, "Appeal of a reviewer's decision about a policy violation"), (13, 'Has auto-approval disabled'), (14, 'Belongs to a promoted group')], default=0, editable=False), | ||
), | ||
migrations.AlterField( | ||
model_name='usagetier', | ||
name='growth_threshold_before_flagging', | ||
field=models.IntegerField(blank=True, default=None, help_text='Percentage point increase in growth over the average in that tier before we start automatically flagging the add-on for human review. For example, if set to 50 and the average hotness in that tier is 0.01, add-ons over 0.51 hotness get flagged.', null=True), | ||
), | ||
] |
26 changes: 26 additions & 0 deletions
26
src/olympia/reviewers/migrations/0040_queuecount_queuecount_queue_count_unique_name_date.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Generated by Django 4.2.16 on 2024-12-05 12:01 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('reviewers', '0039_alter_autoapprovalsummary_verdict_and_more'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='QueueCount', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('date', models.DateField(auto_now_add=True)), | ||
('name', models.CharField(max_length=255)), | ||
('value', models.IntegerField()), | ||
], | ||
), | ||
migrations.AddConstraint( | ||
model_name='queuecount', | ||
constraint=models.UniqueConstraint(fields=('name', 'date'), name='queue_count_unique_name_date'), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
from datetime import date | ||
|
||
from django.conf import settings | ||
|
||
from freezegun import freeze_time | ||
|
||
from olympia import amo | ||
from olympia.amo.tests import TestCase, addon_factory, user_factory | ||
from olympia.constants.promoted import NOTABLE, PROMOTED_GROUPS, RECOMMENDED | ||
from olympia.reviewers.cron import record_reviewer_queues_counts | ||
from olympia.reviewers.models import NeedsHumanReview, QueueCount | ||
from olympia.reviewers.views import reviewer_tables_registry | ||
|
||
|
||
class TestQueueCount(TestCase): | ||
def setUp(self): | ||
user_factory(pk=settings.TASK_USER_ID) | ||
|
||
def _test_expected_count(self, date): | ||
# We are recording every queue, plus drilling down in every promoted | ||
# group, minus the special not promoted group. | ||
expected_count = len(reviewer_tables_registry) + len(PROMOTED_GROUPS) - 1 | ||
assert QueueCount.objects.filter(date=date).count() == expected_count | ||
|
||
def test_empty(self): | ||
with freeze_time('2024-12-03'): | ||
expected_date = date.today() | ||
record_reviewer_queues_counts() | ||
|
||
self._test_expected_count(expected_date) | ||
|
||
for metric in QueueCount.objects.all(): | ||
assert metric.date == expected_date | ||
assert metric.name | ||
assert metric.value == 0 | ||
|
||
def test_basic(self): | ||
addon_factory() | ||
addon_factory( | ||
needshumanreview_kw={'reason': NeedsHumanReview.REASONS.UNKNOWN}, | ||
) | ||
addon_factory( | ||
file_kw={'status': amo.STATUS_AWAITING_REVIEW}, | ||
needshumanreview_kw={ | ||
'reason': NeedsHumanReview.REASONS.AUTO_APPROVAL_DISABLED | ||
}, | ||
) | ||
self.addon_recommended_1 = addon_factory( | ||
file_kw={'status': amo.STATUS_AWAITING_REVIEW}, | ||
promoted=RECOMMENDED, | ||
needshumanreview_kw={ | ||
'reason': NeedsHumanReview.REASONS.BELONGS_TO_PROMOTED_GROUP | ||
}, | ||
) | ||
addon_factory( | ||
file_kw={'status': amo.STATUS_AWAITING_REVIEW}, | ||
promoted=RECOMMENDED, | ||
needshumanreview_kw={ | ||
'reason': NeedsHumanReview.REASONS.BELONGS_TO_PROMOTED_GROUP | ||
}, | ||
) | ||
addon_factory( | ||
file_kw={'status': amo.STATUS_AWAITING_REVIEW}, | ||
promoted=NOTABLE, | ||
needshumanreview_kw={ | ||
'reason': NeedsHumanReview.REASONS.BELONGS_TO_PROMOTED_GROUP | ||
}, | ||
) | ||
|
||
with freeze_time('2024-12-03'): | ||
expected_date = date.today() | ||
record_reviewer_queues_counts() | ||
|
||
self._test_expected_count(expected_date) | ||
|
||
metric = QueueCount.objects.get(name='queue_extension') | ||
assert metric.date == expected_date | ||
assert metric.value == 5 | ||
|
||
metric = QueueCount.objects.get(name='queue_extension/recommended') | ||
assert metric.date == expected_date | ||
assert metric.value == 2 | ||
|
||
metric = QueueCount.objects.get(name='queue_extension/notable') | ||
assert metric.date == expected_date | ||
assert metric.value == 1 | ||
|
||
def test_twice_same_date_doesnt_override(self): | ||
self.test_basic() | ||
self.test_basic() | ||
|
||
def test_twice_different_day(self): | ||
self.test_basic() | ||
previous_date = QueueCount.objects.latest('pk').date | ||
|
||
self.addon_recommended_1.current_version.file.update(status=amo.STATUS_APPROVED) | ||
self.addon_recommended_1.current_version.needshumanreview_set.all()[0].update( | ||
is_active=False | ||
) | ||
|
||
with freeze_time('2024-12-04'): | ||
expected_date = date.today() | ||
record_reviewer_queues_counts() | ||
|
||
# Previous date records are not affected. | ||
self._test_expected_count(previous_date) | ||
assert ( | ||
QueueCount.objects.get(date=previous_date, name='queue_extension').value | ||
== 5 | ||
) | ||
assert ( | ||
QueueCount.objects.get( | ||
date=previous_date, name='queue_extension/recommended' | ||
).value | ||
== 2 | ||
) | ||
assert ( | ||
QueueCount.objects.get( | ||
date=previous_date, name='queue_extension/notable' | ||
).value | ||
== 1 | ||
) | ||
|
||
# New date records | ||
self._test_expected_count(expected_date) | ||
|
||
# One fewer add-on in the queue. | ||
assert ( | ||
QueueCount.objects.get(date=expected_date, name='queue_extension').value | ||
== 4 | ||
) | ||
|
||
# One fewer add-on in the queue that was recommended. | ||
assert ( | ||
QueueCount.objects.get( | ||
date=expected_date, name='queue_extension/recommended' | ||
).value | ||
== 1 | ||
) | ||
|
||
# No changes to notable. | ||
assert ( | ||
QueueCount.objects.get( | ||
date=expected_date, name='queue_extension/notable' | ||
).value | ||
== 1 | ||
) |
Oops, something went wrong.