Skip to content

Commit 0c0e758

Browse files
committed
Added sortable columns to Review Team Reviewers page. Partially addresses issue ietf-tools#2656 Commit ready for merge.
- Legacy-Id: 16094
1 parent 7224e06 commit 0c0e758

2 files changed

Lines changed: 66 additions & 45 deletions

File tree

ietf/group/views.py

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@
44

55
# Portion Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
66
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
7-
#
7+
#
88
# Redistribution and use in source and binary forms, with or without
9-
# modification, are permitted provided that the following conditions
9+
# modification, are permitted provided that the following conditions
1010
# are met:
11-
#
11+
#
1212
# * Redistributions of source code must retain the above copyright
1313
# notice, this list of conditions and the following disclaimer.
14-
#
14+
#
1515
# * Redistributions in binary form must reproduce the above
1616
# copyright notice, this list of conditions and the following
1717
# disclaimer in the documentation and/or other materials provided
1818
# with the distribution.
19-
#
19+
#
2020
# * Neither the name of the Nokia Corporation and/or its
2121
# subsidiary(-ies) nor the names of its contributors may be used
2222
# to endorse or promote products derived from this software
2323
# without specific prior written permission.
24-
#
24+
#
2525
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2626
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2727
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -73,12 +73,12 @@
7373
from ietf.group.mails import email_admin_re_charter, email_personnel_change, email_comment
7474
from ietf.group.models import ( Group, Role, GroupEvent, GroupStateTransitions, GroupURL,
7575
ChangeStateGroupEvent, GroupFeatures )
76-
from ietf.group.utils import (get_charter_text, can_manage_group_type,
76+
from ietf.group.utils import (get_charter_text, can_manage_group_type,
7777
milestone_reviewer_for_group_type, can_provide_status_update,
78-
can_manage_materials,
78+
can_manage_materials,
7979
construct_group_menu_context, get_group_materials,
80-
save_group_in_history, can_manage_group,
81-
get_group_or_404, setup_default_community_list_for_group, )
80+
save_group_in_history, can_manage_group,
81+
get_group_or_404, setup_default_community_list_for_group, )
8282
#
8383
from ietf.ietfauth.utils import has_role, is_authorized_in_group
8484
from ietf.mailtrigger.utils import gather_relevant_expansions
@@ -337,7 +337,7 @@ def active_programs(request):
337337
return render(request, 'group/active_programs.html', {'programs' : programs })
338338

339339
def active_areas(request):
340-
areas = Group.objects.filter(type="area", state="active").order_by("name")
340+
areas = Group.objects.filter(type="area", state="active").order_by("name")
341341
return render(request, 'group/active_areas.html', {'areas': areas })
342342

343343
def active_wgs(request):
@@ -371,7 +371,7 @@ def active_rgs(request):
371371
group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
372372

373373
return render(request, 'group/active_rgs.html', { 'irtf': irtf, 'groups': groups })
374-
374+
375375
def active_ags(request):
376376

377377
groups = Group.objects.filter(type="ag", state="active").order_by("acronym")
@@ -380,7 +380,7 @@ def active_ags(request):
380380
group.ads = sorted(roles(group, "ad"), key=extract_last_name)
381381

382382
return render(request, 'group/active_ags.html', { 'groups': groups })
383-
383+
384384
def bofs(request, group_type):
385385
groups = Group.objects.filter(type=group_type, state="bof")
386386
return render(request, 'group/bofs.html',dict(groups=groups))
@@ -415,7 +415,7 @@ def concluded_groups(request):
415415
sections['Teams'] = Group.objects.filter(type='team', state="conclude").select_related("state", "charter").order_by("parent__name","acronym")
416416

417417
for name, groups in sections.items():
418-
418+
419419
# add start/conclusion date
420420
d = dict((g.pk, g) for g in groups)
421421

@@ -519,9 +519,9 @@ def group_about(request, acronym, group_type=None):
519519
requested_close = group.state_id != "conclude" and e and e.type == "requested_close"
520520

