Skip to content

Commit 554a839

Browse files
author
Sasha Romijn
committed
Fix ietf-tools#2336 - Add "select me next for an assignment".
Reviewers can set this flag in their reviewer settings, which triggers a mail to be sent to the secretary. They are then kept on top of the recommended assignment order. This flag is automatically reset when any assignment is made to the reviewer. - Legacy-Id: 17048
1 parent e188da5 commit 554a839

7 files changed

Lines changed: 63 additions & 17 deletions

File tree

ietf/group/forms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ class ReviewerSettingsForm(forms.ModelForm):
276276
class Meta:
277277
model = ReviewerSettings
278278
fields = ['min_interval', 'filter_re', 'skip_next', 'remind_days_before_deadline',
279-
'remind_days_open_reviews', 'expertise']
279+
'remind_days_open_reviews', 'request_assignment_next', 'expertise']
280280

281281
def __init__(self, *args, **kwargs):
282282
exclude_fields = kwargs.pop('exclude_fields', [])

ietf/group/tests_review.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ def test_change_reviewer_settings(self):
332332
"filter_re": "test-[regexp]",
333333
"remind_days_before_deadline": "6",
334334
"remind_days_open_reviews": "8",
335+
"request_assignment_next": "1",
335336
"expertise": "Some expertise",
336337
})
337338
self.assertEqual(r.status_code, 302)
@@ -347,6 +348,7 @@ def test_change_reviewer_settings(self):
347348
msg_content = outbox[0].get_payload(decode=True).decode("utf-8").lower()
348349
self.assertTrue("frequency changed", msg_content)
349350
self.assertTrue("skip next", msg_content)
351+
self.assertTrue("requested to be the next person", msg_content)
350352

351353
# Normal reviewer should not be able to change skip_next
352354
r = self.client.post(url, {

ietf/group/views.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1732,7 +1732,10 @@ def change_reviewer_settings(request, acronym, reviewer_email, group_type=None):
17321732
changes.append("Frequency changed to \"{}\" from \"{}\".".format(settings.get_min_interval_display() or "Not specified", prev_min_interval or "Not specified"))
17331733
if settings.skip_next != prev_skip_next:
17341734
changes.append("Skip next assignments changed to {} from {}.".format(settings.skip_next, prev_skip_next))
1735-
1735+
if settings.request_assignment_next:
1736+
changes.append("Reviewer has requested to be the next person selected for an "
1737+
"assignment, as soon as possible, and will be on the top of "
1738+
"the queue.")
17361739
if changes:
17371740
email_reviewer_availability_change(request, group, reviewer_role, "\n\n".join(changes), request.user.person)
17381741

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright The IETF Trust 2016-2019, All Rights Reserved
2+
# -*- coding: utf-8 -*-
3+
# Generated by Django 1.11.23 on 2019-11-18 04:26
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', '0019_auto_20191023_0829'),
13+
]
14+
15+
operations = [
16+
migrations.AddField(
17+
model_name='historicalreviewersettings',
18+
name='request_assignment_next',
19+
field=models.BooleanField(default=False, help_text='If you would like to be assigned to a review as soon as possible, select this option. It is automatically reset once you receive any assignment.', verbose_name='Select me next for an assignment'),
20+
),
21+
migrations.AddField(
22+
model_name='reviewersettings',
23+
name='request_assignment_next',
24+
field=models.BooleanField(default=False, help_text='If you would like to be assigned to a review as soon as possible, select this option. It is automatically reset once you receive any assignment.', verbose_name='Select me next for an assignment'),
25+
),
26+
]

ietf/review/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class ReviewerSettings(models.Model):
3838
skip_next = models.IntegerField(default=0, verbose_name="Skip next assignments")
3939
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.")
4040
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.")
41+
request_assignment_next = models.BooleanField(default=False, verbose_name="Select me next for an assignment", help_text="If you would like to be assigned to a review as soon as possible, select this option. It is automatically reset once you receive any assignment.")
4142
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='')
4243

