Merged in [16705] from sasha@dashcare.nl:
Fix #2337 - Send periodic reminders of open reviews every X days (opt-in)
The interleaved_migrations_test currently fails due to the various
migrations that have been added for individual tickets/commits (unless
--permit-mixed-migrations is set). I think this is better fixed in a
later cleanup, as doing it now could cause confusion when merging
individual commits, and more migrations are likely to be added soon.
- Legacy-Id: 16823
Note: SVN reference [16705] has been migrated to Git commit ec56a03ec6
This commit is contained in:
commit
09e38dffa0
|
@ -24,8 +24,7 @@ import datetime
|
|||
from ietf.review.utils import (
|
||||
review_assignments_needing_reviewer_reminder, email_reviewer_reminder,
|
||||
review_assignments_needing_secretary_reminder, email_secretary_reminder,
|
||||
send_unavaibility_period_ending_reminder)
|
||||
|
||||
send_unavaibility_period_ending_reminder, send_reminder_all_open_reviews)
|
||||
today = datetime.date.today()
|
||||
|
||||
for assignment in review_assignments_needing_reviewer_reminder(today):
|
||||
|
@ -38,5 +37,9 @@ for assignment, secretary_role in review_assignments_needing_secretary_reminder(
|
|||
review_req = assignment.review_request
|
||||
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))
|
||||
|
||||
reminders_sent = send_unavaibility_period_ending_reminder(today)
|
||||
print('\n'.join(reminders_sent))
|
||||
period_end_reminders_sent = send_unavaibility_period_ending_reminder(today)
|
||||
print('\n'.join(period_end_reminders_sent))
|
||||
|
||||
open_reviews_reminders_sent = send_reminder_all_open_reviews(today)
|
||||
print('\n'.join(open_reviews_reminders_sent))
|
||||
|
||||
|
|
|
@ -273,7 +273,8 @@ class EmailOpenAssignmentsForm(forms.Form):
|
|||
class ReviewerSettingsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ReviewerSettings
|
||||
fields = ['min_interval', 'filter_re', 'skip_next', 'remind_days_before_deadline','expertise']
|
||||
fields = ['min_interval', 'filter_re', 'skip_next', 'remind_days_before_deadline',
|
||||
'remind_days_open_reviews', 'expertise']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
exclude_fields = kwargs.pop('exclude_fields', [])
|
||||
|
|
|
@ -23,7 +23,7 @@ from ietf.review.utils import (
|
|||
review_assignments_needing_reviewer_reminder, email_reviewer_reminder,
|
||||
review_assignments_needing_secretary_reminder, email_secretary_reminder,
|
||||
reviewer_rotation_list,
|
||||
send_unavaibility_period_ending_reminder)
|
||||
send_unavaibility_period_ending_reminder, send_reminder_all_open_reviews)
|
||||
from ietf.name.models import ReviewResultName, ReviewRequestStateName, ReviewAssignmentStateName
|
||||
import ietf.group.views
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
|
@ -316,6 +316,7 @@ class ReviewTests(TestCase):
|
|||
"min_interval": "7",
|
||||
"filter_re": "test-[regexp]",
|
||||
"remind_days_before_deadline": "6",
|
||||
"remind_days_open_reviews": "8",
|
||||
"expertise": "Some expertise",
|
||||
})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
@ -324,6 +325,7 @@ class ReviewTests(TestCase):
|
|||
self.assertEqual(settings.filter_re, "test-[regexp]")
|
||||
self.assertEqual(settings.skip_next, 0)
|
||||
self.assertEqual(settings.remind_days_before_deadline, 6)
|
||||
self.assertEqual(settings.remind_days_open_reviews, 8)
|
||||
self.assertEqual(settings.expertise, "Some expertise")
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue("reviewer availability" in outbox[0]["subject"].lower())
|
||||
|
@ -546,6 +548,29 @@ class ReviewTests(TestCase):
|
|||
self.assertTrue(reviewer.person.name in log[0])
|
||||
self.assertTrue(review_team.acronym in log[0])
|
||||
|
||||
def test_send_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
|
||||
ReviewAssignmentFactory(review_request=review_req, state_id='assigned', assigned_on=review_req.time, reviewer=reviewer.email_set.first())
|
||||
RoleFactory(name_id='secr', group=review_req.team, person__user__username='reviewsecretary')
|
||||
ReviewerSettingsFactory(team=review_req.team, person=reviewer, remind_days_open_reviews=1)
|
||||
|
||||
empty_outbox()
|
||||
today = datetime.date.today()
|
||||
log = send_reminder_all_open_reviews(today)
|
||||
|
||||
self.assertEqual(len(outbox), 1)
|
||||
self.assertTrue(reviewer.email_address() in outbox[0]["To"])
|
||||
self.assertEqual(outbox[0]["Subject"], "Reminder: you have 1 open review assignment")
|
||||
message = outbox[0].get_payload(decode=True).decode("utf-8")
|
||||
self.assertTrue(review_req.team.acronym in message)
|
||||
self.assertTrue('you have 1 open review' in message)
|
||||
self.assertTrue(review_req.doc.name in message)
|
||||
self.assertTrue(review_req.deadline.strftime('%Y-%m-%d') in message)
|
||||
self.assertEqual(len(log), 1)
|
||||
self.assertTrue(reviewer.email_address() in log[0])
|
||||
self.assertTrue('1 open review' in log[0])
|
||||
|
||||
|
||||
class BulkAssignmentTests(TestCase):
|
||||
|
||||
|
|
26
ietf/review/migrations/0015_add_remind_days_open_reviews.py
Normal file
26
ietf/review/migrations/0015_add_remind_days_open_reviews.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.23 on 2019-09-05 05:03
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('review', '0014_document_primary_key_cleanup'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='historicalreviewersettings',
|
||||
name='remind_days_open_reviews',
|
||||
field=models.PositiveIntegerField(blank=True, name="Periodic reminder of open reviews every X days", help_text="To get a periodic email reminder of all your open reviews, enter the number of days between these reminders. Clear the field if you don't want these reminders.", null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reviewersettings',
|
||||
name='remind_days_open_reviews',
|
||||
field=models.PositiveIntegerField(blank=True, verbose_name="Periodic reminder of open reviews every X days", help_text="To get a periodic email reminder of all your open reviews, enter the number of days between these reminders. Clear the field if you don't want these reminders.", null=True),
|
||||
),
|
||||
]
|
|
@ -36,7 +36,8 @@ class ReviewerSettings(models.Model):
|
|||
validators=[validate_regular_expression_string, ],
|
||||
help_text="Draft names matching this regular expression should not be assigned")
|
||||
skip_next = models.IntegerField(default=0, verbose_name="Skip next assignments")
|
||||
remind_days_before_deadline = models.IntegerField(null=True, blank=True, help_text="To get an email reminder in case you forget to do an assigned review, enter the number of days before review deadline you want to receive it. Clear the field if you don't want a reminder.")
|
||||
remind_days_before_deadline = models.IntegerField(null=True, blank=True, help_text="To get an email reminder in case you forget to do an assigned review, enter the number of days before review deadline you want to receive it. Clear the field if you don't want this reminder.")
|
||||
remind_days_open_reviews = models.PositiveIntegerField(null=True, blank=True, verbose_name="Periodic reminder of open reviews every X days", help_text="To get a periodic email reminder of all your open reviews, enter the number of days between these reminders. Clear the field if you don't want these reminders.")
|
||||
expertise = models.TextField(verbose_name="Reviewer's expertise in this team's area", max_length=2048, blank=True, help_text="Describe the reviewer's expertise in this team's area", default='')
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -12,6 +12,7 @@ import six
|
|||
from collections import defaultdict, namedtuple
|
||||
|
||||
from django.db.models import Q, Max, F
|
||||
from django.template.defaultfilters import pluralize
|
||||
from django.urls import reverse as urlreverse
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
|
@ -957,7 +958,44 @@ def send_unavaibility_period_ending_reminder(remind_date):
|
|||
to, period.person, period.team.acronym,period.pk))
|
||||
return log
|
||||
|
||||
|
||||
def send_reminder_all_open_reviews(remind_date):
|
||||
log = []
|
||||
# The origin date is arbitrarily chosen, to have a single reference date for "every X days"
|
||||
origin_date = datetime.date(2019, 1, 1)
|
||||
days_since_origin = (remind_date - origin_date).days
|
||||
relevant_reviewer_settings = ReviewerSettings.objects.filter(remind_days_open_reviews__isnull=False)
|
||||
|
||||
for reviewer_settings in relevant_reviewer_settings:
|
||||
if days_since_origin % reviewer_settings.remind_days_open_reviews != 0:
|
||||
continue
|
||||
|
||||
assignments = ReviewAssignment.objects.filter(
|
||||
state__in=("assigned", "accepted"),
|
||||
reviewer__person=reviewer_settings.person,
|
||||
)
|
||||
if not assignments:
|
||||
continue
|
||||
|
||||
to = reviewer_settings.person.formatted_email()
|
||||
subject = "Reminder: you have {} open review assignment{}".format(len(assignments), pluralize(len(assignments)))
|
||||
|
||||
domain = Site.objects.get_current().domain
|
||||
url = urlreverse("ietf.group.views.reviewer_overview",
|
||||
kwargs={"group_type": reviewer_settings.team.type_id,
|
||||
"acronym": reviewer_settings.team.acronym})
|
||||
|
||||
send_mail(None, to, None, subject, "review/reviewer_reminder_all_open_reviews.txt", {
|
||||
"reviewer_overview_url": "https://{}{}".format(domain, url),
|
||||
"assignments": assignments,
|
||||
"team": reviewer_settings.team,
|
||||
"remind_days": reviewer_settings.remind_days_open_reviews,
|
||||
})
|
||||
log.append("Emailed reminder to {} of their {} open reviews".format(to, len(assignments)))
|
||||
|
||||
return log
|
||||
|
||||
|
||||
def review_assignments_needing_reviewer_reminder(remind_date):
|
||||
assignment_qs = ReviewAssignment.objects.filter(
|
||||
state__in=("assigned", "accepted"),
|
||||
|
|
|
@ -146,6 +146,10 @@
|
|||
<th>Remind days before deadline</th>
|
||||
<td>{{ t.reviewer_settings.remind_days_before_deadline|default:"(Do not remind)" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Periodic reminder of open reviews every X days</th>
|
||||
<td>{{ t.reviewer_settings.remind_days_open_reviews|default:"(Do not remind)" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Unavailable periods</th>
|
||||
<td>
|
||||
|
|
10
ietf/templates/review/reviewer_reminder_all_open_reviews.txt
Normal file
10
ietf/templates/review/reviewer_reminder_all_open_reviews.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% load ietf_filters %}{% autoescape off %}{% filter wordwrap:78 %}This is just a friendly reminder that you have {{ assignments|length }} open review{{ assignments|length|pluralize }} in team {{ team.acronym }}.
|
||||
|
||||
The following reviews are open:{% for assignment in assignments %}
|
||||
- {{ assignment.review_request.doc.name }} (deadline {{ assignment.review_request.deadline }})
|
||||
{% endfor %}
|
||||
|
||||
You are receiving this reminder because you have configured the Datatracker to remind you of all your open reviews every {{ remind_days }} day{{ remind_days|pluralize }}. You can see your reviews and change your settings here:
|
||||
|
||||
{{ reviewer_overview_url }}
|
||||
{% endfilter %}{% endautoescape %}
|
Loading…
Reference in a new issue