Merge pull request #6569 from diox/reviewers-new-points-system

Award Reviewer Points for post-review/content-review actions
This commit is contained in:
Mathieu Pillard 2017-10-06 15:21:12 +02:00 коммит произвёл GitHub
Родитель 0a95b25c95 14746c9dae
Коммит 0e3d9cab59
10 изменённых файлов: 426 добавлений и 150 удалений

Просмотреть файл

@ -372,10 +372,6 @@ VERSION_BETA = re.compile(r"""(a|alpha|b|beta|pre|rc) # Either of these
""", re.VERBOSE)
VERSION_SEARCH = re.compile('\.(\d+)$')
# Reviewer Tools
EDITOR_VIEWING_INTERVAL = 8 # How often we ping for "who's watching?"
EDITOR_REVIEW_LOCK_LIMIT = 3 # How many pages can an editor "watch"
# Types of SiteEvent
SITE_EVENT_OTHER = 1
SITE_EVENT_EXCEPTION = 2
@ -389,120 +385,10 @@ SITE_EVENT_CHOICES = {
SITE_EVENT_CHANGE: _('Change'),
}
# Types of Canned Responses for reviewer tools.
CANNED_RESPONSE_ADDON = 1
CANNED_RESPONSE_APP = 2 # Unused, should be removed
CANNED_RESPONSE_PERSONA = 3
CANNED_RESPONSE_CHOICES = {
CANNED_RESPONSE_ADDON: _('Add-on'),
CANNED_RESPONSE_APP: _('App'),
CANNED_RESPONSE_PERSONA: _('Persona'),
}
# For use in urls.
ADDON_ID = r"""(?P<addon_id>[^/<>"']+)"""
ADDON_UUID = r'(?P<uuid>[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})'
# Reviewer Incentive Scores.
# Note: Don't change these since they're used as keys in the database.
REVIEWED_MANUAL = 0
REVIEWED_ADDON_FULL = 10
_REVIEWED_ADDON_PRELIM = 11 # Deprecated for new reviews - no more prelim.
REVIEWED_ADDON_UPDATE = 12
REVIEWED_DICT_FULL = 20
_REVIEWED_DICT_PRELIM = 21 # Deprecated for new reviews - no more prelim.
REVIEWED_DICT_UPDATE = 22
REVIEWED_LP_FULL = 30
_REVIEWED_LP_PRELIM = 31 # Deprecated for new reviews - no more prelim.
REVIEWED_LP_UPDATE = 32
REVIEWED_OVERDUE_BONUS = 2
REVIEWED_OVERDUE_LIMIT = 7
REVIEWED_PERSONA = 40
# TODO: Leaving room for persona points based on queue.
REVIEWED_SEARCH_FULL = 50
_REVIEWED_SEARCH_PRELIM = 51 # Deprecated for new reviews - no more prelim.
REVIEWED_SEARCH_UPDATE = 52
REVIEWED_THEME_FULL = 60
_REVIEWED_THEME_PRELIM = 61 # Deprecated for new reviews - no more prelim.
REVIEWED_THEME_UPDATE = 62
REVIEWED_ADDON_REVIEW = 80
REVIEWED_ADDON_REVIEW_POORLY = 81
# We need to keep the deprecated choices for existing points in the database.
REVIEWED_CHOICES = {
REVIEWED_MANUAL: _('Manual Reviewer Points'),
REVIEWED_ADDON_FULL: _('New Add-on Review'),
_REVIEWED_ADDON_PRELIM: _('Preliminary Add-on Review'),
REVIEWED_ADDON_UPDATE: _('Updated Add-on Review'),
REVIEWED_DICT_FULL: _('New Dictionary Review'),
_REVIEWED_DICT_PRELIM: _('Preliminary Dictionary Review'),
REVIEWED_DICT_UPDATE: _('Updated Dictionary Review'),
REVIEWED_LP_FULL: _('New Language Pack Review'),
_REVIEWED_LP_PRELIM: _('Preliminary Language Pack Review'),
REVIEWED_LP_UPDATE: _('Updated Language Pack Review'),
REVIEWED_OVERDUE_BONUS: _('Bonus for overdue reviews'),
REVIEWED_OVERDUE_LIMIT: _('Days Before Bonus Points Applied'),
REVIEWED_PERSONA: _('Theme Review'),
REVIEWED_SEARCH_FULL: _('New Search Provider Review'),
_REVIEWED_SEARCH_PRELIM: _('Preliminary Search Provider Review'),
REVIEWED_SEARCH_UPDATE: _('Updated Search Provider Review'),
REVIEWED_THEME_FULL: _('New Complete Theme Review'),
_REVIEWED_THEME_PRELIM: _('Preliminary Complete Theme Review'),
REVIEWED_THEME_UPDATE: _('Updated Complete Theme Review'),
REVIEWED_ADDON_REVIEW: _('Moderated Add-on Review'),
REVIEWED_ADDON_REVIEW_POORLY: _('Add-on Review Moderation Reverted'),
}
REVIEWED_SCORES = {
REVIEWED_MANUAL: 0,
REVIEWED_ADDON_FULL: 120,
REVIEWED_ADDON_UPDATE: 80,
REVIEWED_DICT_FULL: 60,
REVIEWED_DICT_UPDATE: 60,
REVIEWED_LP_FULL: 60,
REVIEWED_LP_UPDATE: 60,
REVIEWED_OVERDUE_BONUS: 2,
REVIEWED_OVERDUE_LIMIT: 7,
REVIEWED_PERSONA: 5,
REVIEWED_SEARCH_FULL: 30,
REVIEWED_SEARCH_UPDATE: 30,
REVIEWED_THEME_FULL: 80,
REVIEWED_THEME_UPDATE: 80,
REVIEWED_ADDON_REVIEW: 1,
REVIEWED_ADDON_REVIEW_POORLY: -1, # -REVIEWED_ADDON_REVIEW
}
REVIEWED_AMO = (
REVIEWED_ADDON_FULL,
REVIEWED_ADDON_UPDATE,
REVIEWED_DICT_FULL,
REVIEWED_DICT_UPDATE,
REVIEWED_LP_FULL,
REVIEWED_LP_UPDATE,
REVIEWED_SEARCH_FULL,
REVIEWED_SEARCH_UPDATE,
REVIEWED_THEME_FULL,
REVIEWED_THEME_UPDATE,
REVIEWED_ADDON_REVIEW,
)
REVIEWED_LEVELS = [
{'name': _('Level 1'), 'points': 2160},
{'name': _('Level 2'), 'points': 4320},
{'name': _('Level 3'), 'points': 8700},
{'name': _('Level 4'), 'points': 21000},
{'name': _('Level 5'), 'points': 45000},
{'name': _('Level 6'), 'points': 96000},
{'name': _('Level 7'), 'points': 300000},
{'name': _('Level 8'), 'points': 1200000},
{'name': _('Level 9'), 'points': 3000000},
]
# Amount of hours to hide add-on reviews from users with permission
# Addons:DelayedReviews
REVIEW_LIMITED_DELAY_HOURS = 20
# Default strict_min_version and strict_max_version for WebExtensions
DEFAULT_WEBEXT_MIN_VERSION = '42.0'
DEFAULT_WEBEXT_MAX_VERSION = '*'