521521
can_manage = can_manage_group_type(request.user, group)
522-
charter_submit_url = ""
523-
if group.features.has_chartering_process:
524-
charter_submit_url = urlreverse('ietf.doc.views_charter.submit', kwargs={ "name": charter_name_for_group(group) })
522+
charter_submit_url = ""
523+
if group.features.has_chartering_process:
524+
charter_submit_url = urlreverse('ietf.doc.views_charter.submit', kwargs={ "name": charter_name_for_group(group) })
525525

526526
can_provide_update = can_provide_status_update(request.user, group)
527527
status_update = group.latest_event(type="status_update")
@@ -606,7 +606,7 @@ def group_about_status_edit(request, acronym, group_type=None):
606606
by=login,
607607
type='status_update',
608608
desc=update_text,
609-
)
609+
)
610610
return redirect('ietf.group.views.group_about',acronym=group.acronym)
611611
else:
612612
form = None
@@ -617,7 +617,7 @@ def group_about_status_edit(request, acronym, group_type=None):
617617
form = StatusUpdateForm(initial={"content": old_update.desc if old_update else ""})
618618

619619
return render(request, 'group/group_about_status_edit.html',
620-
{
620+
{
621621
'form': form,
622622
'group':group,
623623
}
@@ -650,7 +650,7 @@ def email(request, acronym, group_type=None):
650650
'aliases':aliases,
651651
'group':group,
652652
'ietf_domain':settings.IETF_DOMAIN,
653-
}))
653+
}))
654654

655655
def history(request, acronym, group_type=None):
656656
group = get_group_or_404(acronym, group_type)
@@ -727,7 +727,7 @@ def email_aliases(request, acronym=None, group_type=None):
727727

728728
if not acronym:
729729
# require login for the overview page, but not for the group-specific
730-
# pages
730+
# pages
731731
if not request.user.is_authenticated:
732732
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
733733

@@ -789,7 +789,7 @@ def reorder_roles(roles, role_names):
789789
list += [ r for r in roles if r.name_id == name ]
790790
list += [ r for r in roles if not r in list ]
791791
return list
792-
792+
793793
def group_photos(request, group_type=None, acronym=None):
794794
group = get_object_or_404(Group, acronym=acronym)
795795
roles = sorted(Role.objects.filter(group__acronym=acronym),key=lambda x: x.name.name+x.person.last_name())
@@ -808,7 +808,7 @@ def group_photos(request, group_type=None, acronym=None):
808808
## XXX Remove after testing
809809
# def get_or_create_initial_charter(group, group_type):
810810
# charter_name = charter_name_for_group(group)
811-
#
811+
#
812812
# try:
813813
# charter = Document.objects.get(docalias__name=charter_name)
814814
# except Document.DoesNotExist:
@@ -822,40 +822,40 @@ def group_photos(request, group_type=None, acronym=None):
822822
# )
823823
# charter.save()
824824
# charter.set_state(State.objects.get(used=True, type="charter", slug="notrev"))
825-
#
825+
#
826826
# # Create an alias as well
827827
# DocAlias.objects.create(name=charter.name, document=charter)
828-
#
828+
#
829829
# return charter
830-
#
830+
#
831831
# @login_required
832832
# def submit_initial_charter(request, group_type=None, acronym=None):
833-
#
833+
#
834834
# # This needs refactoring.
835835
# # The signature assumed you could have groups with the same name, but with different types, which we do not allow.
836-
# # Consequently, this can be called with an existing group acronym and a type
836+
# # Consequently, this can be called with an existing group acronym and a type
837837
# # that doesn't match the existing group type. The code below essentially ignores the group_type argument.
838838
# #
839839
# # If possible, the use of get_or_create_initial_charter should be moved
840840
# # directly into charter_submit, and this function should go away.
841-
#
841+
#
842842
# if acronym==None:
843843
# raise Http404
844-
#
844+
#
845845
# group = get_object_or_404(Group, acronym=acronym)
846846
# if not group.features.has_chartering_process:
847847
# raise Http404
848-
#
848+
#
849849
# # This is where we start ignoring the passed in group_type
850850
# group_type = group.type_id
851-
#
851+
#
852852
# if not can_manage_group(request.user, group):
853853
# return HttpResponseForbidden("You don't have permission to access this view")
854-
#
854+
#
855855
# if not group.charter:
856856
# group.charter = get_or_create_initial_charter(group, group_type)
857857
# group.save()
858-
#
858+
#
859859
# return redirect('ietf.doc.views_charter.submit', name=group.charter.name, option="initcharter")
860860

