Merge pull request #6569 from diox/reviewers-new-points-system
Award Reviewer Points for post-review/content-review actions
This commit is contained in:
Коммит
0e3d9cab59
|
@ -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
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче