From cc649922043b7f7e3c402015008960525040f616 Mon Sep 17 00:00:00 2001 From: Sasha Romijn Date: Mon, 30 Sep 2019 12:27:43 +0000 Subject: [PATCH] Refs #2277 - Send daily reminders of overdue reviews to secretaries Commit ready for merge. - Legacy-Id: 16765 --- ietf/bin/send-review-reminders | 4 ++- ietf/group/tests_review.py | 23 +++++++++++- .../0010_add_review_reminder_mailtriggers.py | 35 +++++++++++++++++++ ietf/name/fixtures/names.json | 11 ++++++ ietf/review/utils.py | 34 +++++++++++++++++- .../review_reminder_overdue_assignment.txt | 14 ++++++++ 6 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 ietf/mailtrigger/migrations/0010_add_review_reminder_mailtriggers.py create mode 100644 ietf/templates/review/review_reminder_overdue_assignment.txt diff --git a/ietf/bin/send-review-reminders b/ietf/bin/send-review-reminders index 58213140b..f51f780c0 100755 --- a/ietf/bin/send-review-reminders +++ b/ietf/bin/send-review-reminders @@ -24,7 +24,8 @@ import datetime from ietf.review.utils import ( review_assignments_needing_reviewer_reminder, email_reviewer_reminder, review_assignments_needing_secretary_reminder, email_secretary_reminder, - email_unavaibility_period_ending_reminder, email_reminder_all_open_reviews) + email_unavaibility_period_ending_reminder, email_reminder_all_open_reviews, + email_review_reminder_overdue_assignment) today = datetime.date.today() @@ -39,4 +40,5 @@ for assignment, secretary_role in review_assignments_needing_secretary_reminder( print("Emailed reminder to {} for review of {} in {} (req. id {})".format(secretary_role.email.address, review_req.doc_id, review_req.team.acronym, review_req.pk)) print('\n'.join(email_unavaibility_period_ending_reminder(today))) +print('\n'.join(email_review_reminder_overdue_assignment(today))) print('\n'.join(email_reminder_all_open_reviews(today))) diff --git a/ietf/group/tests_review.py b/ietf/group/tests_review.py index 1bf0751d9..72f61b021 100644 --- a/ietf/group/tests_review.py +++ b/ietf/group/tests_review.py @@ -23,7 +23,8 @@ from ietf.review.utils import ( review_assignments_needing_reviewer_reminder, email_reviewer_reminder, review_assignments_needing_secretary_reminder, email_secretary_reminder, reviewer_rotation_list, - email_unavaibility_period_ending_reminder, email_reminder_all_open_reviews) + email_unavaibility_period_ending_reminder, email_reminder_all_open_reviews, + email_review_reminder_overdue_assignment) from ietf.name.models import ReviewResultName, ReviewRequestStateName, ReviewAssignmentStateName import ietf.group.views from ietf.utils.mail import outbox, empty_outbox @@ -548,6 +549,26 @@ class ReviewTests(TestCase): self.assertTrue(reviewer.person.name in log[0]) self.assertTrue(review_team.acronym in log[0]) + def test_email_review_reminder_overdue_assignment(self): + today = datetime.date.today() + review_req = ReviewRequestFactory(state_id='assigned', deadline=today - datetime.timedelta(5)) + reviewer = RoleFactory(name_id='reviewer', group=review_req.team,person__user__username='reviewer').person + ReviewAssignmentFactory(review_request=review_req, state_id='assigned', assigned_on=review_req.time, reviewer=reviewer.email_set.first()) + secretary = RoleFactory(name_id='secr', group=review_req.team, person__user__username='reviewsecretary') + + empty_outbox() + log = email_review_reminder_overdue_assignment(today) + + self.assertEqual(len(outbox), 1) + self.assertTrue(secretary.person.email_address() in outbox[0]["To"]) + self.assertEqual(outbox[0]["Subject"], "1 Overdue review for team {}".format(review_req.team.acronym)) + message = outbox[0].get_payload(decode=True).decode("utf-8") + self.assertTrue(review_req.team.acronym + ' has 1 accepted or assigned review overdue by at least 5 days.' in message) + self.assertTrue('Review of {} by {}'.format(review_req.doc.name, reviewer.name) in message) + self.assertEqual(len(log), 1) + self.assertTrue(secretary.person.email_address() in log[0]) + self.assertTrue('1 overdue review' in log[0]) + def test_email_reminder_all_open_reviews(self): review_req = ReviewRequestFactory(state_id='assigned') reviewer = RoleFactory(name_id='reviewer', group=review_req.team,person__user__username='reviewer').person diff --git a/ietf/mailtrigger/migrations/0010_add_review_reminder_mailtriggers.py b/ietf/mailtrigger/migrations/0010_add_review_reminder_mailtriggers.py new file mode 100644 index 000000000..701870317 --- /dev/null +++ b/ietf/mailtrigger/migrations/0010_add_review_reminder_mailtriggers.py @@ -0,0 +1,35 @@ +# Copyright The IETF Trust 2019, All Rights Reserved +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, print_function, unicode_literals + +from django.db import migrations + + +def forward(apps, schema_editor): + MailTrigger = apps.get_model('mailtrigger', 'MailTrigger') + Recipient = apps.get_model('mailtrigger', 'Recipient') + + review_reminder_overdue_assignment = MailTrigger.objects.create( + slug="review_reminder_overdue_assignment", + desc="Recipients for overdue review assignment reminders", + ) + review_reminder_overdue_assignment.to.add( + Recipient.objects.get(slug='group_secretaries') + ) + + +def reverse(apps, schema_editor): + MailTrigger = apps.get_model('mailtrigger', 'MailTrigger') + MailTrigger.objects.filter(slug='review_reminder_overdue_assignment').delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ('mailtrigger', '0009_custom_review_complete_mailtriggers'), + ] + + operations = [ + migrations.RunPython(forward, reverse) + ] diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json index 509fcd23e..680312e97 100644 --- a/ietf/name/fixtures/names.json +++ b/ietf/name/fixtures/names.json @@ -4164,6 +4164,17 @@ "cc": [] } }, + { + "model": "mailtrigger.mailtrigger", + "pk": "review_reminder_overdue_assignment", + "fields": { + "desc": "Recipients for overdue review assignment reminders", + "to": [ + "group_secretaries" + ], + "cc": [] + } + }, { "fields": { "desc": "The person providing a comment to nomcom", diff --git a/ietf/review/utils.py b/ietf/review/utils.py index 3ee59fae2..a44d647d9 100644 --- a/ietf/review/utils.py +++ b/ietf/review/utils.py @@ -26,7 +26,8 @@ from ietf.iesg.models import TelechatDate from ietf.mailtrigger.utils import gather_address_lists from ietf.person.models import Person from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream -from ietf.review.models import (ReviewRequest, ReviewAssignment, ReviewRequestStateName, ReviewTypeName, +from ietf.review.models import (ReviewRequest, ReviewAssignment, ReviewRequestStateName, + ReviewTypeName, ReviewerSettings, UnavailablePeriod, ReviewWish, NextReviewerInTeam, ReviewSecretarySettings) from ietf.utils.mail import send_mail @@ -959,6 +960,37 @@ def email_unavaibility_period_ending_reminder(remind_date): return log +def email_review_reminder_overdue_assignment(remind_date): + min_overdue_days = 5 + min_deadline = remind_date + datetime.timedelta(days=min_overdue_days) + teams = Group.objects.exclude(reviewteamsettings=None) + log = [] + for team in teams: + assignments = ReviewAssignment.objects.filter( + state__in=("assigned", "accepted"), + review_request__deadline__lte=min_deadline, + review_request__team=team, + ) + if not assignments: + continue + + (to, cc) = gather_address_lists('review_reminder_overdue_assignment', group=team) + domain = Site.objects.get_current().domain + subject = "{} Overdue review{} for team {}".format( + len(assignments), pluralize(len(assignments)), team.acronym) + + send_mail(None, to, None, subject, "review/review_reminder_overdue_assignment.txt", { + "domain": domain, + "assignments": assignments, + "team": team, + "min_overdue_days": min_overdue_days, + }, cc=cc) + log.append("Emailed reminder to {} about {} overdue reviews in {}".format( + to, assignments.count(), team.acronym, + )) + return log + + def email_reminder_all_open_reviews(remind_date): log = [] # The origin date is arbitrarily chosen, to have a single reference date for "every X days" diff --git a/ietf/templates/review/review_reminder_overdue_assignment.txt b/ietf/templates/review/review_reminder_overdue_assignment.txt new file mode 100644 index 000000000..3252acc05 --- /dev/null +++ b/ietf/templates/review/review_reminder_overdue_assignment.txt @@ -0,0 +1,14 @@ +{% load ietf_filters %}{% autoescape off %}{% filter wordwrap:78 %} +Team {{ team.acronym }} has {{ assignments|length }} accepted or assigned review{{ assignments|length|pluralize }} overdue by at least {{ min_overdue_days }} days. + +All reviews overdue by at least {{ min_overdue_days }} days are listed below, including links to mark each of them as no response. + +{% for assignment in assignments %} +Review of {{ assignment.review_request.doc }} by {{ assignment.reviewer.person }} +https://{{ domain }}{% url 'ietf.doc.views_review.mark_reviewer_assignment_no_response' name=assignment.review_request.doc.name assignment_id=assignment.pk %} + +{% endfor %} + +This reminder will be sent daily. + +{% endfilter %}{% endautoescape %}