Skip to content

Commit 486b6da

Browse files
author
Sasha Romijn
committed
Fix ietf-tools#2475 - Send opt-in reminders for unconfirmed review assignments.
If enabled for a team, reminders will be sent every X days to reviewers for review assignments they have not accepted or rejected. Commit ready for merge. - Legacy-Id: 16792
1 parent cc64992 commit 486b6da

6 files changed

Lines changed: 113 additions & 7 deletions

File tree

ietf/bin/send-review-reminders

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ from ietf.review.utils import (
2525
review_assignments_needing_reviewer_reminder, email_reviewer_reminder,
2626
review_assignments_needing_secretary_reminder, email_secretary_reminder,
2727
email_unavaibility_period_ending_reminder, email_reminder_all_open_reviews,
28-
email_review_reminder_overdue_assignment)
28+
email_review_reminder_overdue_assignment, email_reminder_unconfirmed_assignments)
2929

3030
today = datetime.date.today()
3131

@@ -42,3 +42,4 @@ for assignment, secretary_role in review_assignments_needing_secretary_reminder(
4242
print('\n'.join(email_unavaibility_period_ending_reminder(today)))
4343
print('\n'.join(email_review_reminder_overdue_assignment(today)))
4444
print('\n'.join(email_reminder_all_open_reviews(today)))
45+
print('\n'.join(email_reminder_unconfirmed_assignments(today)))

ietf/group/tests_review.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
from ietf.group.models import Role
1818
from ietf.iesg.models import TelechatDate
1919
from ietf.person.models import Person
20-
from ietf.review.models import ReviewerSettings, UnavailablePeriod, ReviewSecretarySettings
20+
from ietf.review.models import ReviewerSettings, UnavailablePeriod, ReviewSecretarySettings, \
21+
ReviewTeamSettings
2122
from ietf.review.utils import (
2223
suggested_review_requests_for_team,
2324
review_assignments_needing_reviewer_reminder, email_reviewer_reminder,
2425
review_assignments_needing_secretary_reminder, email_secretary_reminder,
2526
reviewer_rotation_list,
2627
email_unavaibility_period_ending_reminder, email_reminder_all_open_reviews,
27-
email_review_reminder_overdue_assignment)
28+
email_review_reminder_overdue_assignment, email_reminder_unconfirmed_assignments)
2829
from ietf.name.models import ReviewResultName, ReviewRequestStateName, ReviewAssignmentStateName
2930
import ietf.group.views
3031
from ietf.utils.mail import outbox, empty_outbox
@@ -592,6 +593,34 @@ def test_email_reminder_all_open_reviews(self):
592593
self.assertTrue(reviewer.email_address() in log[0])
593594
self.assertTrue('1 open review' in log[0])
594595

596+
def test_email_reminder_unconfirmed_assignments(self):
597+
review_req = ReviewRequestFactory(state_id='assigned')
598+
reviewer = RoleFactory(name_id='reviewer', group=review_req.team, person__user__username='reviewer').person
599+
ReviewAssignmentFactory(review_request=review_req, state_id='assigned', assigned_on=review_req.time, reviewer=reviewer.email_set.first())
600+
RoleFactory(name_id='secr', group=review_req.team, person__user__username='reviewsecretary')
601+
today = datetime.date.today()
602+
603+
# By default, these reminders are disabled for all teams.
604+
empty_outbox()
605+
log = email_reminder_unconfirmed_assignments(today)
606+
self.assertEqual(len(outbox), 0)
607+
self.assertFalse(log)
608+
609+
ReviewTeamSettings.objects.update(remind_days_unconfirmed_assignments=1)
610+
611+
empty_outbox()
612+
log = email_reminder_unconfirmed_assignments(today)
613+
self.assertEqual(len(outbox), 1)
614+
self.assertIn(reviewer.email_address(), outbox[0]["To"])
615+
self.assertEqual(outbox[0]["Subject"], "Reminder: you have not responded to a review assignment")
616+
message = outbox[0].get_payload(decode=True).decode("utf-8")
617+
self.assertIn(review_req.team.acronym, message)
618+
self.assertIn('accept or reject the assignment on', message)
619+
self.assertIn(review_req.doc.name, message)
620+
self.assertEqual(len(log), 1)
621+
self.assertIn(reviewer.email_address(), log[0])
622+
self.assertIn('not accepted/rejected review assignment', log[0])
623+
595624

596625
class BulkAssignmentTests(TestCase):
597626

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright The IETF Trust 2019, All Rights Reserved
2+
# -*- coding: utf-8 -*-
3+
# Generated by Django 1.11.23 on 2019-10-01 04:40
4+
from __future__ import unicode_literals
5+
6+
from django.db import migrations, models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('review', '0015_add_remind_days_open_reviews'),
13+
]
14+
15+
operations = [
16+
migrations.AddField(
17+
model_name='reviewteamsettings',
18+
name='remind_days_unconfirmed_assignments',
19+
field=models.PositiveIntegerField(blank=True, help_text="To send a periodic email reminder to reviewers of review assignments that are not accepted yet, enter the number of days between these reminders. Clear the field if you don't want these reminders to be sent.", null=True, verbose_name='Periodic reminder of not yet accepted or rejected review assignments to reviewer every X days'),
20+
),
21+
22+
]