861861
@login_required
@@ -1068,7 +1068,7 @@ def conclude(request, acronym, group_type=None):
10681068
kwargs = {'acronym':group.acronym}
10691069
if group_type:
10701070
kwargs['group_type'] = group_type
1071-
1071+
10721072
return redirect(group.features.about_page, **kwargs)
10731073
else:
10741074
form = ConcludeGroupForm()
@@ -1384,10 +1384,11 @@ def reviewer_overview(request, acronym, group_type=None):
13841384
person.completely_unavailable = any(p.availability == "unavailable"
13851385
and (p.start_date is None or p.start_date <= today) and (p.end_date is None or today <= p.end_date)
13861386
for p in person.unavailable_periods)
1387-
person.busy = person.id in days_needed
1388-
1387+
person.busy = person.id in days_needed
1388+
13891389

13901390
MAX_CLOSED_REQS = 10
1391+
days_since = 9999
13911392
req_data = req_data_for_reviewers.get(person.pk, [])
13921393
open_reqs = sum(1 for d in req_data if d.state in ["requested", "accepted"])
13931394
latest_reqs = []
@@ -1396,7 +1397,15 @@ def reviewer_overview(request, acronym, group_type=None):
13961397
latest_reqs.append((d.req_pk, d.doc, d.reviewed_rev, d.assigned_time, d.deadline,
13971398
review_state_by_slug.get(d.state),
13981399
int(math.ceil(d.assignment_to_closure_days)) if d.assignment_to_closure_days is not None else None))
1400+
if d.state in ["completed", "completed_in_time", "completed_late"]:
1401+
if d.assigned_time is not None:
1402+
delta = datetime.datetime.now() - d.assigned_time
1403+
if d.assignment_to_closure_days is not None:
1404+
days = int(delta.days - d.assignment_to_closure_days)
1405+
if days_since > days: days_since = days
1406+
13991407
person.latest_reqs = latest_reqs
1408+
person.days_since_completed_review = days_since
14001409