Просмотреть файл

@ -1,5 +1,145 @@
from django.utils.translation import ugettext_lazy as _
# Reviewer Tools
EDITOR_VIEWING_INTERVAL = 8 # How often we ping for "who's watching?"
EDITOR_REVIEW_LOCK_LIMIT = 3 # How many pages can an editor "watch"
# Types of Canned Responses for reviewer tools.
CANNED_RESPONSE_ADDON = 1
CANNED_RESPONSE_APP = 2 # Unused, should be removed
CANNED_RESPONSE_PERSONA = 3
CANNED_RESPONSE_CHOICES = {
CANNED_RESPONSE_ADDON: _('Add-on'),
CANNED_RESPONSE_APP: _('App'),
CANNED_RESPONSE_PERSONA: _('Persona'),
}
# Risk tiers for post-review weight.
POST_REVIEW_WEIGHT_HIGHEST_RISK = 150
POST_REVIEW_WEIGHT_HIGH_RISK = 100
POST_REVIEW_WEIGHT_MEDIUM_RISK = 20
# Reviewer Incentive Scores.
# Note: Don't change these since they're used as keys in the database.
REVIEWED_MANUAL = 0
REVIEWED_ADDON_FULL = 10
_REVIEWED_ADDON_PRELIM = 11 # Deprecated for new reviews - no more prelim.
REVIEWED_ADDON_UPDATE = 12
REVIEWED_DICT_FULL = 20
_REVIEWED_DICT_PRELIM = 21 # Deprecated for new reviews - no more prelim.
REVIEWED_DICT_UPDATE = 22
REVIEWED_LP_FULL = 30
_REVIEWED_LP_PRELIM = 31 # Deprecated for new reviews - no more prelim.
REVIEWED_LP_UPDATE = 32
REVIEWED_PERSONA = 40
# TODO: Leaving room for persona points based on queue.
REVIEWED_SEARCH_FULL = 50
_REVIEWED_SEARCH_PRELIM = 51 # Deprecated for new reviews - no more prelim.
REVIEWED_SEARCH_UPDATE = 52
REVIEWED_THEME_FULL = 60
_REVIEWED_THEME_PRELIM = 61 # Deprecated for new reviews - no more prelim.
REVIEWED_THEME_UPDATE = 62
REVIEWED_ADDON_REVIEW = 80
REVIEWED_ADDON_REVIEW_POORLY = 81
REVIEWED_CONTENT_REVIEW = 101
REVIEWED_EXTENSION_HIGHEST_RISK = 102
REVIEWED_EXTENSION_HIGH_RISK = 103
REVIEWED_EXTENSION_MEDIUM_RISK = 104
REVIEWED_EXTENSION_LOW_RISK = 105
# We need to keep the deprecated choices for existing points in the database.
REVIEWED_CHOICES = {
REVIEWED_MANUAL: _('Manual Reviewer Points'),
REVIEWED_ADDON_FULL: _('New Add-on Review'),
_REVIEWED_ADDON_PRELIM: _('Preliminary Add-on Review'),
REVIEWED_ADDON_UPDATE: _('Updated Add-on Review'),
REVIEWED_DICT_FULL: _('New Dictionary Review'),
_REVIEWED_DICT_PRELIM: _('Preliminary Dictionary Review'),
REVIEWED_DICT_UPDATE: _('Updated Dictionary Review'),
REVIEWED_LP_FULL: _('New Language Pack Review'),
_REVIEWED_LP_PRELIM: _('Preliminary Language Pack Review'),
REVIEWED_LP_UPDATE: _('Updated Language Pack Review'),
REVIEWED_PERSONA: _('Theme Review'),
REVIEWED_SEARCH_FULL: _('New Search Provider Review'),
_REVIEWED_SEARCH_PRELIM: _('Preliminary Search Provider Review'),
REVIEWED_SEARCH_UPDATE: _('Updated Search Provider Review'),
REVIEWED_THEME_FULL: _('New Complete Theme Review'),
_REVIEWED_THEME_PRELIM: _('Preliminary Complete Theme Review'),
REVIEWED_THEME_UPDATE: _('Updated Complete Theme Review'),
REVIEWED_ADDON_REVIEW: _('Moderated Add-on Review'),
REVIEWED_ADDON_REVIEW_POORLY: _('Add-on Review Moderation Reverted'),
REVIEWED_CONTENT_REVIEW: _('Add-on Content Review'),
REVIEWED_EXTENSION_HIGHEST_RISK:
_('Post-Approval Add-on Review (Highest Risk)'),
REVIEWED_EXTENSION_HIGH_RISK:
_('Post-Approval Add-on Review (High Risk)'),
REVIEWED_EXTENSION_MEDIUM_RISK:
_('Post-Approval Add-on Review (Medium Risk)'),
REVIEWED_EXTENSION_LOW_RISK:
_('Post-Approval Add-on Review (Low Risk)'),
}
REVIEWED_OVERDUE_BONUS = 2
REVIEWED_OVERDUE_LIMIT = 7
REVIEWED_SCORES = {
REVIEWED_MANUAL: 0,
REVIEWED_ADDON_FULL: 120,
REVIEWED_ADDON_UPDATE: 80,
REVIEWED_DICT_FULL: 60,
REVIEWED_DICT_UPDATE: 60,
REVIEWED_LP_FULL: 60,
REVIEWED_LP_UPDATE: 60,
REVIEWED_PERSONA: 5,
REVIEWED_SEARCH_FULL: 30,
REVIEWED_SEARCH_UPDATE: 30,
REVIEWED_THEME_FULL: 80,
REVIEWED_THEME_UPDATE: 80,
REVIEWED_ADDON_REVIEW: 1,
REVIEWED_ADDON_REVIEW_POORLY: -1, # -REVIEWED_ADDON_REVIEW,
REVIEWED_CONTENT_REVIEW: 10,
REVIEWED_EXTENSION_HIGHEST_RISK: 140,
REVIEWED_EXTENSION_HIGH_RISK: 120,
REVIEWED_EXTENSION_MEDIUM_RISK: 90,
REVIEWED_EXTENSION_LOW_RISK: 0,
}
REVIEWED_AMO = (
REVIEWED_ADDON_FULL,
REVIEWED_ADDON_UPDATE,
REVIEWED_DICT_FULL,
REVIEWED_DICT_UPDATE,
REVIEWED_LP_FULL,
REVIEWED_LP_UPDATE,
REVIEWED_SEARCH_FULL,
REVIEWED_SEARCH_UPDATE,
REVIEWED_THEME_FULL,
REVIEWED_THEME_UPDATE,
REVIEWED_ADDON_REVIEW,
REVIEWED_CONTENT_REVIEW,
REVIEWED_EXTENSION_HIGHEST_RISK,
REVIEWED_EXTENSION_HIGH_RISK,
REVIEWED_EXTENSION_MEDIUM_RISK,
REVIEWED_EXTENSION_LOW_RISK,
)
REVIEWED_LEVELS = [
{'name': _('Level 1'), 'points': 2160},
{'name': _('Level 2'), 'points': 4320},
{'name': _('Level 3'), 'points': 8700},
{'name': _('Level 4'), 'points': 21000},
{'name': _('Level 5'), 'points': 45000},
{'name': _('Level 6'), 'points': 96000},
{'name': _('Level 7'), 'points': 300000},
{'name': _('Level 8'), 'points': 1200000},
{'name': _('Level 9'), 'points': 3000000},
]
# Amount of hours to hide add-on reviews from users with permission
# Addons:DelayedReviews
REVIEW_LIMITED_DELAY_HOURS = 20
# Review queue pagination
REVIEWS_PER_PAGE = 200
REVIEWS_PER_PAGE_MAX = 400

