Skip to content

Commit 5c8be91

Browse files
committed
Improve review assignment slightly by sorting reviewers by latest
review - still missing a bunch of factors, and unassignment is now temporarily gone - Legacy-Id: 11531
1 parent 7aff7d3 commit 5c8be91

4 files changed

Lines changed: 74 additions & 5 deletions

File tree

ietf/doc/views_review.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
from ietf.review.utils import (active_review_teams, assign_review_request_to_reviewer,
1818
can_request_review_of_doc, can_manage_review_requests_for_team,
1919
email_review_request_change, make_new_review_request_from_existing,
20-
close_review_request_states, close_review_request)
20+
close_review_request_states, close_review_request,
21+
construct_review_request_assignment_choices)
2122
from ietf.review import mailarch
2223
from ietf.utils.fields import DatepickerDateField
2324
from ietf.utils.text import skip_prefix
@@ -212,6 +213,8 @@ def __init__(self, review_req, *args, **kwargs):
212213
f.queryset = f.queryset.filter(role__name="reviewer", role__group=review_req.team)
213214
if review_req.reviewer:
214215
f.initial = review_req.reviewer_id
216+
f.choices = construct_review_request_assignment_choices(f.queryset, review_req.team, review_req)
217+
215218

216219
@login_required
217220
def assign_reviewer(request, name, request_id):

ietf/group/views_review.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
extract_revision_ordered_review_requests_for_documents,
99
assign_review_request_to_reviewer,
1010
close_review_request,
11-
# email_review_request_change, make_new_review_request_from_existing,
11+
construct_review_request_assignment_choices,
12+
# make_new_review_request_from_existing,
1213
suggested_review_requests_for_team)
1314
from ietf.group.utils import get_group_or_404
1415
from ietf.person.fields import PersonEmailChoiceField
@@ -58,7 +59,7 @@ def __init__(self, review_req, *args, **kwargs):
5859
role__name="reviewer",
5960
role__group=review_req.team,
6061
)
61-
62+
self.fields["reviewer"].choices = construct_review_request_assignment_choices(self.fields["reviewer"].queryset, review_req.team, review_req)
6263
self.fields["reviewer"].widget.attrs["class"] = "form-control input-sm"
6364

6465
if self.is_bound:

ietf/review/models.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ class Reviewer(models.Model):
1111
reviewer and team."""
1212
team = models.ForeignKey(Group)
1313
person = models.ForeignKey(Person)
14-
frequency = models.IntegerField(default=30, help_text="Can review every N days")
14+
FREQUENCIES = [
15+
(7, "Once per week"),
16+
(14, "Once per fortnight"),
17+
(30, "Once per month"),
18+
(60, "Once per two months"),
19+
(90, "Once per quarter"),
20+
]
21+
frequency = models.IntegerField(default=30, help_text="Can review every N days", choices=FREQUENCIES)
1522
unavailable_until = models.DateTimeField(blank=True, null=True, help_text="When will this reviewer be available again")
1623
filter_re = models.CharField(max_length=255, blank=True)
1724
skip_next = models.IntegerField(default=0, help_text="Skip the next N review assignments")

ietf/review/utils.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
from collections import defaultdict
33

44
from django.contrib.sites.models import Site
5+
from django.db import models
56

67
from ietf.group.models import Group, Role
78
from ietf.doc.models import Document, DocEvent, State, LastCallDocEvent
89
from ietf.iesg.models import TelechatDate
910
from ietf.person.models import Person
1011
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream
11-
from ietf.review.models import ReviewRequest, ReviewRequestStateName, ReviewTypeName
12+
from ietf.review.models import ReviewRequest, ReviewRequestStateName, ReviewTypeName, Reviewer
1213
from ietf.utils.mail import send_mail
1314
from ietf.doc.utils import extract_complete_replaces_ancestor_mapping_for_docs
1415

@@ -273,3 +274,60 @@ def extract_revision_ordered_review_requests_for_documents(queryset, names):
273274
front = next_front
274275

275276
return res
277+
278+
def construct_review_request_assignment_choices(possible_emails, team, review_req=None):
279+
possible_emails = list(possible_emails)
280+
281+
reviewers = { r.person_id: r for r in Reviewer.objects.filter(team=team, person__in=[e.person_id for e in possible_emails]) }
282+
283+
latest_assignment_for_reviewer = dict(ReviewRequest.objects.filter(
284+
reviewer__in=possible_emails,
285+
).values_list("reviewer").annotate(models.Max("time")))
286+
287+
now = datetime.datetime.now()
288+
289+
rankings = []
290+
for e in possible_emails:
291+
reviewer = reviewers.get(e.person_id)
292+
if not reviewer:
293+
reviewer = Reviewer()
294+
295+
days_past = None
296+
latest = latest_assignment_for_reviewer.get(e.pk)
297+
if latest is not None:
298+
days_past = (now - latest).days - reviewer.frequency
299+
300+
# FIXME:
301+
# positive: (Perhaps do these separately? As initial values?)
302+
# has done review of previous rev
303+
# would like to review
304+
305+
# blocks:
306+
# connections to doc + filter_re
307+
# has rejected same request/completed partial review
308+
# is unavailable_until
309+
310+
if days_past is None:
311+
ready_for = "first time"
312+
else:
313+
d = int(round(days_past))
314+
if d > 0:
315+
ready_for = "ready for {} {}".format(d, "day" if d == 1 else "days")
316+
else:
317+
d = -d
318+
ready_for = "frequency exceeded - ready in {} {}".format(d, "day" if d == 1 else "days")
319+
320+
label = "{}: {}".format(e.person, ready_for)
321+
322+
rank = (-100000 if days_past is None else -days_past,)
323+
324+
rankings.append({
325+
"email": e,
326+
"rank": rank,
327+
"label": label,
328+
})
329+
330+
rankings.sort(key=lambda r: r["rank"])
331+
332+
# FIXME: empty choices
333+
return [(r["email"].pk, r["label"]) for r in rankings]

0 commit comments

Comments
 (0)