14011410
return render(request, 'group/reviewer_overview.html',
14021411
construct_group_menu_context(request, group, "reviewers", group_type, {
@@ -1503,7 +1512,7 @@ def manage_review_requests(request, acronym, group_type=None, assignment_status=
15031512
if not person in assignments_by_person:
15041513
assignments_by_person[person] = []
15051514
assignments_by_person[person].append(r)
1506-
1515+
15071516
# Make sure the any assignments to the person at the head
15081517
# of the rotation queue are processed first so that the queue
15091518
# rotates before any more assignments are processed
@@ -1517,15 +1526,15 @@ def manage_review_requests(request, acronym, group_type=None, assignment_status=
15171526

15181527
for review_req in reqs_to_assign:
15191528
assign_review_request_to_reviewer(request, review_req, review_req.form.cleaned_data["reviewer"],review_req.form.cleaned_data["add_skip"])
1520-
1529+
15211530
kwargs = { "acronym": group.acronym }
15221531
if group_type:
15231532
kwargs["group_type"] = group_type
15241533

15251534
if form_action == "save-continue":
15261535
if assignment_status:
15271536
kwargs["assignment_status"] = assignment_status
1528-
1537+
15291538
return redirect(manage_review_requests, **kwargs)
15301539
else:
15311540
import ietf.group.views
@@ -1625,7 +1634,7 @@ def email_open_review_assignments(request, acronym, group_type=None):
16251634
"rotation_list": reviewer_rotation_list(group)[:10],
16261635
"group" : group,
16271636
})
1628-
1637+
16291638
(msg,_,_) = parse_preformatted(partial_msg)
16301639

16311640
body = msg.get_payload()
@@ -1840,7 +1849,7 @@ def add_comment(request, acronym, group_type=None):
18401849

18411850
if not is_authorized_in_group(request.user,group):
18421851
return HttpResponseForbidden("You need to a chair, secretary, or delegate of this group to add a comment.")
1843-
1852+
18441853
if request.method == 'POST':
18451854
form = AddCommentForm(request.POST)
18461855
if form.is_valid():

ietf/templates/group/reviewer_overview.html

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
{% block group_content %}
1010
{% origin %}
1111

12+
{% block pagehead %}
13+
<link rel="stylesheet" href="{% static "jquery.tablesorter/css/theme.bootstrap.min.css" %}">
14+
{% endblock %}
15+
1216
{% if can_access_stats %}
1317
<h1 class="pull-right"><a href="{% url "ietf.stats.views.review_stats" stats_type="completion" acronym=group.acronym %}" class="icon-link">&nbsp;<span class="small fa fa-bar-chart">&nbsp;</span></a></h1>
1418
{% endif %}
@@ -25,11 +29,13 @@ <h2>Reviewers</h2>
2529
</div>
2630

2731
{% if reviewers %}
28-
<table class="table reviewer-overview">
32+
<table class="table reviewer-overview tablesorter">
2933
<thead>
3034
<tr>
35+
<th>Next</th>
3136
<th>Reviewer</th>
3237
<th>Assigned/Deadline/state/time between assignment and closure for latest assignments</th>
38+
<th>Days Since Completed</th>
3339
<th>Settings</th>
3440
</tr>
3541
</thead>
@@ -39,12 +45,13 @@ <h2>Reviewers</h2>
3945
{% elif person.busy %}class="busy"
4046
{% elif person.settings.skip_next %}class="skip-next"
4147
{% endif %}>
48+
<td>{{ forloop.counter }}</td>
4249
<td>{% if person.settings_url %}<a href="{{ person.settings_url }}" title="{{person.settings.expertise}}">{% endif %}{{ person }}{% if person.settings_url %}</a>{% endif %}</td>
4350
<td>
4451
<table class="simple-table">
4552
{% for req_pk, doc_name, reviewed_rev, assigned_time, deadline, state, assignment_to_closure_days in person.latest_reqs %}
4653
<tr>
47-
<td>{{ assigned_time|date }}</td>
54+
<td><span style="display:none">X</span>{{ assigned_time|date }}</td>
4855
<td><a href="{% url "ietf.doc.views_review.review_request" name=doc_name request_id=req_pk %}">{{ deadline|date }}</a></td>
4956
<td>
5057
<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>
@@ -57,6 +64,7 @@ <h2>Reviewers</h2>
5764
{% endfor %}
5865
</table>
5966
</td>
67+
<td> {% if person.days_since_completed_review != 9999 %} {{ person.days_since_completed_review }} {% else %} <span style="display:none">9999</span> </td> {% endif %}
6068
<td>
6169
{% if person.settings.min_interval %}
6270
{{ person.settings.get_min_interval_display }}<br>
@@ -78,9 +86,13 @@ <h2>Reviewers</h2>
7886
{% endfor %}
7987
</tbody>
8088
</table>
81-
89+
8290
{% else %}
8391
<p>No reviewers found.</p>
8492
{% endif %}
8593

8694
{% endblock %}
95+
96+
{% block js %}
97+
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
98+
{% endblock %}

0 commit comments

Comments
 (0)