Fix #6474.
This commit is contained in:
Andreas Wagner 2018-11-23 13:51:04 +01:00 коммит произвёл GitHub
Родитель f0ecf3da85
Коммит 4d41645987
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 501 добавлений и 0 удалений

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

@ -413,3 +413,6 @@ pygit2==0.27.2 \
webencodings==0.5.1 \ webencodings==0.5.1 \
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
--hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
premailer==3.2.0 \
--hash=sha256:a344b2013f8e099962bdea3a433fbea8614006fbe2ef62cd89c653a2b33290ad \
--hash=sha256:ca97cec6115fea6590b49558c55d891996f9eb4da6490c7b60c3a8af4c8c0735

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

@ -46,6 +46,9 @@ HOME=/tmp
30 11 * * * %(z_cron)s update_addon_average_daily_users 30 11 * * * %(z_cron)s update_addon_average_daily_users
00 12 * * * %(z_cron)s index_latest_stats 00 12 * * * %(z_cron)s index_latest_stats
# Once per week
1 9 * * 1 %(django)s review_reports
# Do not put crons below this line # Do not put crons below this line
MAILTO=root MAILTO=root

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

@ -0,0 +1,155 @@
from datetime import date, timedelta
import os
import settings
from django.core.management.base import BaseCommand
from django.db import connection
import olympia.core.logger
from olympia.amo.utils import send_mail
from olympia.constants.reviewers import (POST_REVIEW_WEIGHT_HIGHEST_RISK,
POST_REVIEW_WEIGHT_HIGH_RISK,
POST_REVIEW_WEIGHT_MEDIUM_RISK)
from premailer import transform
SQL_DIR = os.path.join(
settings.ROOT,
"src/olympia/reviewers/management/commands/review_reports_sql/")
REPORTS = {
'addon': [('Weekly Add-on Reviews, 5 Reviews or More',
os.path.join(SQL_DIR, 'addon/weekly.sql')),
('Weekly Volunteer Contribution Ratio',
os.path.join(SQL_DIR, 'addon/breakdown.sql')),
('Weekly Add-on Reviews by Risk Profiles',
os.path.join(SQL_DIR, 'addon/risk.sql')),
('Quarterly contributions',
os.path.join(SQL_DIR, 'addon/quarterly.sql'))],
'content': [('Weekly Content Reviews, 10 Reviews or More',
os.path.join(SQL_DIR, 'content/weekly.sql')),
('Weekly Volunteer Contribution Ratio',
os.path.join(SQL_DIR, 'content/breakdown.sql')),
('Quarterly contributions',
os.path.join(SQL_DIR, 'content/quarterly.sql'))]
}
log = olympia.core.logger.getLogger('z.reviewers.review_report')
class Command(BaseCommand):
help = 'Generate and send the review report'
def handle(self, *args, **options):
log.info("Generating add-on reviews report...")
addon_report_data = self.fetch_report_data('addon')
addon_report_html = self.generate_report_html('addon',
addon_report_data)
addon_report_subject = '%s %s-%s' % (
'Weekly Add-on Reviews Report',
self.week_begin, self.week_end)
self.mail_report('addon-reviewers@mozilla.org',
addon_report_subject,
addon_report_html)
log.info("Generating content reviews report...")
content_report_data = self.fetch_report_data('content')
content_report_html = self.generate_report_html('content',
content_report_data)
content_report_subject = '%s %s-%s' % (
'Weekly Add-on Content Reviews Report',
self.week_begin, self.week_end)
self.mail_report('addon-content-reviewers@mozilla.org',
content_report_subject,
content_report_html)
def fetch_report_data(self, group):
today = date.today()
with connection.cursor() as cursor:
# Set variables that are being used in the review report,
# as well as the email output.
cursor.execute("""
SET @WEEK_BEGIN=%s;
SET @WEEK_END=%s;
SET @QUARTER_BEGIN=%s;
SET @RISK_HIGHEST=%s;
SET @RISK_HIGH=%s;
SET @RISK_MEDIUM=%s;
""", [today - timedelta(days=today.weekday() + 7),
today - timedelta(days=today.weekday() + 1),
date(today.year, (today.month - 1) // 3 * 3 + 1, 1),
POST_REVIEW_WEIGHT_HIGHEST_RISK,
POST_REVIEW_WEIGHT_HIGH_RISK,
POST_REVIEW_WEIGHT_MEDIUM_RISK])
# Read the beginning/end of the week
# in order to put it in the email.
cursor.execute('SELECT @WEEK_BEGIN, @WEEK_END;')
data = cursor.fetchone()
self.week_begin = data[0]
self.week_end = data[1]
report_data = []
for header, query_file in REPORTS.get(group):
with open(query_file) as report_query:
query_string = report_query.read().replace('\n', ' ')
cursor.execute(query_string)
table_header = []
for descr in cursor.description:
table_header.append(descr[0])
table_content = cursor.fetchall()
report_data.append((header, table_header, table_content))
return report_data
def generate_report_html(self, group, report_data):
# Pre-set email with style information and header
all_html = """
<style>
h1 { margin: 0; padding: 0; }
h2 { margin: 0; padding: 30px 0 10px 0; }
th { text-align: left; }
th, td { padding: 0 12px; }
td { text-align: right; white-space: nowrap; }
td:first-child { text-align: left; white-space: nowrap; }
</style>
<h1>Weekly Add-on %sReviews Report</h1>
<h3>%s - %s</h3>
""" % (('Content ' if group == 'content' else ''),
self.week_begin, self.week_end)
# For each group, execute the individual SQL reports
# and build the HTML email.
for section in report_data:
all_html += '<h2>%s</h2>\n' % section[0]
table_html = '<table>\n'
table_html += '<tr><th>' + '</th><th>'.join(
[header for header in section[1]]) + '</th></tr>\n'
for row in section[2]:
table_html += '<tr><td>' + '</td><td>'.join(
[entry for entry in row]) + '</td></tr>\n'
table_html += '</table>\n'
all_html += table_html
# Some email clients (e.g. GMail) require all styles to be inline.
# 'transform' takes the file-wide styles defined above and transforms
# them to be inline styles.
return transform(all_html)
def mail_report(self, recipient, subject, content):
log.info("Sending report '%s' to %s." % (subject, recipient))
send_mail(subject,
content,
from_email='nobody@mzilla.org',
recipient_list=[recipient],
html_message=content,
reply_to=[recipient])

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

@ -0,0 +1,30 @@
SELECT 'All Reviewers' AS `Group`,
FORMAT(SUM(aa.weight), 0) AS `Total Risk`,
FORMAT(AVG(aa.weight), 2) AS `Average Risk`,
FORMAT(COUNT(*), 0) AS `Add-ons Reviewed`
FROM editors_autoapprovalsummary aa
JOIN reviewer_scores rs ON rs.version_id = aa.version_id
WHERE DATE(rs.created) BETWEEN @WEEK_BEGIN AND @WEEK_END
/* Filter out internal task user */
AND user_id <> 4757633
/* The type of review, see constants/reviewers.py */
AND rs.note_key IN (10, 12, 20, 22, 30, 32, 50, 52, 102, 103, 104, 105)
UNION ALL
SELECT 'Volunteers' AS `Group`,
FORMAT(SUM(aa.weight), 0) AS `Total Risk`,
FORMAT(AVG(aa.weight), 2) AS `Average Risk`,
FORMAT(COUNT(*), 0) AS `Add-ons Reviewed`
FROM editors_autoapprovalsummary aa
JOIN reviewer_scores rs ON rs.version_id = aa.version_id
WHERE DATE(rs.created) BETWEEN @WEEK_BEGIN AND @WEEK_END
/* Filter out internal task user */
AND user_id <> 4757633
/* The type of review, see constants/reviewers.py */
AND rs.note_key IN (10, 12, 20, 22, 30, 32, 50, 52, 102, 103, 104, 105)
AND rs.user_id NOT IN
(SELECT user_id
FROM groups_users
WHERE group_id IN
(SELECT id
FROM groups
WHERE name IN ('Staff', 'No Reviewer Incentives')));

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

@ -0,0 +1,19 @@
SELECT u.display_name AS `Name`,
IFNULL(FORMAT(SUM(rs.score), 0), 0) AS `Points`,
FORMAT(COUNT(*), 0) AS `Add-ons Reviewed`
FROM reviewer_scores rs
JOIN users u ON u.id = rs.user_id
WHERE DATE(rs.created) BETWEEN @QUARTER_BEGIN AND @WEEK_END
/* Filter out internal task user */
AND user_id <> 4757633
/* The type of review, see constants/reviewers.py */
AND rs.note_key IN (10, 12, 20, 22, 30, 32, 50, 52, 102, 103, 104, 105)
AND rs.user_id NOT IN
(SELECT user_id
FROM groups_users
WHERE group_id IN
(SELECT id
FROM groups
WHERE name IN ('Staff', 'No Reviewer Incentives')))
GROUP BY rs.user_id
ORDER BY SUM(rs.score) DESC;

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

@ -0,0 +1,32 @@
SELECT risk_category AS `Risk Category`,
FORMAT(SUM(n), 0) AS `All Reviewers`,
FORMAT(SUM(CASE WHEN `group_category` = 'volunteer' THEN n ELSE 0 END), 0) AS 'Volunteers'
FROM
(SELECT CASE
WHEN weight > @RISK_HIGHEST THEN 'highest'
WHEN weight > @RISK_HIGH THEN 'high'
WHEN weight > @RISK_MEDIUM THEN 'medium'
ELSE 'low'
END AS risk_category,
group_category,
COUNT(*) AS n
FROM editors_autoapprovalsummary aa
JOIN
(SELECT version_id,
CASE WHEN user_id NOT IN
(SELECT user_id
FROM groups_users
WHERE group_id IN
(SELECT id
FROM groups
WHERE name IN ('Staff', 'No Reviewer Incentives'))) THEN 'volunteer' ELSE 'all' END AS `group_category`
FROM reviewer_scores rs
WHERE DATE(rs.created) BETWEEN @WEEK_BEGIN AND @WEEK_END
/* Filter out internal task user */
AND user_id <> 4757633
/* The type of review, see constants/reviewers.py */
AND rs.note_key IN (10, 12, 20, 22, 30, 32, 50, 52, 102, 103, 104, 105)) reviews ON reviews.version_id = aa.version_id
GROUP BY risk_category,
`group_category`) risk
GROUP BY 1
ORDER BY FIELD(risk_category,'highest','high','medium', 'low');

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

@ -0,0 +1,30 @@
SELECT u.display_name AS `Name`,
IF(
(SELECT DISTINCT(user_id)
FROM groups_users
WHERE group_id IN
(SELECT id
FROM groups
WHERE name IN ('Staff', 'No Reviewer Incentives'))
AND user_id = rs.user_id), '*', '') AS `Staff`,
FORMAT(SUM(aa.weight), 0) AS `Total Risk`,
FORMAT(AVG(aa.weight), 2) AS `Average Risk`,
IFNULL(IF(
(SELECT DISTINCT(user_id)
FROM groups_users
WHERE group_id IN
(SELECT id
FROM groups
WHERE name IN ('Staff', 'No Reviewer Incentives'))
AND user_id = rs.user_id), '-', SUM(rs.score)), 0) AS `Points`,
FORMAT(COUNT(*), 0) AS `Add-ons Reviewed`
FROM editors_autoapprovalsummary aa
JOIN reviewer_scores rs ON rs.version_id = aa.version_id
JOIN users u ON u.id = rs.user_id
WHERE DATE(rs.created) BETWEEN @WEEK_BEGIN AND @WEEK_END
/* Filter out internal task user */
AND user_id <> 4757633
/* The type of review, see constants/reviewers.py */
AND rs.note_key IN (10, 12, 20, 22, 30, 32, 50, 52, 102, 103, 104, 105)
GROUP BY user_id HAVING `Add-ons Reviewed` >= 5
ORDER BY SUM(aa.weight) DESC;

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

@ -0,0 +1,26 @@
SELECT 'All Reviewers' AS `Group`,
FORMAT(COUNT(*), 0) AS `Add-ons Reviewed`
FROM editors_autoapprovalsummary aa
JOIN reviewer_scores rs ON rs.version_id = aa.version_id
WHERE DATE(rs.created) BETWEEN @WEEK_BEGIN AND @WEEK_END
/* Filter out internal task user */
AND user_id <> 4757633
/* The type of review, see constants/reviewers.py */
AND rs.note_key IN (101)
UNION ALL
SELECT 'Volunteers' AS `Group`,
FORMAT(COUNT(*), 0) AS `Add-ons Reviewed`
FROM editors_autoapprovalsummary aa
JOIN reviewer_scores rs ON rs.version_id = aa.version_id
WHERE DATE(rs.created) BETWEEN @WEEK_BEGIN AND @WEEK_END
/* Filter out internal task user */
AND user_id <> 4757633
/* The type of review, see constants/reviewers.py */
AND rs.note_key IN (101)
AND rs.user_id NOT IN
(SELECT user_id
FROM groups_users
WHERE group_id IN
(SELECT id
FROM groups
WHERE name IN ('Staff', 'No Reviewer Incentives')));

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

@ -0,0 +1,19 @@
SELECT u.display_name AS `Name`,
IFNULL(FORMAT(SUM(rs.score), 0), 0) AS `Points`,
FORMAT(COUNT(*), 0) AS `Add-ons Reviewed`
FROM reviewer_scores rs
JOIN users u ON u.id = rs.user_id
WHERE DATE(rs.created) BETWEEN @QUARTER_BEGIN AND @WEEK_END
/* Filter out internal task user */
AND user_id <> 4757633
/* The type of review, see constants/reviewers.py */
AND rs.note_key IN (101)
AND rs.user_id NOT IN
(SELECT user_id
FROM groups_users
WHERE group_id IN
(SELECT id
FROM groups
WHERE name IN ('Staff', 'No Reviewer Incentives')))
GROUP BY rs.user_id
ORDER BY SUM(rs.score) DESC;

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

@ -0,0 +1,28 @@
SELECT u.display_name AS `Name`,
IF(
(SELECT DISTINCT(user_id)
FROM groups_users
WHERE group_id IN
(SELECT id
FROM groups
WHERE name IN ('Staff', 'No Reviewer Incentives'))
AND user_id = rs.user_id), '*', '') AS `Staff`,
IFNULL(IF(
(SELECT DISTINCT(user_id)
FROM groups_users
WHERE group_id IN
(SELECT id
FROM groups
WHERE name IN ('Staff', 'No Reviewer Incentives'))
AND user_id = rs.user_id), '-', SUM(rs.score)), 0) AS `Points`,
FORMAT(COUNT(*), 0) AS `Add-ons Reviewed`
FROM editors_autoapprovalsummary aa
JOIN reviewer_scores rs ON rs.version_id = aa.version_id
JOIN users u ON u.id = rs.user_id
WHERE DATE(rs.created) BETWEEN @WEEK_BEGIN AND @WEEK_END
/* Filter out internal task user */
AND user_id <> 4757633
/* The type of review, see constants/reviewers.py */
AND rs.note_key IN (101)
GROUP BY user_id HAVING `Add-ons Reviewed` >= 10
ORDER BY `Add-ons Reviewed` DESC;

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

@ -0,0 +1,156 @@
from datetime import date, timedelta
from freezegun import freeze_time
from django.core import mail
from olympia import amo
from olympia.amo.tests import TestCase, user_factory, addon_factory
from olympia.reviewers.management.commands.review_reports import Command
from olympia.reviewers.models import AutoApprovalSummary, ReviewerScore
class TestReviewReports(TestCase):
today = date.today()
last_week_begin = today - timedelta(days=today.weekday() + 7)
last_week_end = today - timedelta(days=today.weekday() + 1)
this_quarter_begin = date(today.year, (today.month - 1) // 3 * 3 + 1, 1)
def create_and_review_addon(self, user, weight, verdict, content_review):
addon = addon_factory()
AutoApprovalSummary.objects.create(
version=addon.current_version, verdict=verdict, weight=weight)
ReviewerScore.award_points(
user, addon, addon.status, version=addon.versions.all()[0],
post_review=True, content_review=content_review)
def setUp(self):
super(TestReviewReports, self).setUp()
with freeze_time(self.last_week_begin):
reviewer1 = user_factory(display_name='Volunteer A')
reviewer2 = user_factory(display_name='Staff B')
reviewer3 = user_factory(display_name='Volunteer Content C')
reviewer4 = user_factory(display_name='Staff Content D')
self.grant_permission(reviewer2, '', name='Staff')
self.grant_permission(reviewer4, '', name='No Reviewer Incentives')
data = [
(reviewer1, 178, amo.AUTO_APPROVED, False),
(reviewer1, 95, amo.AUTO_APPROVED, False),
(reviewer1, 123, amo.NOT_AUTO_APPROVED, False),
(reviewer1, 328, amo.AUTO_APPROVED, False),
(reviewer1, 450, amo.AUTO_APPROVED, False),
(reviewer1, 999, amo.NOT_AUTO_APPROVED, False),
(reviewer1, 131, amo.AUTO_APPROVED, False),
(reviewer1, 74, amo.NOT_AUTO_APPROVED, False),
(reviewer1, 15, amo.AUTO_APPROVED, False),
(reviewer2, 951, amo.NOT_AUTO_APPROVED, False),
(reviewer2, 8421, amo.AUTO_APPROVED, False),
(reviewer2, 281, amo.AUTO_APPROVED, False),
(reviewer2, 54, amo.NOT_AUTO_APPROVED, False),
(reviewer2, 91, amo.NOT_AUTO_APPROVED, False),
(reviewer2, 192, amo.AUTO_APPROVED, False),
(reviewer2, 222, amo.NOT_AUTO_APPROVED, False),
(reviewer3, 178, amo.AUTO_APPROVED, True),
(reviewer3, 95, amo.AUTO_APPROVED, True),
(reviewer3, 123, amo.NOT_AUTO_APPROVED, True),
(reviewer3, 328, amo.AUTO_APPROVED, True),
(reviewer3, 450, amo.AUTO_APPROVED, True),
(reviewer3, 999, amo.NOT_AUTO_APPROVED, True),
(reviewer3, 131, amo.AUTO_APPROVED, True),
(reviewer3, 74, amo.NOT_AUTO_APPROVED, True),
(reviewer3, 15, amo.AUTO_APPROVED, True),
(reviewer3, 48, amo.AUTO_APPROVED, True),
(reviewer3, 87, amo.NOT_AUTO_APPROVED, True),
(reviewer3, 265, amo.AUTO_APPROVED, True),
(reviewer4, 951, amo.NOT_AUTO_APPROVED, True),
(reviewer4, 8421, amo.AUTO_APPROVED, True),
(reviewer4, 281, amo.AUTO_APPROVED, True),
(reviewer4, 54, amo.NOT_AUTO_APPROVED, True),
(reviewer4, 91, amo.NOT_AUTO_APPROVED, True),
(reviewer4, 192, amo.AUTO_APPROVED, True),
(reviewer4, 222, amo.NOT_AUTO_APPROVED, True),
(reviewer4, 192, amo.AUTO_APPROVED, True),
(reviewer4, 444, amo.NOT_AUTO_APPROVED, True),
(reviewer4, 749, amo.AUTO_APPROVED, True),
]
for review_action in data:
self.create_and_review_addon(review_action[0],
review_action[1],
review_action[2],
review_action[3])
def test_report_addon_reviewer(self):
command = Command()
data = command.fetch_report_data('addon')
assert data == [
('Weekly Add-on Reviews, 5 Reviews or More',
['Name', 'Staff', 'Total Risk', 'Average Risk', 'Points',
'Add-ons Reviewed'],
((u'Staff B', u'*', u'10,212', u'1,458.86', '-', u'7'),
(u'Volunteer A', u'', u'2,393', u'265.89', '810', u'9'))),
('Weekly Volunteer Contribution Ratio',
['Group', 'Total Risk', 'Average Risk', 'Add-ons Reviewed'],
((u'All Reviewers', u'12,605', u'787.81', u'16'),
(u'Volunteers', u'2,393', u'265.89', u'9'))),
('Weekly Add-on Reviews by Risk Profiles',
['Risk Category', 'All Reviewers', 'Volunteers'],
((u'highest', u'6', u'3'), (u'high', u'3', u'1'),
(u'medium', u'4', u'3'), (u'low', u'3', u'2'))),
('Quarterly contributions',
['Name', 'Points', 'Add-ons Reviewed'],
((u'Volunteer A', u'810', u'9'),))
]
html = command.generate_report_html('addon', data)
assert 'Weekly Add-on Reviews Report' in html
assert 'Volunteer A' in html
assert 'Staff B' in html
to = 'addon-reviewers@mozilla.org'
subject = '%s %s-%s' % (
'Weekly Add-on Reviews Report',
self.last_week_begin, self.last_week_end)
command.mail_report(to, subject, html)
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert to in email.to
assert subject in email.subject
def test_report_content_reviewer(self):
command = Command()
data = command.fetch_report_data('content')
assert data == [
('Weekly Content Reviews, 10 Reviews or More',
['Name', 'Staff', 'Points', 'Add-ons Reviewed'],
((u'Volunteer Content C', u'', '120', u'12'),
(u'Staff Content D', u'*', '-', u'10'))),
('Weekly Volunteer Contribution Ratio',
['Group', 'Add-ons Reviewed'],
((u'All Reviewers', u'22'), (u'Volunteers', u'12'))),
('Quarterly contributions',
['Name', 'Points', 'Add-ons Reviewed'],
((u'Volunteer Content C', u'120', u'12'),))
]
html = command.generate_report_html('content', data)
assert 'Weekly Add-on Content Reviews Report' in html
assert 'Volunteer Content C' in html
assert 'Staff Content D' in html
to = 'addon-content-reviewers@mozilla.org'
subject = '%s %s-%s' % (
'Weekly Add-on Content Reviews Report',
self.last_week_begin, self.last_week_end)
command.mail_report(to, subject, html)
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert to in email.to
assert subject in email.subject