Skip to content

Commit a177dc6

Browse files
committed
Fix a couple of bugs, add test for reviewer overview page
- Legacy-Id: 12081
1 parent 4c7b284 commit a177dc6

9 files changed

Lines changed: 159 additions & 25 deletions

File tree

ietf/doc/resources.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
DocumentAuthor, DocEvent, StateDocEvent, DocHistory, ConsensusDocEvent, DocAlias,
1212
TelechatDocEvent, DocReminder, LastCallDocEvent, NewRevisionDocEvent, WriteupDocEvent,
1313
InitialReviewDocEvent, DocHistoryAuthor, BallotDocEvent, RelatedDocument,
14-
RelatedDocHistory, BallotPositionDocEvent)
14+
RelatedDocHistory, BallotPositionDocEvent, ReviewRequestDocEvent)
1515

1616

1717
from ietf.name.resources import BallotPositionNameResource, DocTypeNameResource
@@ -513,3 +513,32 @@ class Meta:
513513
}
514514
api.doc.register(BallotPositionDocEventResource())
515515

516+
517+
518+
from ietf.person.resources import PersonResource
519+
from ietf.review.resources import ReviewRequestResource
520+
from ietf.name.resources import ReviewRequestStateNameResource
521+
class ReviewRequestDocEventResource(ModelResource):
522+
by = ToOneField(PersonResource, 'by')
523+
doc = ToOneField(DocumentResource, 'doc')
524+
docevent_ptr = ToOneField(DocEventResource, 'docevent_ptr')
525+
review_request = ToOneField(ReviewRequestResource, 'review_request')
526+
state = ToOneField(ReviewRequestStateNameResource, 'state', null=True)
527+
class Meta:
528+
queryset = ReviewRequestDocEvent.objects.all()
529+
serializer = api.Serializer()
530+
cache = SimpleCache()
531+
#resource_name = 'reviewrequestdocevent'
532+
filtering = {
533+
"id": ALL,
534+
"time": ALL,
535+
"type": ALL,
536+
"desc": ALL,
537+
"by": ALL_WITH_RELATIONS,
538+
"doc": ALL_WITH_RELATIONS,
539+
"docevent_ptr": ALL_WITH_RELATIONS,
540+
"review_request": ALL_WITH_RELATIONS,
541+
"state": ALL_WITH_RELATIONS,
542+
}
543+
api.doc.register(ReviewRequestDocEventResource())
544+

ietf/group/tests_review.py

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
from ietf.doc.models import TelechatDocEvent
1010
from ietf.iesg.models import TelechatDate
1111
from ietf.person.models import Email, Person
12-
from ietf.review.models import ReviewRequest, ReviewRequestStateName, ReviewerSettings, UnavailablePeriod
12+
from ietf.review.models import ReviewRequest, ReviewerSettings, UnavailablePeriod
1313
from ietf.review.utils import suggested_review_requests_for_team
14+
from ietf.name.models import ReviewTypeName, ReviewResultName, ReviewRequestStateName
1415
import ietf.group.views_review
1516
from ietf.utils.mail import outbox, empty_outbox
1617

@@ -32,8 +33,8 @@ def test_review_requests(self):
3233
url = urlreverse(ietf.group.views_review.review_requests, kwargs={ 'acronym': group.acronym })
3334

3435
# close request, listed under closed
35-
review_req.state_id = "completed"
36-
review_req.result_id = "ready"
36+
review_req.state = ReviewRequestStateName.objects.get(slug="completed")
37+
review_req.result = ReviewResultName.objects.get(slug="ready")
3738
review_req.save()
3839

3940
r = self.client.get(url)
@@ -97,8 +98,50 @@ def test_suggested_review_requests(self):
9798
review_req.save()
9899

99100
self.assertEqual(len(suggested_review_requests_for_team(team)), 1)
100-
101101

