From cde7cfacd55cbfb068106fd2621cdd5a0acd369f Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 7 Nov 2016 22:32:53 +0000 Subject: [PATCH] Modify the reviewer overview page to display up to 10 historic reviews per person from the past year instead of only 5. Add basic reviewer stats to the assignment select box. - Legacy-Id: 12279 --- ietf/doc/tests_review.py | 1 + ietf/group/views_review.py | 20 +++++++------------- ietf/review/utils.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/ietf/doc/tests_review.py b/ietf/doc/tests_review.py index 28f2c806d..2613764d4 100644 --- a/ietf/doc/tests_review.py +++ b/ietf/doc/tests_review.py @@ -299,6 +299,7 @@ class ReviewTests(TestCase): self.assertIn("unavailable indefinitely", reviewer_label) self.assertIn("skip next 1", reviewer_label) self.assertIn("#1", reviewer_label) + self.assertIn("no response 0/1", reviewer_label) # assign empty_outbox() diff --git a/ietf/group/views_review.py b/ietf/group/views_review.py index 2e682fb53..73ec5692c 100644 --- a/ietf/group/views_review.py +++ b/ietf/group/views_review.py @@ -1,4 +1,4 @@ -import datetime, math, itertools +import datetime, math from collections import defaultdict from django.shortcuts import render, redirect, get_object_or_404 @@ -21,7 +21,7 @@ from ietf.review.utils import (can_manage_review_requests_for_team, current_unavailable_periods_for_reviewers, email_reviewer_availability_change, reviewer_rotation_list, - extract_review_request_data) + latest_review_requests_for_reviewers) from ietf.group.models import Role from ietf.group.utils import get_group_or_404, construct_group_menu_context from ietf.person.fields import PersonEmailChoiceField @@ -130,12 +130,7 @@ def reviewer_overview(request, acronym, group_type=None): today = datetime.date.today() - extracted_data = extract_review_request_data(teams=[group], time_from=today - datetime.timedelta(days=365), ordering=["reviewer"]) - req_data_for_reviewer = {} - for reviewer, req_data_items in itertools.groupby(extracted_data, key=lambda data: data.reviewer): - l = list(req_data_items) - l.reverse() - req_data_for_reviewer[reviewer] = l + req_data_for_reviewers = latest_review_requests_for_reviewers(group) review_state_by_slug = { n.slug: n for n in ReviewRequestStateName.objects.all() } for person in reviewers: @@ -152,13 +147,12 @@ def reviewer_overview(request, acronym, group_type=None): and (p.start_date is None or p.start_date <= today) and (p.end_date is None or today <= p.end_date) for p in person.unavailable_periods) - MAX_REQS = 5 - req_data = req_data_for_reviewer.get(person.pk, []) - open_reqs = sum(1 for d in req_data if d.state in ("requested", "accepted")) + MAX_CLOSED_REQS = 10 + req_data = req_data_for_reviewers.get(person.pk, []) + open_reqs = sum(1 for d in req_data if d.state in ["requested", "accepted"]) latest_reqs = [] for d in req_data: - # any open requests pushes the others out - if ((d.state in ("requested", "accepted") and len(latest_reqs) < MAX_REQS) or (len(latest_reqs) + open_reqs < MAX_REQS)): + if d.state in ["requested", "accepted"] or len(latest_reqs) < MAX_CLOSED_REQS + open_reqs: latest_reqs.append((d.req_pk, d.doc, d.reviewed_rev, d.deadline, review_state_by_slug.get(d.state), int(math.ceil(d.assignment_to_closure_days)) if d.assignment_to_closure_days is not None else None)) diff --git a/ietf/review/utils.py b/ietf/review/utils.py index aeedf0c68..5a482ff50 100644 --- a/ietf/review/utils.py +++ b/ietf/review/utils.py @@ -294,6 +294,23 @@ def sum_raw_review_request_aggregations(raw_aggregations): return state_dict, late_state_dict, result_dict, assignment_to_closure_days_list, assignment_to_closure_days_count +def latest_review_requests_for_reviewers(team, days_back=365): + """Collect and return stats for reviewers on latest requests, in + extract_review_request_data format.""" + + extracted_data = extract_review_request_data( + teams=[team], + time_from=datetime.date.today() - datetime.timedelta(days=days_back), + ordering=["reviewer"], + ) + + req_data_for_reviewers = { + reviewer: list(reversed(list(req_data_items))) + for reviewer, req_data_items in itertools.groupby(extracted_data, key=lambda data: data.reviewer) + } + + return req_data_for_reviewers + def make_new_review_request_from_existing(review_req): obj = ReviewRequest() obj.time = review_req.time @@ -717,6 +734,9 @@ def make_assignment_choices(email_queryset, review_req): # unavailable periods unavailable_periods = current_unavailable_periods_for_reviewers(team) + # reviewers statistics + req_data_for_reviewers = latest_review_requests_for_reviewers(team) + ranking = [] for e in possible_emails: settings = reviewer_settings.get(e.person_id) @@ -763,10 +783,26 @@ def make_assignment_choices(email_queryset, review_req): if settings.skip_next > 0: explanations.append("skip next {}".format(settings.skip_next)) + # index index = rotation_index.get(e.person_id, 0) scores.append(-index) explanations.append("#{}".format(index + 1)) + # stats + stats = [] + req_data = req_data_for_reviewers.get(e.person_id, []) + + currently_open = sum(1 for d in req_data if d.state in ["requested", "accepted"]) + if currently_open > 0: + stats.append("currently {} open".format(currently_open)) + could_have_completed = [d for d in req_data if d.state in ["part-completed", "completed", "no-response"]] + if could_have_completed: + no_response = sum(1 for d in could_have_completed if d.state == "no-response") + stats.append("no response {}/{}".format(no_response, len(could_have_completed))) + + if stats: + explanations.append(", ".join(stats)) + label = unicode(e.person) if explanations: label = u"{}: {}".format(label, u"; ".join(explanations))