Просмотреть файл

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
from django.core.management.base import BaseCommand
import olympia.core.logger
from olympia import amo
from olympia.activity.models import ActivityLog
from olympia.editors.models import ReviewerScore
log = olympia.core.logger.getLogger('z.editors.award_post_review_points')
# The note we're going to add to the ReviewerScore this command creates to be
# able to find them later.
MANUAL_NOTE = 'Retroactively awarded for past post/content review approval.'
class Command(BaseCommand):
help = 'Retroactively award points for past post/content review approvals'
def handle(self, *args, **options):
self.award_all_points_for_action(
amo.LOG.APPROVE_CONTENT, content_review=True)
self.award_all_points_for_action(
amo.LOG.CONFIRM_AUTO_APPROVED, content_review=False)
def award_all_points_for_action(self, action, content_review=False):
for activity_log in ActivityLog.objects.filter(action=action.id):
user = activity_log.user
try:
addon = activity_log.arguments[0]
version = activity_log.arguments[1]
except IndexError:
log.error('ActivityLog %d is missing one or more arguments',
activity_log.pk)
continue
# If there is already a score recorded in the database for this
# event, with our special note, it means we already processed it
# somehow (maybe we ran the script twice...), so ignore it.
# Otherwise award the points!
event = ReviewerScore.get_event(
addon, amo.STATUS_PUBLIC, version=version,
post_review=True, content_review=content_review)
if not ReviewerScore.objects.filter(
user=user, addon=addon, note_key=event,
note=MANUAL_NOTE).exists():
ReviewerScore.award_points(
user, addon, amo.STATUS_PUBLIC,
version=version, post_review=True,
content_review=content_review, extra_note=MANUAL_NOTE)
else:
log.error('Already awarded points for "%s" action on %s %s',
action.short, addon, version)
continue