102+
def test_reviewer_overview(self):
103+
doc = make_test_data()
104+
review_req1 = make_review_data(doc)
105+
review_req1.state = ReviewRequestStateName.objects.get(slug="completed")
106+
review_req1.save()
107+
108+
reviewer = review_req1.reviewer.person
109+
110+
ReviewRequest.objects.create(
111+
doc=review_req1.doc,
112+
team=review_req1.team,
113+
type_id="early",
114+
deadline=datetime.date.today() + datetime.timedelta(days=30),
115+
state_id="accepted",
116+
reviewer=review_req1.reviewer,
117+
requested_by=Person.objects.get(user__username="plain"),
118+
)
119+
120+
UnavailablePeriod.objects.create(
121+
team=review_req1.team,
122+
person=reviewer,
123+
start_date=datetime.date.today() - datetime.timedelta(days=10),
124+
availability="unavailable",
125+
)
126+
127+
settings = ReviewerSettings.objects.get(person=reviewer)
128+
settings.skip_next = 1
129+
settings.save()
130+
131+
group = review_req1.team
132+
133+
url = urlreverse(ietf.group.views_review.reviewer_overview, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id })
134+
135+
# get
136+
r = self.client.get(url)
137+
self.assertEqual(r.status_code, 200)
138+
self.assertTrue(unicode(reviewer) in unicontent(r))
139+
self.assertTrue(review_req1.doc.name in unicontent(r))
140+
141+
self.client.login(username="secretary", password="secretary+password")
142+
r = self.client.get(url)
143+
self.assertEqual(r.status_code, 200)
144+
102145
def test_manage_review_requests(self):
103146
doc = make_test_data()
104147
review_req1 = make_review_data(doc)
@@ -128,6 +171,33 @@ def test_manage_review_requests(self):
128171
requested_by=Person.objects.get(user__username="plain"),
129172
)
130173

174+
# previous reviews
175+
ReviewRequest.objects.create(
176+
time=datetime.datetime.now() - datetime.timedelta(days=100),
177+
requested_by=Person.objects.get(name="(System)"),
178+
doc=doc,
179+
type=ReviewTypeName.objects.get(slug="early"),
180+
team=review_req1.team,
181+
state=ReviewRequestStateName.objects.get(slug="completed"),
182+
result=ReviewResultName.objects.get(slug="ready-nits"),
183+
reviewed_rev="01",
184+
deadline=datetime.date.today() - datetime.timedelta(days=80),
185+
reviewer=review_req1.reviewer,
186+
)
187+
188+
ReviewRequest.objects.create(
189+
time=datetime.datetime.now() - datetime.timedelta(days=100),
190+
requested_by=Person.objects.get(name="(System)"),
191+
doc=doc,
192+
type=ReviewTypeName.objects.get(slug="early"),
193+
team=review_req1.team,
194+
state=ReviewRequestStateName.objects.get(slug="completed"),
195+
result=ReviewResultName.objects.get(slug="ready"),
196+
reviewed_rev="01",
197+
deadline=datetime.date.today() - datetime.timedelta(days=80),
198+
reviewer=review_req1.reviewer,
199+
)
200+
131201
# get
132202
r = self.client.get(url)
133203
self.assertEqual(r.status_code, 200)

ietf/group/views_review.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import datetime
1+
import datetime, math
22
from collections import defaultdict
33

44
from django.shortcuts import render, redirect, get_object_or_404
@@ -126,7 +126,8 @@ def reviewer_overview(request, acronym, group_type=None):
126126
for req_pk, doc, req_time, state, deadline, result, late_days, request_to_assignment_days, assignment_to_closure_days, request_to_closure_days in req_data:
127127
# any open requests pushes the others out
128128
if ((state in ("requested", "accepted") and len(latest_reqs) < MAX_REQS) or (len(latest_reqs) + open_reqs < MAX_REQS)):
129-
print review_state_by_slug.get(state), assignment_to_closure_days
129+
if assignment_to_closure_days is not None:
130+
assignment_to_closure_days = int(math.ceil(assignment_to_closure_days))
130131
latest_reqs.append((req_pk, doc, deadline, review_state_by_slug.get(state), assignment_to_closure_days))
131132
person.latest_reqs = latest_reqs
132133

@@ -205,14 +206,13 @@ def manage_review_requests(request, acronym, group_type=None):
205206
set(r.doc_id for r in review_requests),
206207
)
207208

208-
209209
# we need a mutable query dict for resetting upon saving with
210210
# conflicts
211211
query_dict = request.POST.copy() if request.method == "POST" else None
212212
for req in review_requests:
213213
l = []
214214
# take all on the latest reviewed rev
215-
for r in document_requests[req.doc_id]:
215+
for r in document_requests.get(req.doc_id, []):
216216
if l and l[0].reviewed_rev:
217217
if r.doc_id == l[0].doc_id and r.reviewed_rev:
218218
if int(r.reviewed_rev) > int(l[0].reviewed_rev):

ietf/ietfauth/tests.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import unicode_literals
33

4-
import os, shutil, time
4+
import os, shutil, time, datetime
55
from urlparse import urlsplit
66
from pyquery import PyQuery
77
from unittest import skipIf
@@ -18,7 +18,7 @@
1818
from ietf.group.models import Group, Role, RoleName
1919
from ietf.ietfauth.htpasswd import update_htpasswd_file
2020
from ietf.mailinglists.models import Subscribed
21-
from ietf.review.models import ReviewWish
21+
from ietf.review.models import ReviewWish, UnavailablePeriod
2222
from ietf.utils.decorators import skip_coverage
2323

