Skip to content

Commit 77d7d8b

Browse files
committed
Add totals to review statistics tables and make it possible to select
multiple teams in the graph and get accumulated counts shown - Legacy-Id: 12223
1 parent 3835532 commit 77d7d8b

4 files changed

Lines changed: 94 additions & 33 deletions

File tree

ietf/review/utils.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,9 @@ def positive_days(time_from, time_to):
220220

221221
yield d
222222

223-
def aggregate_review_request_stats(review_request_data, count=None):
223+
def aggregate_raw_review_request_stats(review_request_data, count=None):
224224
"""Take a sequence of review request data from
225-
extract_review_request_data and compute aggregated statistics."""
225+
extract_review_request_data and aggregate them."""
226226

227227
state_dict = defaultdict(int)
228228
late_state_dict = defaultdict(int)
@@ -248,6 +248,12 @@ def aggregate_review_request_stats(review_request_data, count=None):
248248
assignment_to_closure_days_list.append(assignment_to_closure_days)
249249
assignment_to_closure_days_count += c
250250

251+
return state_dict, late_state_dict, result_dict, assignment_to_closure_days_list, assignment_to_closure_days_count
252+
253+
def compute_review_request_stats(raw_aggregation):
254+
"""Compute statistics from aggregated review request data."""
255+
state_dict, late_state_dict, result_dict, assignment_to_closure_days_list, assignment_to_closure_days_count = raw_aggregation
256+
251257
res = {}
252258
res["state"] = state_dict
253259
res["result"] = result_dict
@@ -265,6 +271,27 @@ def aggregate_review_request_stats(review_request_data, count=None):
265271

266272
return res
267273

274+
def sum_raw_review_request_aggregations(raw_aggregations):
275+
"""Collapse a sequence of aggregations into one aggregation."""
276+
state_dict = defaultdict(int)
277+
late_state_dict = defaultdict(int)
278+
result_dict = defaultdict(int)
279+
assignment_to_closure_days_list = []
280+
assignment_to_closure_days_count = 0
281+
282+
for raw_aggr in raw_aggregations:
283+
i_state_dict, i_late_state_dict, i_result_dict, i_assignment_to_closure_days_list, i_assignment_to_closure_days_count = raw_aggr
284+
for s, v in i_state_dict.iteritems():
285+
state_dict[s] += v
286+
for s, v in i_late_state_dict.iteritems():
287+
late_state_dict[s] += v
288+
for r, v in i_result_dict.iteritems():
289+
result_dict[r] += v
290+
291+
assignment_to_closure_days_list.extend(i_assignment_to_closure_days_list)
292+
assignment_to_closure_days_count += i_assignment_to_closure_days_count
293+
294+
return state_dict, late_state_dict, result_dict, assignment_to_closure_days_list, assignment_to_closure_days_count
268295

269296
def make_new_review_request_from_existing(review_req):
270297
obj = ReviewRequest()

ietf/static/ietf/css/ietf.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,10 @@ table.simple-table td:last-child {
572572
text-align: center;
573573
}
574574

575+
.review-stats tr.totals {
576+
font-weight: bold;
577+
}
578+
575579
.review-stats-teams {
576580
-moz-column-width: 18em;
577581
-webkit-column-width: 18em;

ietf/stats/views.py

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
import dateutil.relativedelta
99

10-
from ietf.review.utils import extract_review_request_data, aggregate_review_request_stats, ReviewRequestData
10+
from ietf.review.utils import (extract_review_request_data,
11+
aggregate_raw_review_request_stats,
12+
ReviewRequestData,
13+
compute_review_request_stats,
14+
sum_raw_review_request_aggregations)
1115
from ietf.group.models import Role, Group
1216
from ietf.person.models import Person
1317
from ietf.name.models import ReviewRequestStateName, ReviewResultName
@@ -59,12 +63,17 @@ def build_review_stats_url(stats_type_override=Ellipsis, acronym_override=Ellips
5963

6064
return base_url + query_part
6165

62-
def get_from_selection(get_parameter, possible_choices):
63-
val = request.GET.get(get_parameter)
64-
for slug, label, url in possible_choices:
65-
if slug == val:
66-
return slug
67-
return None
66+
def get_choice(get_parameter, possible_choices, multiple=False):
67+
values = request.GET.getlist(get_parameter)
68+
found = [t[0] for t in possible_choices if t[0] in values]
69+
70+
if multiple:
71+
return found
72+
else:
73+
if found:
74+
return found[0]
75+
else:
76+
return None
6877

6978
# which overview - team or reviewer
7079
if acronym:
@@ -96,7 +105,7 @@ def get_from_selection(get_parameter, possible_choices):
96105

97106
possible_count_choices = [ (slug, label, build_review_stats_url(get_overrides={ "count": slug })) for slug, label in possible_count_choices ]
98107

99-
count = get_from_selection("count", possible_count_choices) or ""
108+
count = get_choice("count", possible_count_choices) or ""
100109

101110
# time range
102111
def parse_date(s):
@@ -175,31 +184,41 @@ def parse_date(s):
175184
group_by_objs = { r.pk: r for r in query_reviewers }
176185
group_by_index = ReviewRequestData._fields.index("reviewer")
177186

178-
# now aggregate the data
187+
# now filter and aggregate the data
179188
possible_teams = possible_completion_types = possible_results = possible_states = None
180-
selected_team = selected_completion_type = selected_result = selected_state = None
189+
selected_teams = selected_completion_type = selected_result = selected_state = None
181190

182191
if stats_type == "time":
183-
possible_teams = [(t.acronym, t.acronym, build_review_stats_url(get_overrides={ "team": t.acronym })) for t in teams]
184-
selected_team = get_from_selection("team", possible_teams)
185-
query_teams = [t for t in query_teams if t.acronym == selected_team]
192+
possible_teams = [(t.acronym, t.acronym) for t in teams]
193+
selected_teams = get_choice("team", possible_teams, multiple=True)
186194

187-
extracted_data = extract_review_request_data(query_teams, query_reviewers, from_time, to_time, ordering=[level])
195+
def add_if_exists_else_subtract(element, l):
196+
if element in l:
197+
return [x for x in l if x != element]
198+
else:
199+
return l + [element]
200+
201+
possible_teams = [(slug, label, build_review_stats_url(get_overrides={
202+
"team": add_if_exists_else_subtract(slug, selected_teams)
203+
})) for slug, label in possible_teams]
204+
query_teams = [t for t in query_teams if t.acronym in selected_teams]
205+
206+
extracted_data = extract_review_request_data(query_teams, query_reviewers, from_time, to_time)
188207

189-
if stats_type == "time":
190208
req_time_index = ReviewRequestData._fields.index("req_time")
191209

192210
def time_key_fn(t):
193211
d = t[req_time_index].date()
194-
#d -= datetime.timedelta(days=d.weekday())
195-
d -= datetime.timedelta(days=d.day)
196-
return (t[group_by_index], d)
212+
#d -= datetime.timedelta(days=d.weekday()) # weekly
213+
d -= datetime.timedelta(days=d.day) # monthly
214+
return d
197215

198216
found_results = set()
199217
found_states = set()
200218
aggrs = []
201-
for (group_pk, d), request_data_items in itertools.groupby(extracted_data, key=time_key_fn):
202-
aggr = aggregate_review_request_stats(request_data_items, count=count)
219+
for d, request_data_items in itertools.groupby(extracted_data, key=time_key_fn):
220+
raw_aggr = aggregate_raw_review_request_stats(request_data_items, count=count)
221+
aggr = compute_review_request_stats(raw_aggr)
203222

204223
aggrs.append((d, aggr))
205224

@@ -225,21 +244,21 @@ def time_key_fn(t):
225244
for slug, label in possible_completion_types
226245
]
227246

228-
selected_completion_type = get_from_selection("completion", possible_completion_types)
247+
selected_completion_type = get_choice("completion", possible_completion_types)
229248

230249
possible_results = [
231250
(r.slug, r.name, build_review_stats_url(get_overrides={ "completion": None, "result": r.slug, "state": None }))
232251
for r in results
233252
]
234253

235-
selected_result = get_from_selection("result", possible_results)
254+
selected_result = get_choice("result", possible_results)
236255

237256
possible_states = [
238257
(s.slug, s.name, build_review_stats_url(get_overrides={ "completion": None, "result": None, "state": s.slug }))
239258
for s in states
240259
]
241260

242-
selected_state = get_from_selection("state", possible_states)
261+
selected_state = get_choice("state", possible_states)
243262

244263
if not selected_completion_type and not selected_result and not selected_state:
245264
selected_completion_type = "completed_in_time"
@@ -261,13 +280,18 @@ def time_key_fn(t):
261280
}])
262281

263282
else: # tabular data
283+
extracted_data = extract_review_request_data(query_teams, query_reviewers, from_time, to_time, ordering=[level])
264284

265285
data = []
266286

267287
found_results = set()
268288
found_states = set()
289+
raw_aggrs = []
269290
for group_pk, request_data_items in itertools.groupby(extracted_data, key=lambda t: t[group_by_index]):
270-
aggr = aggregate_review_request_stats(request_data_items, count=count)
291+
raw_aggr = aggregate_raw_review_request_stats(request_data_items, count=count)
292+
raw_aggrs.append(raw_aggr)
293+
294+
aggr = compute_review_request_stats(raw_aggr)
271295

272296
# skip zero-valued rows
273297
if aggr["open"] == 0 and aggr["completed"] == 0 and aggr["not_completed"] == 0:
@@ -282,6 +306,12 @@ def time_key_fn(t):
282306

283307
data.append(aggr)
284308

309+
# add totals row
310+
if len(raw_aggrs) > 1:
311+
totals = compute_review_request_stats(sum_raw_review_request_aggregations(raw_aggrs))
312+
totals["obj"] = "Totals"
313+
data.append(totals)
314+
285315
results = ReviewResultName.objects.filter(slug__in=found_results)
286316
states = ReviewRequestStateName.objects.filter(slug__in=found_states)
287317

@@ -313,7 +343,7 @@ def time_key_fn(t):
313343

314344
# time options
315345
"possible_teams": possible_teams,
316-
"selected_team": selected_team,
346+
"selected_teams": selected_teams,
317347
"possible_completion_types": possible_completion_types,
318348
"selected_completion_type": selected_completion_type,
319349
"possible_results": possible_results,

ietf/templates/stats/review_stats.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ <h1>
6767
Team:
6868
<div class="btn-group">
6969
{% for slug, label, url in possible_teams %}
70-
<a class="btn btn-default {% if slug == selected_team %}active{% endif %}" href="{{ url }}">{{ label }}</a>
70+
<a class="btn btn-default {% if slug in selected_teams %}active{% endif %}" href="{{ url }}">{{ label }}</a>
7171
{% endfor %}
7272
</div>
7373
</div>
7474

75-
{% if selected_team %}
75+
{% if selected_teams %}
7676
<div>
7777
Completion:
7878
<div class="btn-group">
@@ -127,7 +127,7 @@ <h3>Completion status and completion time</h3>
127127
</thead>
128128
<tbody>
129129
{% for row in data %}
130-
<tr>
130+
<tr {% if row.obj == "Totals" %}class="totals"{% endif %}>
131131
<td>{{ row.obj }}</td>
132132
<td>{{ row.open_in_time }}</td>
133133
<td>{{ row.open_late }}</td>
@@ -163,7 +163,7 @@ <h3>Results of completed reviews</h3>
163163
</thead>
164164
<tbody>
165165
{% for row in data %}
166-
<tr>
166+
<tr {% if row.obj == "Totals" %}class="totals"{% endif %}>
167167
<td>{{ row.obj }}</td>
168168
{% for c in row.result_list %}
169169
<td>{{ c }}</td>
@@ -192,7 +192,7 @@ <h3>Specific request states</h3>
192192
</thead>
193193
<tbody>
194194
{% for row in data %}
195-
<tr>
195+
<tr {% if row.obj == "Totals" %}class="totals"{% endif %}>
196196
<td>{{ row.obj }}</td>
197197
{% for c in row.state_list %}
198198
<td>{{ c }}</td>
@@ -202,7 +202,7 @@ <h3>Specific request states</h3>
202202
</tbody>
203203
</table>
204204

205-
{% elif stats_type == "time" and selected_team %}
205+
{% elif stats_type == "time" and selected_teams %}
206206

207207
<h3>Counts per month</h3>
208208

0 commit comments

Comments
 (0)