Просмотреть файл

@ -30,6 +30,9 @@ from olympia.versions.models import Version, version_uploaded
user_log = olympia.core.logger.getLogger('z.users')
log = olympia.core.logger.getLogger('z.editors')
VIEW_QUEUE_FLAGS = (
('admin_review', 'admin-review', _('Admin Review')),
('is_jetpack', 'jetpack', _('Jetpack Add-on')),
@ -410,47 +413,87 @@ class ReviewerScore(ModelBase):
return '%s:%s' % (ns_key, key)
@classmethod
def get_event(cls, addon, status, **kwargs):
def get_event(cls, addon, status, version=None, post_review=False,
content_review=False):
"""Return the review event type constant.
This is determined by the addon.type and the queue the addon is
currently in (which is determined from the status).
currently in (which is determined from the various parameters sent
down from award_points()).
Note: We're not using addon.status because this is called after the
status has been updated by the reviewer action.
Note: We're not using addon.status or addon.current_version because
this is called after the status/current_version might have been updated
by the reviewer action.
"""
queue = ''
if status == amo.STATUS_NOMINATED:
queue = 'FULL'
elif status == amo.STATUS_PUBLIC:
queue = 'UPDATE'
if (addon.type in [amo.ADDON_EXTENSION, amo.ADDON_PLUGIN,
amo.ADDON_API] and queue):
return getattr(amo, 'REVIEWED_ADDON_%s' % queue)
elif addon.type == amo.ADDON_DICT and queue:
return getattr(amo, 'REVIEWED_DICT_%s' % queue)
elif addon.type in [amo.ADDON_LPAPP, amo.ADDON_LPADDON] and queue:
return getattr(amo, 'REVIEWED_LP_%s' % queue)
elif addon.type == amo.ADDON_PERSONA:
return amo.REVIEWED_PERSONA
elif addon.type == amo.ADDON_SEARCH and queue:
return getattr(amo, 'REVIEWED_SEARCH_%s' % queue)
elif addon.type == amo.ADDON_THEME and queue:
return getattr(amo, 'REVIEWED_THEME_%s' % queue)
reviewed_score_name = None
if content_review:
# Content review always gives the same amount of points.
reviewed_score_name = 'REVIEWED_CONTENT_REVIEW'
elif post_review:
# There are 4 tiers of post-review scores depending on the addon
# weight.
try:
if version is None:
raise AutoApprovalSummary.DoesNotExist
weight = version.autoapprovalsummary.weight
except AutoApprovalSummary.DoesNotExist as exception:
log.exception(
'No such version/auto approval summary when determining '
'event type to award points: %r', exception)
weight = 0
if weight >= amo.POST_REVIEW_WEIGHT_HIGHEST_RISK:
reviewed_score_name = 'REVIEWED_EXTENSION_HIGHEST_RISK'
elif weight >= amo.POST_REVIEW_WEIGHT_HIGH_RISK:
reviewed_score_name = 'REVIEWED_EXTENSION_HIGH_RISK'
elif weight >= amo.POST_REVIEW_WEIGHT_MEDIUM_RISK:
reviewed_score_name = 'REVIEWED_EXTENSION_MEDIUM_RISK'
else:
reviewed_score_name = 'REVIEWED_EXTENSION_LOW_RISK'
else:
return None
if status == amo.STATUS_NOMINATED:
queue = 'FULL'
elif status == amo.STATUS_PUBLIC:
queue = 'UPDATE'
else:
queue = ''
if (addon.type in [amo.ADDON_EXTENSION, amo.ADDON_PLUGIN,
amo.ADDON_API] and queue):
reviewed_score_name = 'REVIEWED_ADDON_%s' % queue
elif addon.type == amo.ADDON_DICT and queue:
reviewed_score_name = 'REVIEWED_DICT_%s' % queue
elif addon.type in [amo.ADDON_LPAPP, amo.ADDON_LPADDON] and queue:
reviewed_score_name = 'REVIEWED_LP_%s' % queue
elif addon.type == amo.ADDON_PERSONA:
reviewed_score_name = 'REVIEWED_PERSONA'
elif addon.type == amo.ADDON_SEARCH and queue:
reviewed_score_name = 'REVIEWED_SEARCH_%s' % queue
elif addon.type == amo.ADDON_THEME and queue:
reviewed_score_name = 'REVIEWED_THEME_%s' % queue
if reviewed_score_name:
return getattr(amo, reviewed_score_name)
return None
@classmethod
def award_points(cls, user, addon, status, version=None, **kwargs):
def award_points(cls, user, addon, status, version=None,
post_review=False, content_review=False,
extra_note=''):
"""Awards points to user based on an event and the queue.
`event` is one of the `REVIEWED_` keys in constants.
`status` is one of the `STATUS_` keys in constants.
`version` is the `Version` object that was affected by the review.
`post_review` is set to True if the add-on was auto-approved and the
reviewer is confirming/rejecting post-approval.
`content_review` is set to True if it's a content-only review of an
auto-approved add-on.
"""
event = cls.get_event(addon, status, **kwargs)
event = cls.get_event(
addon, status, version=version, post_review=post_review,
content_review=content_review)
score = amo.REVIEWED_SCORES.get(event)
# Add bonus to reviews greater than our limit to encourage fixing
@ -464,7 +507,7 @@ class ReviewerScore(ModelBase):
if score:
cls.objects.create(user=user, addon=addon, score=score,
note_key=event)
note_key=event, note=extra_note)
cls.get_key(invalidate=True)
user_log.info(
(u'Awarding %s points to user %s for "%s" for addon %s' % (

Просмотреть файл

@ -16,7 +16,7 @@ from olympia.amo.tests import (
from olympia.editors.management.commands import auto_approve
from olympia.editors.models import (
AutoApprovalNotEnoughFilesError, AutoApprovalNoValidationResultError,
AutoApprovalSummary, get_reviewing_cache)
AutoApprovalSummary, get_reviewing_cache, ReviewerScore)
from olympia.files.models import FileValidation
from olympia.files.utils import atomic_lock
from olympia.zadmin.models import Config, get_config, set_config
@ -476,3 +476,76 @@ class TestAutoApproveCommand(TestCase):
assert self.log_final_summary_mock.call_count == 0
assert self.file.reload().status == amo.STATUS_AWAITING_REVIEW
class TestAwardPostReviewPoints(TestCase):
def setUp(self):
self.user1 = user_factory()
self.user2 = user_factory()
self.user3 = user_factory()
self.addon1 = addon_factory()
self.addon2 = addon_factory()
# First user approved content of addon1.
ActivityLog.create(
amo.LOG.APPROVE_CONTENT, self.addon1,
self.addon1.current_version, user=self.user1)
# Second user confirmed auto-approved of addon2.
ActivityLog.create(
amo.LOG.CONFIRM_AUTO_APPROVED, self.addon2,
self.addon2.current_version, user=self.user2)
# Third user approved content of addon2.
ActivityLog.create(
amo.LOG.APPROVE_CONTENT, self.addon2,
self.addon2.current_version, user=self.user3,)
def test_missing_auto_approval_summary(self):
assert ReviewerScore.objects.count() == 0
call_command('award_post_review_points')
# CONFIRM_AUTO_APPROVED was skipped since we can't determine its
# weight (has no AutoApprovalSummary).
assert ReviewerScore.objects.count() == 2
first_score = ReviewerScore.objects.filter(user=self.user1).get()
assert first_score.addon == self.addon1
assert first_score.note == (
'Retroactively awarded for past post/content review approval.')
assert first_score.note_key == amo.REVIEWED_CONTENT_REVIEW
second_score = ReviewerScore.objects.filter(user=self.user3).get()
assert second_score.addon == self.addon2
assert second_score.note == (
'Retroactively awarded for past post/content review approval.')
assert second_score.note_key == amo.REVIEWED_CONTENT_REVIEW
def test_full(self):
AutoApprovalSummary.objects.create(
version=self.addon2.current_version, verdict=amo.AUTO_APPROVED,
weight=151, confirmed=True)
assert ReviewerScore.objects.count() == 0
call_command('award_post_review_points')
assert ReviewerScore.objects.count() == 3
first_score = ReviewerScore.objects.filter(user=self.user1).get()
assert first_score.addon == self.addon1
assert first_score.note == (
'Retroactively awarded for past post/content review approval.')
assert first_score.note_key == amo.REVIEWED_CONTENT_REVIEW
second_score = ReviewerScore.objects.filter(user=self.user2).get()
assert second_score.addon == self.addon2
assert second_score.note == (
'Retroactively awarded for past post/content review approval.')
assert second_score.note_key == amo.REVIEWED_EXTENSION_HIGHEST_RISK
third_score = ReviewerScore.objects.filter(user=self.user3).get()
assert third_score.addon == self.addon2
assert third_score.note == (
'Retroactively awarded for past post/content review approval.')
assert third_score.note_key == amo.REVIEWED_CONTENT_REVIEW
def test_run_twice(self):
# Running twice should only generate the scores once.
AutoApprovalSummary.objects.create(
version=self.addon2.current_version, verdict=amo.AUTO_APPROVED,
weight=151, confirmed=True)
call_command('award_post_review_points')
call_command('award_post_review_points')
assert ReviewerScore.objects.count() == 3

Просмотреть файл

@ -480,11 +480,62 @@ class TestReviewerScore(TestCase):
event = None
self.check_event(tk, sk, event)
def test_events_post_review(self):
base_args = (self.addon, amo.STATUS_PUBLIC)
# No version.
assert ReviewerScore.get_event(
*base_args, version=None,
post_review=True) == amo.REVIEWED_EXTENSION_LOW_RISK
# No autoapprovalsummary.
assert ReviewerScore.get_event(
*base_args, version=self.addon.current_version,
post_review=True) == amo.REVIEWED_EXTENSION_LOW_RISK
# Now with a summary... low risk.
summary = AutoApprovalSummary.objects.create(
version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
weight=-10)
assert ReviewerScore.get_event(
*base_args, version=self.addon.current_version,
post_review=True) is amo.REVIEWED_EXTENSION_LOW_RISK
# Medium risk.
summary.update(weight=21)
assert ReviewerScore.get_event(
*base_args, version=self.addon.current_version,
post_review=True) is amo.REVIEWED_EXTENSION_MEDIUM_RISK
# High risk.
summary.update(weight=101)
assert ReviewerScore.get_event(
*base_args, version=self.addon.current_version,
post_review=True) is amo.REVIEWED_EXTENSION_HIGH_RISK
# Highest risk.
summary.update(weight=151)
assert ReviewerScore.get_event(
*base_args, version=self.addon.current_version,
post_review=True) is amo.REVIEWED_EXTENSION_HIGHEST_RISK
# Highest risk again.
summary.update(weight=65535)
assert ReviewerScore.get_event(
*base_args, version=self.addon.current_version,
post_review=True) is amo.REVIEWED_EXTENSION_HIGHEST_RISK
# Content review is always the same.
assert ReviewerScore.get_event(
*base_args, version=self.addon.current_version, post_review=True,
content_review=True) == amo.REVIEWED_CONTENT_REVIEW
def test_award_points(self):
self._give_points()
assert ReviewerScore.objects.all()[0].score == (
amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])
def test_award_points_with_extra_note(self):
ReviewerScore.award_points(
self.user, self.addon, self.addon.status, extra_note=u'ÔMG!')
reviewer_score = ReviewerScore.objects.all()[0]
assert reviewer_score.note_key == amo.REVIEWED_ADDON_FULL
assert reviewer_score.score == (
amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])
assert reviewer_score.note == u'ÔMG!'
def test_award_points_bonus(self):
user2 = UserProfile.objects.get(email='admin@mozilla.com')
bonus_days = 2

Просмотреть файл

@ -745,7 +745,7 @@ class TestReviewHelper(TestCase):
self.grant_permission(self.request.user, 'Addons:PostReview')
self.setup_data(amo.STATUS_PUBLIC, file_status=amo.STATUS_PUBLIC)
summary = AutoApprovalSummary.objects.create(
version=self.version, verdict=amo.AUTO_APPROVED)
version=self.version, verdict=amo.AUTO_APPROVED, weight=150)
assert summary.confirmed is None
self.create_paths()
@ -769,6 +769,9 @@ class TestReviewHelper(TestCase):
assert activity.arguments == [self.addon, self.version]
assert activity.details['comments'] == ''
# Check points awarded.
self._check_score(amo.REVIEWED_EXTENSION_HIGHEST_RISK)
def test_public_with_unreviewed_version_addon_confirm_auto_approval(self):
self.grant_permission(self.request.user, 'Addons:PostReview')
self.setup_data(amo.STATUS_PUBLIC, file_status=amo.STATUS_PUBLIC)
@ -1047,6 +1050,8 @@ class TestReviewHelper(TestCase):
def test_reject_multiple_versions(self):
old_version = self.version
self.version = version_factory(addon=self.addon, version='3.0')
AutoApprovalSummary.objects.create(
version=self.version, verdict=amo.AUTO_APPROVED, weight=100)
# An extra file should not change anything.
file_factory(version=self.version, platform=amo.PLATFORM_LINUX.id)
self.setup_data(amo.STATUS_PUBLIC, file_status=amo.STATUS_PUBLIC)
@ -1082,11 +1087,16 @@ class TestReviewHelper(TestCase):
assert self.check_log_count(amo.LOG.REJECT_VERSION.id) == 2
assert self.check_log_count(amo.LOG.REJECT_CONTENT.id) == 0
# Check points awarded.
self._check_score(amo.REVIEWED_EXTENSION_HIGH_RISK)
def test_reject_multiple_versions_except_latest(self):
old_version = self.version
extra_version = version_factory(addon=self.addon, version='3.1')
# Add yet another version we don't want to reject.
self.version = version_factory(addon=self.addon, version='42.0')
AutoApprovalSummary.objects.create(
version=self.version, verdict=amo.AUTO_APPROVED, weight=20)
self.setup_data(amo.STATUS_PUBLIC, file_status=amo.STATUS_PUBLIC)
# Safeguards.
@ -1124,6 +1134,9 @@ class TestReviewHelper(TestCase):
assert self.check_log_count(amo.LOG.REJECT_VERSION.id) == 2
assert self.check_log_count(amo.LOG.REJECT_CONTENT.id) == 0
# Check points awarded.
self._check_score(amo.REVIEWED_EXTENSION_MEDIUM_RISK)
def test_reject_multiple_versions_content_review(self):
self.grant_permission(self.request.user, 'Addons:ContentReview')
old_version = self.version
@ -1194,6 +1207,9 @@ class TestReviewHelper(TestCase):
assert activity.arguments == [self.addon, self.version]
assert activity.details['comments'] == ''
# Check points awarded.
self._check_score(amo.REVIEWED_CONTENT_REVIEW)
def test_dev_versions_url_in_context(self):
self.helper.set_data(self.get_data())
context_data = self.helper.handler.get_context_data()