2424
import ietf.ietfauth.views
@@ -348,6 +348,13 @@ def test_review_overview(self):
348348
review_req.reviewer = reviewer.email_set.first()
349349
review_req.save()
350350

351+
UnavailablePeriod.objects.create(
352+
team=review_req.team,
353+
person=reviewer,
354+
start_date=datetime.date.today() - datetime.timedelta(days=10),
355+
availability="unavailable",
356+
)
357+
351358
url = urlreverse(ietf.ietfauth.views.review_overview)
352359

353360
login_testing_unauthorized(self, reviewer.user.username, url)

ietf/review/import_from_review_tool.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -466,33 +466,37 @@ def fix_docname(docname):
466466
if "assigned" in event_collection:
467467
continue # skip requested unless there's no assigned event
468468

469-
e = ReviewRequestDocEvent.objects.filter(type="requested_review", doc=review_req.doc).first() or ReviewRequestDocEvent(type="requested_review", doc=review_req.doc)
469+
e = ReviewRequestDocEvent.objects.filter(type="requested_review", doc=review_req.doc, review_request=review_req).first()
470+
if not e:
471+
e = ReviewRequestDocEvent(type="requested_review", doc=review_req.doc, review_request=review_req)
470472
e.time = time
471473
e.by = by
472474
e.desc = "Requested {} review by {}".format(review_req.type.name, review_req.team.acronym.upper())
473-
e.review_request = review_req
474475
e.state = None
475476
e.skip_community_list_notification = True
476477
e.save()
477478
print "imported event requested_review", e.desc, e.doc_id
478479

479480
elif key == "assigned":
480-
e = ReviewRequestDocEvent.objects.filter(type="assigned_review_request", doc=review_req.doc).first() or ReviewRequestDocEvent(type="assigned_review_request", doc=review_req.doc)
481+
e = ReviewRequestDocEvent.objects.filter(type="assigned_review_request", doc=review_req.doc, review_request=review_req).first()
482+
if not e:
483+
e = ReviewRequestDocEvent(type="assigned_review_request", doc=review_req.doc, review_request=review_req)
481484
e.time = parse_timestamp(timestamp)
482485
e.by = by
483486
e.desc = "Request for {} review by {} is assigned to {}".format(
484487
review_req.type.name,
485488
review_req.team.acronym.upper(),
486489
review_req.reviewer.person if review_req.reviewer else "(None)",
487490
)
488-
e.review_request = review_req
489491
e.state = None
490492
e.skip_community_list_notification = True
491493
e.save()
492494
print "imported event assigned_review_request", e.pk, e.desc, e.doc_id
493495

494496
elif key == "closed" and review_req.state_id not in ("requested", "accepted"):
495-
e = ReviewRequestDocEvent.objects.filter(type="closed_review_request", doc=review_req.doc).first() or ReviewRequestDocEvent(type="closed_review_request", doc=review_req.doc)
497+
e = ReviewRequestDocEvent.objects.filter(type="closed_review_request", doc=review_req.doc, review_request=review_req).first()
498+
if not e:
499+
e = ReviewRequestDocEvent(type="closed_review_request", doc=review_req.doc, review_request=review_req)
496500
e.time = parse_timestamp(timestamp)
497501
e.by = by
498502
e.state = states.get(state) if state else None
@@ -512,7 +516,6 @@ def fix_docname(docname):
512516
)
513517
else:
514518
e.desc = "Closed request for {} review by {} with state '{}'".format(review_req.type.name, review_req.team.acronym.upper(), e.state.name)
515-
e.review_request = review_req
516519
e.skip_community_list_notification = True
517520
e.save()
518521
completion_event = e
@@ -580,4 +583,18 @@ def fix_docname(docname):
580583
review_req.state = states["overtaken"]
581584
review_req.save()
582585

586+
if "closed" not in event_collection and "assigned" in event_collection:
587+
e = ReviewRequestDocEvent.objects.filter(type="closed_review_request", doc=review_req.doc, review_request=review_req).first()
588+
if not e:
589+
e = ReviewRequestDocEvent(type="closed_review_request", doc=review_req.doc, review_request=review_req)
590+
e.time = datetime.datetime.now()
591+
e.by = by
592+
e.state = review_req.state
593+
e.desc = "Closed request for {} review by {} with state '{}'".format(review_req.type.name, review_req.team.acronym.upper(), e.state.name)
594+
e.skip_community_list_notification = True
595+
e.save()
596+
completion_event = e
597+
print "imported event closed_review_request (generated upon closing)", e.desc, e.doc_id
598+
599+
583600
print "imported review request", row.reviewid, "as", review_req.pk, review_req.time, review_req.deadline, review_req.type, review_req.doc_id, review_req.state, review_req.doc.get_state_slug("draft-iesg")

