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
This commit is contained in:
Ole Laursen 2016-10-27 21:36:02 +00:00
parent 3835532d71
commit 77d7d8b236
4 changed files with 94 additions and 33 deletions

View file

@ -220,9 +220,9 @@ def extract_review_request_data(teams=None, reviewers=None, time_from=None, time
yield d
def aggregate_review_request_stats(review_request_data, count=None):
def aggregate_raw_review_request_stats(review_request_data, count=None):
"""Take a sequence of review request data from
extract_review_request_data and compute aggregated statistics."""
extract_review_request_data and aggregate them."""
state_dict = defaultdict(int)
late_state_dict = defaultdict(int)
@ -248,6 +248,12 @@ def aggregate_review_request_stats(review_request_data, count=None):
assignment_to_closure_days_list.append(assignment_to_closure_days)
assignment_to_closure_days_count += c
return state_dict, late_state_dict, result_dict, assignment_to_closure_days_list, assignment_to_closure_days_count
def compute_review_request_stats(raw_aggregation):
"""Compute statistics from aggregated review request data."""
state_dict, late_state_dict, result_dict, assignment_to_closure_days_list, assignment_to_closure_days_count = raw_aggregation
res = {}
res["state"] = state_dict
res["result"] = result_dict
@ -265,6 +271,27 @@ def aggregate_review_request_stats(review_request_data, count=None):
return res
def sum_raw_review_request_aggregations(raw_aggregations):
"""Collapse a sequence of aggregations into one aggregation."""
state_dict = defaultdict(int)
late_state_dict = defaultdict(int)
result_dict = defaultdict(int)
assignment_to_closure_days_list = []
assignment_to_closure_days_count = 0
for raw_aggr in raw_aggregations:
i_state_dict, i_late_state_dict, i_result_dict, i_assignment_to_closure_days_list, i_assignment_to_closure_days_count = raw_aggr
for s, v in i_state_dict.iteritems():
state_dict[s] += v
for s, v in i_late_state_dict.iteritems():
late_state_dict[s] += v
for r, v in i_result_dict.iteritems():
result_dict[r] += v
assignment_to_closure_days_list.extend(i_assignment_to_closure_days_list)
assignment_to_closure_days_count += i_assignment_to_closure_days_count
return state_dict, late_state_dict, result_dict, assignment_to_closure_days_list, assignment_to_closure_days_count
def make_new_review_request_from_existing(review_req):
obj = ReviewRequest()

View file

@ -572,6 +572,10 @@ table.simple-table td:last-child {
text-align: center;
}
.review-stats tr.totals {
font-weight: bold;
}
.review-stats-teams {
-moz-column-width: 18em;
-webkit-column-width: 18em;

View file

@ -7,7 +7,11 @@ from django.http import HttpResponseRedirect, HttpResponseForbidden
import dateutil.relativedelta
from ietf.review.utils import extract_review_request_data, aggregate_review_request_stats, ReviewRequestData
from ietf.review.utils import (extract_review_request_data,
aggregate_raw_review_request_stats,
ReviewRequestData,
compute_review_request_stats,
sum_raw_review_request_aggregations)
from ietf.group.models import Role, Group
from ietf.person.models import Person
from ietf.name.models import ReviewRequestStateName, ReviewResultName
@ -59,12 +63,17 @@ def review_stats(request, stats_type=None, acronym=None):
return base_url + query_part
def get_from_selection(get_parameter, possible_choices):
val = request.GET.get(get_parameter)
for slug, label, url in possible_choices:
if slug == val:
return slug
return None
def get_choice(get_parameter, possible_choices, multiple=False):
values = request.GET.getlist(get_parameter)
found = [t[0] for t in possible_choices if t[0] in values]
if multiple:
return found
else:
if found:
return found[0]
else:
return None
# which overview - team or reviewer
if acronym:
@ -96,7 +105,7 @@ def review_stats(request, stats_type=None, acronym=None):
possible_count_choices = [ (slug, label, build_review_stats_url(get_overrides={ "count": slug })) for slug, label in possible_count_choices ]
count = get_from_selection("count", possible_count_choices) or ""
count = get_choice("count", possible_count_choices) or ""
# time range
def parse_date(s):
@ -175,31 +184,41 @@ def review_stats(request, stats_type=None, acronym=None):
group_by_objs = { r.pk: r for r in query_reviewers }
group_by_index = ReviewRequestData._fields.index("reviewer")
# now aggregate the data
# now filter and aggregate the data
possible_teams = possible_completion_types = possible_results = possible_states = None
selected_team = selected_completion_type = selected_result = selected_state = None
selected_teams = selected_completion_type = selected_result = selected_state = None
if stats_type == "time":
possible_teams = [(t.acronym, t.acronym, build_review_stats_url(get_overrides={ "team": t.acronym })) for t in teams]
selected_team = get_from_selection("team", possible_teams)
query_teams = [t for t in query_teams if t.acronym == selected_team]
possible_teams = [(t.acronym, t.acronym) for t in teams]
selected_teams = get_choice("team", possible_teams, multiple=True)
extracted_data = extract_review_request_data(query_teams, query_reviewers, from_time, to_time, ordering=[level])
def add_if_exists_else_subtract(element, l):
if element in l:
return [x for x in l if x != element]
else:
return l + [element]
possible_teams = [(slug, label, build_review_stats_url(get_overrides={
"team": add_if_exists_else_subtract(slug, selected_teams)
})) for slug, label in possible_teams]
query_teams = [t for t in query_teams if t.acronym in selected_teams]
extracted_data = extract_review_request_data(query_teams, query_reviewers, from_time, to_time)
if stats_type == "time":
req_time_index = ReviewRequestData._fields.index("req_time")
def time_key_fn(t):
d = t[req_time_index].date()
#d -= datetime.timedelta(days=d.weekday())
d -= datetime.timedelta(days=d.day)
return (t[group_by_index], d)
#d -= datetime.timedelta(days=d.weekday()) # weekly
d -= datetime.timedelta(days=d.day) # monthly
return d
found_results = set()
found_states = set()
aggrs = []
for (group_pk, d), request_data_items in itertools.groupby(extracted_data, key=time_key_fn):
aggr = aggregate_review_request_stats(request_data_items, count=count)
for d, request_data_items in itertools.groupby(extracted_data, key=time_key_fn):
raw_aggr = aggregate_raw_review_request_stats(request_data_items, count=count)
aggr = compute_review_request_stats(raw_aggr)
aggrs.append((d, aggr))
@ -225,21 +244,21 @@ def review_stats(request, stats_type=None, acronym=None):
for slug, label in possible_completion_types
]
selected_completion_type = get_from_selection("completion", possible_completion_types)
selected_completion_type = get_choice("completion", possible_completion_types)
possible_results = [
(r.slug, r.name, build_review_stats_url(get_overrides={ "completion": None, "result": r.slug, "state": None }))
for r in results
]
selected_result = get_from_selection("result", possible_results)
selected_result = get_choice("result", possible_results)
possible_states = [
(s.slug, s.name, build_review_stats_url(get_overrides={ "completion": None, "result": None, "state": s.slug }))
for s in states
]
selected_state = get_from_selection("state", possible_states)
selected_state = get_choice("state", possible_states)
if not selected_completion_type and not selected_result and not selected_state:
selected_completion_type = "completed_in_time"
@ -261,13 +280,18 @@ def review_stats(request, stats_type=None, acronym=None):
}])
else: # tabular data
extracted_data = extract_review_request_data(query_teams, query_reviewers, from_time, to_time, ordering=[level])
data = []
found_results = set()
found_states = set()
raw_aggrs = []
for group_pk, request_data_items in itertools.groupby(extracted_data, key=lambda t: t[group_by_index]):
aggr = aggregate_review_request_stats(request_data_items, count=count)
raw_aggr = aggregate_raw_review_request_stats(request_data_items, count=count)
raw_aggrs.append(raw_aggr)
aggr = compute_review_request_stats(raw_aggr)
# skip zero-valued rows
if aggr["open"] == 0 and aggr["completed"] == 0 and aggr["not_completed"] == 0:
@ -282,6 +306,12 @@ def review_stats(request, stats_type=None, acronym=None):
data.append(aggr)
# add totals row
if len(raw_aggrs) > 1:
totals = compute_review_request_stats(sum_raw_review_request_aggregations(raw_aggrs))
totals["obj"] = "Totals"
data.append(totals)
results = ReviewResultName.objects.filter(slug__in=found_results)
states = ReviewRequestStateName.objects.filter(slug__in=found_states)
@ -313,7 +343,7 @@ def review_stats(request, stats_type=None, acronym=None):
# time options
"possible_teams": possible_teams,
"selected_team": selected_team,
"selected_teams": selected_teams,
"possible_completion_types": possible_completion_types,
"selected_completion_type": selected_completion_type,
"possible_results": possible_results,

View file

@ -67,12 +67,12 @@
Team:
<div class="btn-group">
{% for slug, label, url in possible_teams %}
<a class="btn btn-default {% if slug == selected_team %}active{% endif %}" href="{{ url }}">{{ label }}</a>
<a class="btn btn-default {% if slug in selected_teams %}active{% endif %}" href="{{ url }}">{{ label }}</a>
{% endfor %}
</div>
</div>
{% if selected_team %}
{% if selected_teams %}
<div>
Completion:
<div class="btn-group">
@ -127,7 +127,7 @@
</thead>
<tbody>
{% for row in data %}
<tr>
<tr {% if row.obj == "Totals" %}class="totals"{% endif %}>
<td>{{ row.obj }}</td>
<td>{{ row.open_in_time }}</td>
<td>{{ row.open_late }}</td>
@ -163,7 +163,7 @@
</thead>
<tbody>
{% for row in data %}
<tr>
<tr {% if row.obj == "Totals" %}class="totals"{% endif %}>
<td>{{ row.obj }}</td>
{% for c in row.result_list %}
<td>{{ c }}</td>
@ -192,7 +192,7 @@
</thead>
<tbody>
{% for row in data %}
<tr>
<tr {% if row.obj == "Totals" %}class="totals"{% endif %}>
<td>{{ row.obj }}</td>
{% for c in row.state_list %}
<td>{{ c }}</td>
@ -202,7 +202,7 @@
</tbody>
</table>
{% elif stats_type == "time" and selected_team %}
{% elif stats_type == "time" and selected_teams %}
<h3>Counts per month</h3>