Просмотреть файл

@ -29,7 +29,6 @@ from olympia.addons.models import (
Addon, AddonApprovalsCounter, AddonDependency, AddonUser)
from olympia.amo.tests import check_links, formset, initial
from olympia.amo.urlresolvers import reverse
from olympia.constants.base import REVIEW_LIMITED_DELAY_HOURS
from olympia.editors.models import (
AutoApprovalSummary, EditorSubscription, ReviewerScore)
from olympia.files.models import File, FileValidation, WebextPermission
@ -3663,7 +3662,7 @@ class TestLimitedReviewerQueue(QueueTest, LimitedReviewerBase):
version = addon.find_latest_version(
channel=amo.RELEASE_CHANNEL_LISTED)
if version.nomination <= datetime.now() - timedelta(
hours=REVIEW_LIMITED_DELAY_HOURS):
hours=amo.REVIEW_LIMITED_DELAY_HOURS):
self.expected_addons.append(addon)
self.create_limited_user()

Просмотреть файл

@ -21,7 +21,6 @@ from olympia.addons.models import Addon, AddonApprovalsCounter
from olympia.amo.templatetags.jinja_helpers import absolutify
from olympia.amo.urlresolvers import reverse
from olympia.amo.utils import to_language
from olympia.constants.base import REVIEW_LIMITED_DELAY_HOURS
from olympia.editors.models import (
get_flags, ReviewerScore, ViewFullReviewQueue, ViewPendingQueue,
ViewUnlistedAllList)
@ -297,7 +296,7 @@ class ReviewHelper(object):
(self.version and
self.version.nomination is not None and
(datetime.datetime.now() - self.version.nomination >=
datetime.timedelta(hours=REVIEW_LIMITED_DELAY_HOURS))))
datetime.timedelta(hours=amo.REVIEW_LIMITED_DELAY_HOURS))))
reviewable_because_pending = (
self.version and
len(self.version.is_unreviewed) > 0)
@ -682,6 +681,8 @@ class ReviewBase(object):
version is listed, incrementing AddonApprovalsCounter, which also
resets the last human review date to now, and log it so that it's
displayed later in the review page."""
status = self.addon.status
latest_version = self.version
# The confirm auto-approval action should not show the comment box,
# so override the text in case the reviewer switched between actions
# and accidently submitted some comments from another action.
@ -698,11 +699,18 @@ class ReviewBase(object):
AddonApprovalsCounter.increment_for_addon(addon=self.addon)
self.log_action(amo.LOG.CONFIRM_AUTO_APPROVED)
# Assign reviewer incentive scores.
if self.request:
ReviewerScore.award_points(
self.request.user, self.addon, status, version=latest_version,
post_review=True, content_review=self.content_review_only)
def reject_multiple_versions(self):
"""Reject a list of versions."""
# self.version and self.files won't point to the versions we want to
# modify in this action, so set them to None before finding the right
# versions.
status = self.addon.status
latest_version = self.version
self.version = None
self.files = None
@ -731,6 +739,12 @@ class ReviewBase(object):
u', '.join(unicode(v.pk) for v in self.data['versions'])))
log.info(u'Sending email for %s' % (self.addon))
# Assign reviewer incentive scores.
if self.request:
ReviewerScore.award_points(
self.request.user, self.addon, status, version=latest_version,
post_review=True, content_review=self.content_review_only)
class ReviewAddon(ReviewBase):
set_addon_status = True

Просмотреть файл

@ -26,7 +26,6 @@ from olympia.amo.decorators import (
json_view, permission_required, post_required)
from olympia.amo.utils import paginate, render
from olympia.amo.urlresolvers import reverse
from olympia.constants.base import REVIEW_LIMITED_DELAY_HOURS
from olympia.constants.editors import REVIEWS_PER_PAGE, REVIEWS_PER_PAGE_MAX
from olympia.editors import forms
from olympia.editors.models import (
@ -434,7 +433,7 @@ def _queue(request, TableObj, tab, qs=None, unlisted=False,
if hasattr(qs, 'sql_model') and not unlisted:
if is_limited_reviewer(request):
qs = qs.having(
'waiting_time_hours >=', REVIEW_LIMITED_DELAY_HOURS)
'waiting_time_hours >=', amo.REVIEW_LIMITED_DELAY_HOURS)
if waffle.switch_is_active('post-review'):
# Hide webextensions from the queues so that human reviewers
@ -478,7 +477,7 @@ def queue_counts(type=None, unlisted=False, admin_reviewer=False,
query = query.having('waiting_time_days <=', days_max)
if limited_reviewer:
query = query.having('waiting_time_hours >=',
REVIEW_LIMITED_DELAY_HOURS)
amo.REVIEW_LIMITED_DELAY_HOURS)
return query.count