4344
def __str__(self):

ietf/review/policies.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ def default_reviewer_rotation_list(self, dont_skip_person_ids=None):
3939
"""
4040
raise NotImplementedError # pragma: no cover
4141

42-
def update_policy_state_for_assignment(self, assignee_person_id, add_skip=False):
42+
def update_policy_state_for_assignment(self, assignee_person, add_skip=False):
4343
"""
4444
Update the internal state of a policy to reflect an assignment.
4545
"""
46-
raise NotImplementedError # pragma: no cover
46+
settings = self._reviewer_settings_for(assignee_person)
47+
settings.request_assignment_next = False
48+
settings.save()
4749

4850
# TODO : Change this field to deal with multiple already assigned reviewers???
4951
def setup_reviewer_field(self, field, review_req):
@@ -89,8 +91,12 @@ def _entirely_unavailable_reviewers(self, dont_skip_person_ids=None):
8991
reviewers_to_skip.add(person_id)
9092

9193
return reviewers_to_skip
92-
94+
95+
def _reviewer_settings_for(self, person):
96+
return (ReviewerSettings.objects.filter(team=self.team, person=person).first()
97+
or ReviewerSettings(team=self.team, person=person))
9398

99+
94100
class AssignmentOrderResolver:
95101
"""
96102
The AssignmentOrderResolver resolves the "recommended assignment order",
@@ -178,7 +184,7 @@ def format_period(p):
178184
if periods:
179185
explanations.append(", ".join(format_period(p) for p in periods))
180186