ietf/review/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ class ReviewTeamSettings(models.Model):
174174
review_results = models.ManyToManyField(ReviewResultName, default=get_default_review_results, related_name='reviewteamsettings_review_results_set')
175175
notify_ad_when = models.ManyToManyField(ReviewResultName, related_name='reviewteamsettings_notify_ad_set', blank=True)
176176
secr_mail_alias = models.CharField(verbose_name="Email alias for all of the review team secretaries", max_length=255, blank=True, help_text="Email alias for all of the review team secretaries")
177+
remind_days_unconfirmed_assignments = models.PositiveIntegerField(null=True, blank=True, verbose_name="Periodic reminder of not yet accepted or rejected review assignments to reviewer every X days", help_text="To send a periodic email reminder to reviewers of review assignments they have neither accepted nor rejected, enter the number of days between these reminders. Clear the field if you don't want these reminders to be sent.")
177178

178179
def __str__(self):
179180
return "%s" % (self.group.acronym,)

ietf/review/utils.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,15 @@
2929
from ietf.review.models import (ReviewRequest, ReviewAssignment, ReviewRequestStateName,
3030
ReviewTypeName,
3131
ReviewerSettings, UnavailablePeriod, ReviewWish, NextReviewerInTeam,
32-
ReviewSecretarySettings)
32+
ReviewSecretarySettings, ReviewTeamSettings)
3333
from ietf.utils.mail import send_mail
3434
from ietf.doc.utils import extract_complete_replaces_ancestor_mapping_for_docs
3535

36+
# The origin date is used to have a single reference date for "every X days".
37+
# This date is arbitrarily chosen and has no special meaning, but should be consistent.
38+
ORIGIN_DATE_PERIODIC_REMINDERS = datetime.date(2019, 1, 1)
39+
40+
3641
def active_review_teams():
3742
return Group.objects.filter(reviewteamsettings__isnull=False,state="active")
3843

@@ -993,9 +998,7 @@ def email_review_reminder_overdue_assignment(remind_date):
993998

994999
def email_reminder_all_open_reviews(remind_date):
9951000
log = []
996-
# The origin date is arbitrarily chosen, to have a single reference date for "every X days"
997-
origin_date = datetime.date(2019, 1, 1)
998-
days_since_origin = (remind_date - origin_date).days
1001+
days_since_origin = (remind_date - ORIGIN_DATE_PERIODIC_REMINDERS).days
9991002
relevant_reviewer_settings = ReviewerSettings.objects.filter(remind_days_open_reviews__isnull=False)
10001003

10011004
for reviewer_settings in relevant_reviewer_settings:
@@ -1028,6 +1031,47 @@ def email_reminder_all_open_reviews(remind_date):
10281031
return log
10291032

10301033

1034+
def email_reminder_unconfirmed_assignments(remind_date):
1035+
"""
1036+
Remind reviewers of any assigned ReviewAssignments which they have not
1037+
accepted or rejected, if enabled in ReviewTeamSettings.
1038+
"""
1039+
log = []
1040+
days_since_origin = (remind_date - ORIGIN_DATE_PERIODIC_REMINDERS).days
1041+
relevant_review_team_settings = ReviewTeamSettings.objects.filter(
1042+
remind_days_unconfirmed_assignments__isnull=False)
1043+
1044+
for review_team_settings in relevant_review_team_settings:
1045+
if days_since_origin % review_team_settings.remind_days_unconfirmed_assignments != 0:
1046+
continue
1047+
1048+
assignments = ReviewAssignment.objects.filter(
1049+
state='assigned',
1050+
review_request__team=review_team_settings.group,
1051+
)
1052+
if not assignments:
1053+
continue
1054+
1055+
for assignment in assignments:
1056+
to = assignment.reviewer.formatted_email()
1057+
subject = "Reminder: you have not responded to a review assignment"
1058+
domain = Site.objects.get_current().domain
1059+
review_request_url = urlreverse("ietf.doc.views_review.review_request", kwargs={
1060+
"name": assignment.review_request.doc.name,
1061+
"request_id": assignment.review_request.pk
1062+
})
1063+
1064+
send_mail(None, to, None, subject, "review/reviewer_reminder_unconfirmed_assignments.txt", {
1065+
"review_request_url": "https://{}{}".format(domain, review_request_url),
1066+
"assignment": assignment,
1067+
"team": assignment.review_request.team,
1068+
"remind_days": review_team_settings.remind_days_unconfirmed_assignments,
1069+
})
1070+
log.append("Emailed reminder to {} about not accepted/rejected review assignment {}".format(to, assignment.pk))
1071+
1072+
return log
1073+
1074+
10311075
def review_assignments_needing_reviewer_reminder(remind_date):
10321076
assignment_qs = ReviewAssignment.objects.filter(
10331077
state__in=("assigned", "accepted"),
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% load ietf_filters %}{% autoescape off %}{% filter wordwrap:78 %}This is just a friendly reminder that you have a review assignment which you have neither accepted nor rejected.
2+
3+
The review assignment is for {{ assignment.review_request.doc.name }} in team {{ team.acronym }}.
4+
You can accept or reject the assignment on:
5+
{{ review_request_url }}
6+
7+
You are receiving this reminder because your team secretary has configured the Datatracker to remind you every {{ remind_days }} day{{ remind_days|pluralize }}.
8+
9+
{% endfilter %}{% endautoescape %}

0 commit comments

Comments
 (0)