* chore: Replace flot with highcharts Since flot hasn't been updated since 2014 and was only used in one place. Simplify how highcharts is initialized and used, and re-enable pre-bs5 export functionality. * Fix tests * Remove some console.log statements
370 lines
15 KiB
HTML
370 lines
15 KiB
HTML
{% extends "base.html" %}
|
|
{% load origin %}
|
|
{% origin %}
|
|
{% load ietf_filters static django_bootstrap5 %}
|
|
{% block pagehead %}
|
|
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
|
|
<link rel="stylesheet" href="{% static 'ietf/css/datepicker.css' %}">
|
|
{% endblock %}
|
|
{% block content %}
|
|
{% origin %}
|
|
<h1>
|
|
{% block title %}
|
|
{% if level == "team" %}
|
|
Statistics for review teams
|
|
{% elif level == "reviewer" %}
|
|
Statistics for reviewers in {{ reviewers_for_team.name }}
|
|
{% endif %}
|
|
{% endblock %}
|
|
</h1>
|
|
{% if level == "reviewer" %}
|
|
<p>
|
|
<a href="{{ team_level_url }}">« Back to teams</a>
|
|
</p>
|
|
{% endif %}
|
|
<div class="row my-3">
|
|
<label class="fw-bold col-sm-2 col-form-label">Show:</label>
|
|
<div class="btn-group col-sm-10">
|
|
{% for slug, label, url in possible_stats_types %}
|
|
<a class="btn btn-outline-primary
|
|
{% if slug == stats_type %}
|
|
active
|
|
{% endif %}"
|
|
href="{{ url }}">{{ label }}</a>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div class="row my-3">
|
|
<label class="fw-bold col-sm-2 col-form-label">Count:</label>
|
|
<div class="btn-group col-sm-10">
|
|
{% for slug, label, url in possible_count_choices %}
|
|
<a class="btn btn-outline-primary
|
|
{% if slug == count %}
|
|
active
|
|
{% endif %}"
|
|
href="{{ url }}">{{ label }}</a>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<form class="date-range">
|
|
<div class="row my-3">
|
|
<label id="reqtime" class="fw-bold col-sm-2 col-form-label">Request time:</label>
|
|
<div class="col-sm-10">
|
|
<div class="input-group">
|
|
<label class="d-none" for="from" aria-label="Request time, from"></label>
|
|
<input class="form-control"
|
|
type="text"
|
|
name="from"
|
|
id="from"
|
|
value="{{ from_date }}"
|
|
data-provide="datepicker"
|
|
data-date-format="yyyy-mm-dd"
|
|
data-date-autoclose="1"
|
|
data-date-orientation="auto bottom"
|
|
data-date-end-date="{{ today.isoformat }}"
|
|
data-date-start-view="months">
|
|
<span class="input-group-text" id="basic-addon2">—</span>
|
|
<label class="d-none" for="to" aria-label="Request time, to"></label>
|
|
<input class="form-control"
|
|
type="text"
|
|
name="to"
|
|
id="to"
|
|
value="{{ to_date }}"
|
|
data-provide="datepicker"
|
|
data-date-format="yyyy-mm-dd"
|
|
data-date-autoclose="1"
|
|
data-date-orientation="auto bottom"
|
|
data-date-end-date="{{ today.isoformat }}"
|
|
data-date-start-view="months">
|
|
{% for name, value in request.GET.iteritems %}
|
|
{% if name != "from" and name != "to" %}<input type="hidden" name="{{ name }}" value="{{ value }}">{% endif %}
|
|
{% endfor %}
|
|
<button class="btn btn-primary" type="submit">Set</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
{% if stats_type == "time" %}
|
|
<div class="row my-3">
|
|
<label class="fw-bold col-sm-2 col-form-label">Team:</label>
|
|
<div class="btn-group col-sm-10">
|
|
{% for slug, label, url in possible_teams %}
|
|
<a class="btn btn-outline-primary
|
|
{% if slug in selected_teams %}
|
|
active
|
|
{% endif %}"
|
|
href="{{ url }}">{{ label }}</a>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% if selected_teams %}
|
|
<div class="row my-3">
|
|
<label class="fw-bold col-sm-2 col-form-label">Completion:</label>
|
|
<div class="btn-group col-sm-10">
|
|
{% for slug, label, url in possible_completion_types %}
|
|
<a class="btn btn-outline-primary
|
|
{% if slug == selected_completion_type %}
|
|
active
|
|
{% endif %}"
|
|
href="{{ url }}">
|
|
{{ label }}
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div class="row my-3">
|
|
<label class="fw-bold col-sm-2 col-form-label">Result:</label>
|
|
<div class="btn-group col-sm-10">
|
|
{% for slug, label, url in possible_results %}
|
|
<a class="btn btn-outline-primary
|
|
{% if slug == selected_result %}
|
|
active
|
|
{% endif %}"
|
|
href="{{ url }}">{{ label }}</a>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div class="row my-3">
|
|
<label class="fw-bold col-sm-2 col-form-label">State:</label>
|
|
<div class="btn-group col-sm-10">
|
|
{% for slug, label, url in possible_states %}
|
|
<a class="btn btn-outline-primary
|
|
{% if slug == selected_state %}
|
|
active
|
|
{% endif %}"
|
|
href="{{ url }}">{{ label }}</a>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% if stats_type == "completion" %}
|
|
<h2>Completion status and completion time</h2>
|
|
<table class="review-stats table table-sm table-striped tablesorter">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col" data-sort="{{ level }}">
|
|
{% if level == "team" %}
|
|
Team
|
|
{% elif level == "reviewer" %}
|
|
Reviewer
|
|
{% endif %}
|
|
</th>
|
|
<th scope="col" data-sort="req-num"
|
|
title="Requests that are currently requested or accepted by reviewer">Open in time</th>
|
|
<th scope="col" data-sort="past-num"
|
|
title="Requests that are currently requested or accepted by reviewer and past the deadline">
|
|
Open late
|
|
</th>
|
|
<th scope="col" data-sort="comp-num"
|
|
title="Requests that have been completed partially or completely">Completed in time</th>
|
|
<th scope="col" data-sort="comppast-num"
|
|
title="Requests that have been completed partially or completely past the deadline">
|
|
Completed late
|
|
</th>
|
|
<th scope="col" data-sort="rej-num"
|
|
title="Requests that are rejected by the reviewer, withdrawn, overtaken by events or with no response from reviewer">
|
|
Not completed
|
|
</th>
|
|
<th scope="col" data-sort="num"
|
|
title="Average time between assignment and completion for completed reviews, in days">
|
|
Avg. compl. days
|
|
{% if count == "pages" %}/page{% endif %}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
{% for row in data %}
|
|
{% if forloop.first %}<tbody>{% endif %}
|
|
{% if forloop.last %}</tbody><tfoot>{% endif %}
|
|
<tr>
|
|
<td>{{ row.obj }}</td>
|
|
<td>{{ row.open_in_time }}</td>
|
|
<td>{{ row.open_late }}</td>
|
|
<td>{{ row.completed_in_time }}</td>
|
|
<td>
|
|
{{ row.completed_late }}
|
|
</td>
|
|
<td>
|
|
{{ row.not_completed }}
|
|
</td>
|
|
<td>
|
|
{% if row.average_assignment_to_closure_days != None %}
|
|
{{ row.average_assignment_to_closure_days|floatformat }}
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% if forloop.last %}</tfoot>{% endif %}
|
|
{% endfor %}
|
|
</table>
|
|
{% elif stats_type == "results" %}
|
|
<h2>
|
|
Results of completed reviews
|
|
</h2>
|
|
<table class="review-stats table table-sm table-striped tablesorter">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col" data-sort="{{ level }}">
|
|
{% if level == "team" %}
|
|
Team
|
|
{% elif level == "reviewer" %}
|
|
Reviewer
|
|
{% endif %}
|
|
</th>
|
|
{% for r in results %}
|
|
<th scope="col" data-sort="{{ r.name|slugify }}-num">
|
|
{{ r.name }}
|
|
</th>
|
|
{% endfor %}
|
|
</tr>
|
|
</thead>
|
|
{% for row in data %}
|
|
{% if forloop.first %}<tbody>{% endif %}
|
|
{% if forloop.last %}</tbody><tfoot>{% endif %}
|
|
<tr>
|
|
<td>
|
|
{{ row.obj }}
|
|
</td>
|
|
{% for c in row.result_list %}
|
|
<td>
|
|
{{ c }}
|
|
</td>
|
|
{% endfor %}
|
|
</tr>
|
|
{% if forloop.last %}</tfoot>{% endif %}
|
|
{% endfor %}
|
|
</table>
|
|
{% elif stats_type == "states" %}
|
|
<h2>
|
|
Specific request states
|
|
</h2>
|
|
<table class="review-stats table table-sm table-striped tablesorter">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col" data-sort="{{ level }}">
|
|
{% if level == "team" %}
|
|
Team
|
|
{% elif level == "reviewer" %}
|
|
Reviewer
|
|
{% endif %}
|
|
</th>
|
|
{% for s in states %}
|
|
<th scope="col" data-sort="{{ s.name|slugify }}-num">
|
|
{{ s.name }}
|
|
</th>
|
|
{% endfor %}
|
|
</tr>
|
|
</thead>
|
|
{% for row in data %}
|
|
{% if forloop.first %}<tbody>{% endif %}
|
|
{% if forloop.last %}</tbody><tfoot>{% endif %}
|
|
<tr>
|
|
<td>
|
|
{{ row.obj }}
|
|
</td>
|
|
{% for c in row.state_list %}
|
|
<td>
|
|
{{ c }}
|
|
</td>
|
|
{% endfor %}
|
|
</tr>
|
|
{% if forloop.last %}</tfoot>{% endif %}
|
|
{% endfor %}
|
|
</table>
|
|
{% elif stats_type == "time" and selected_teams %}
|
|
<h2>
|
|
Counts per month
|
|
</h2>
|
|
<div id="stats-time-graph">
|
|
</div>
|
|
<script>
|
|
var timeSeriesData = {{ data|safe }};
|
|
var timeSeriesOptions = {
|
|
xaxis: {
|
|
mode: "time",
|
|
timeBase: "milliseconds",
|
|
tickLength: 0
|
|
},
|
|
yaxis: {
|
|
tickDecimals: {% if selected_completion_type == "average_assignment_to_closure_days" %}null{% else %}0{% endif %}
|
|
},
|
|
series: {
|
|
stack: true,
|
|
bars: {
|
|
show: true,
|
|
align: "center",
|
|
lineWidth: 1,
|
|
fill: 0.6
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
{% endif %}
|
|
{% if stats_type != "time" %}
|
|
<p class="text-muted text-end">
|
|
Note:
|
|
{% if level == "team" %}
|
|
teams
|
|
{% elif level == "reviewer" %}
|
|
reviewers
|
|
{% endif %}
|
|
with no requests in the period are omitted.
|
|
</p>
|
|
{% endif %}
|
|
{% if level == "team" and stats_type != "time" %}
|
|
<h2>
|
|
Statistics for Individual Reviewers
|
|
</h2>
|
|
<div class="row row-cols-1 row-cols-md-4">
|
|
{% for t in teams %}
|
|
<a
|
|
|
|
|
|
href="{{ t.reviewer_stats_url }}">
|
|
{{ t.name }}
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|
|
{% block js %}
|
|
<script src="{% static "ietf/js/list.js" %}">
|
|
</script>
|
|
<script src="{% static 'ietf/js/datepicker.js' %}">
|
|
</script>
|
|
{% if stats_type == "time" %}
|
|
<script src="{% static 'ietf/js/highcharts.js' %}"></script>
|
|
<script>
|
|
$(document)
|
|
.ready(function () {
|
|
if (window.timeSeriesData) {
|
|
series = window.timeSeriesData.map(({label, data, color}) => ({
|
|
name: label,
|
|
data: data,
|
|
color: color,
|
|
type: "column"
|
|
}));
|
|
Highcharts.chart("stats-time-graph", {
|
|
xAxis: {
|
|
type: 'datetime',
|
|
},
|
|
yAxis: {
|
|
title: undefined,
|
|
},
|
|
credits: {
|
|
enabled: false,
|
|
},
|
|
title: {
|
|
text: undefined,
|
|
},
|
|
plotOptions: {
|
|
column: {
|
|
stacking: 'normal',
|
|
}
|
|
},
|
|
series: series
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %} |