ietf/review/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,10 @@ def extract_review_request_data(teams=None, reviewers=None, time_from=None, time
161161
res = defaultdict(list)
162162

163163
# we may be dealing with a big bunch of data, so treat it carefully
164-
event_qs = ReviewRequest.objects.filter(filters).order_by("-time", "-id")
164+
event_qs = ReviewRequest.objects.filter(filters)
165165

166166
# left outer join with RequestRequestDocEvent for request/assign/close time
167-
event_qs = event_qs.values_list("pk", "doc", "time", "state", "deadline", "result", "team", "reviewer__person", "reviewrequestdocevent__time", "reviewrequestdocevent__type")
167+
event_qs = event_qs.values_list("pk", "doc", "time", "state", "deadline", "result", "team", "reviewer__person", "reviewrequestdocevent__time", "reviewrequestdocevent__type").order_by("-time", "-pk", "-reviewrequestdocevent__time")
168168

169169
def positive_days(time_from, time_to):
170170
if time_from is None or time_to is None:

ietf/static/ietf/css/ietf.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,11 +515,11 @@ form.email-open-review-assignments [name=body] {
515515
font-family: monospace;
516516
}
517517

518-
table.unavailable-periods td {
518+
table.simple-table td {
519519
padding-right: 0.5em;
520520
}
521521

522-
table.unavailable-periods td:last-child {
522+
table.simple-table td:last-child {
523523
padding-right: 0;
524524
}
525525

ietf/templates/group/reviewer_overview.html

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ <h2>Reviewers</h2>
1616
<thead>
1717
<tr>
1818
<th>Reviewer</th>
19-
<th>Latest assignments (deadline, state)</th>
19+
<th>Deadline/state/time between assignment and closure for latest assignments</th>
2020
<th>Settings</th>
2121
</tr>
2222
</thead>
@@ -25,9 +25,20 @@ <h2>Reviewers</h2>
2525
<tr {% if person.completely_unavailable %}class="completely-unavailable"{% endif %}>
2626
<td><a {% if person.settings_url %}href="{{ person.settings_url }}"{% endif %}>{{ person }}</a></td>
2727
<td>
28-
{% for req_pk, doc_name, deadline, state, assignment_to_closure_days in person.latest_reqs %}
29-
<div><a href="{% url "ietf.doc.views_review.review_request" name=doc_name request_id=req_pk %}">{{ deadline|date }} {{ doc_name }}: {{ state.name }} {% if assignment_to_closure_days != None %}{% if state.slug == "completed" or state.slug == "part-completed" %}(in {{ assignment_to_closure_days|floatformat }} day{{ assignment_to_closure_days|pluralize }}){% endif %}{% endif %}</a></div>
28+
<table class="simple-table">
29+
{% for req_pk, doc_name, deadline, state, assignment_to_closure_days in person.latest_reqs %}
30+
<tr>
31+
<td><a href="{% url "ietf.doc.views_review.review_request" name=doc_name request_id=req_pk %}">{{ deadline|date }}</a></td>
32+
<td>
33+
<span class="label label-{% if state.slug == "completed" or state.slug == "part-completed" %}success{% elif state.slug == "no-response" %}danger{% elif state.slug == "overtaken" %}warning{% elif state.slug == "requested" or state.slug == "accepted" %}primary{% else %}default{% endif %}">{{ state.name }}</span>
34+
</td>
35+
<td>
36+
{% if assignment_to_closure_days != None %}{{ assignment_to_closure_days }}&nbsp;day{{ assignment_to_closure_days|pluralize }}{% endif %}
37+
</td>
38+
<td>{{ doc_name }}</td>
39+
</div>
3040
{% endfor %}
41+
</table>
3142
</td>
3243
<td>
3344
{{ person.settings.get_min_interval_display }} {% if person.settings.skip_next %}(skip: {{ person.settings.skip_next }}){% endif %}<br>

ietf/templates/review/unavailable_table.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<table class="unavailable-periods">
1+
<table class="simple-table">
22
{% for p in unavailable_periods %}
33
<tr class="unavailable-period-{{ p.state }}">
44
<td>{{ p.start_date }} - {{ p.end_date|default:"" }}</td>

0 commit comments

Comments
 (0)