181-
# misc
187+
add_boolean_score(+1, settings.request_assignment_next, "requested to be selected next for assignment")
182188
add_boolean_score(+1, email.person_id in self.has_reviewed_previous, "reviewed document before")
183189
add_boolean_score(+1, email.person_id in self.wish_to_review, "wishes to review document")
184190
add_boolean_score(-1, email.person_id in self.connections,
@@ -294,6 +300,7 @@ def _reviewer_settings_for_person_ids(self, person_ids):
294300
class RotateAlphabeticallyReviewerQueuePolicy(AbstractReviewerQueuePolicy):
295301

296302
def update_policy_state_for_assignment(self, assignee_person, add_skip=False):
303+
super(RotateAlphabeticallyReviewerQueuePolicy, self).update_policy_state_for_assignment(assignee_person, add_skip)
297304
assert assignee_person is not None
298305

299306
rotation_list = self.default_reviewer_rotation_list(dont_skip_person_ids=[assignee_person.pk])
@@ -303,14 +310,10 @@ def reviewer_at_index(i):
303310
return None
304311
return rotation_list[i % len(rotation_list)]
305312

306-
def reviewer_settings_for(person):
307-
return (ReviewerSettings.objects.filter(team=self.team, person=person).first()
308-
or ReviewerSettings(team=self.team, person=person))
309-
310313
if not rotation_list:
311314
return
312315

313-
rotation_list_without_skip = [r for r in rotation_list if not reviewer_settings_for(r).skip_next]
316+
rotation_list_without_skip = [r for r in rotation_list if not self._reviewer_settings_for(r).skip_next]
314317
# In order means: assigned to the first person in the rotation list with skip_next=0
315318
# If the assignment is not in order, skip_next and NextReviewerInTeam are not modified.
316319
in_order_assignment = rotation_list_without_skip[0] == assignee_person
@@ -322,7 +325,7 @@ def reviewer_settings_for(person):
322325
if in_order_assignment:
323326
while True:
324327
current_idx_person = reviewer_at_index(current_idx)
325-
settings = reviewer_settings_for(current_idx_person)
328+
settings = self._reviewer_settings_for(current_idx_person)
326329
if settings.skip_next > 0:
327330
settings.skip_next -= 1
328331
settings.save()
@@ -336,7 +339,7 @@ def reviewer_settings_for(person):
336339
current_idx += 1
337340

338341
if add_skip:
339-
settings = reviewer_settings_for(assignee_person)
342+
settings = self._reviewer_settings_for(assignee_person)
340343
settings.skip_next += 1
341344
settings.save()
342345

ietf/review/test_policies.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,18 @@ def get_skip_next(person):
113113
return reviewer_settings_for(person).skip_next
114114

115115
# Regular in-order assignment without skips
116+
reviewer0_settings = reviewer_settings_for(reviewers[0])
117+
reviewer0_settings.request_assignment_next = True
118+
reviewer0_settings.save()
116119
policy.update_policy_state_for_assignment(assignee_person=reviewers[0], add_skip=False)
117120
self.assertEqual(NextReviewerInTeam.objects.get(team=team).next_reviewer, reviewers[1])
118121
self.assertEqual(get_skip_next(reviewers[0]), 0)
119122
self.assertEqual(get_skip_next(reviewers[1]), 0)
120123
self.assertEqual(get_skip_next(reviewers[2]), 0)
121124
self.assertEqual(get_skip_next(reviewers[3]), 0)
122125
self.assertEqual(get_skip_next(reviewers[4]), 0)
126+
# request_assignment_next should be reset after any assignment
127+
self.assertFalse(reviewer_settings_for(reviewers[0]).request_assignment_next)
123128

124129
# In-order assignment with add_skip
125130
policy.update_policy_state_for_assignment(assignee_person=reviewers[1], add_skip=True)
@@ -214,21 +219,27 @@ def test_determine_ranking(self):
214219

215220
# Trigger max frequency and open review stats
216221
ReviewAssignmentFactory(review_request__team=team, reviewer=reviewer_low.email(), state_id='assigned', review_request__doc__pages=10)
217-
# Trigger skip_next, max frequency and filter_re
222+
# Trigger skip_next, max frequency, filter_re
218223
ReviewerSettings.objects.create(
219224
team=team,
220225
person=reviewer_low,
221226
filter_re='.*draft.*',
222227
skip_next=2,
223228
min_interval=91,
224229
)
230+
# Trigger "assign me next"
231+
ReviewerSettings.objects.create(
232+
team=team,
233+
person=reviewer_high,
234+
request_assignment_next=True,
235+
)
225236

226237
order = AssignmentOrderResolver(Email.objects.all(), review_req, rotation_list)
227238
ranking = order.determine_ranking()
228239
self.assertEqual(len(ranking), 2)
229240
self.assertEqual(ranking[0]['email'], reviewer_high.email())
230241
self.assertEqual(ranking[1]['email'], reviewer_low.email())
231-
self.assertEqual(ranking[0]['scores'], [ 1, 1, 1, 1, 0, 0, -1])
232-
self.assertEqual(ranking[1]['scores'], [-1, -1, -1, -1, -91, -2, 0])
233-
self.assertEqual(ranking[0]['label'], 'Test Reviewer-high: unavailable indefinitely (Can do follow-ups); reviewed document before; wishes to review document; #2; 1 no response, 1 partially complete, 1 fully completed')
242+
self.assertEqual(ranking[0]['scores'], [ 1, 1, 1, 1, 1, 0, 0, -1])
243+
self.assertEqual(ranking[1]['scores'], [-1, -1, -1, -1, -1, -91, -2, 0])
244+
self.assertEqual(ranking[0]['label'], 'Test Reviewer-high: unavailable indefinitely (Can do follow-ups); requested to be selected next for assignment; reviewed document before; wishes to review document; #2; 1 no response, 1 partially complete, 1 fully completed')
234245
self.assertEqual(ranking[1]['label'], 'Test Reviewer-low: is author of document; filter regexp matches; max frequency exceeded, ready in 91 days; skip next 2; #1; currently 1 open, 10 pages')

0 commit comments

Comments
 (0)