Skip to content

Commit e6c4187

Browse files
committed
Added charts for test coverage and release frequency.
- Legacy-Id: 13232
1 parent d6b9392 commit e6c4187

5 files changed

Lines changed: 248 additions & 16 deletions

File tree

ietf/release/tests.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Copyright The IETF Trust 2016, All Rights Reserved
2+
# -*- coding: utf-8 -*-
3+
from __future__ import unicode_literals, print_function
4+
15
from pyquery import PyQuery
26

37
from django.urls import reverse
@@ -32,4 +36,17 @@ def test_todo(self):
3236
r = self.client.get(url)
3337
self.assertEqual(r.status_code, 200)
3438

39+
def test_stats(self):
40+
url = reverse('ietf.release.views.stats')
41+
42+
r = self.client.get(url)
43+
q = PyQuery(r.content)
44+
# grab the script element text, split off the json data
45+
s = q('#coverage-data').text()
46+
self.assertIn("type: 'line',", s)
47+
self.assertIn('"data": [[1426018457000, ', s)
48+
49+
s = q('#frequency-data').text()
50+
self.assertIn("type: 'column',", s)
51+
self.assertIn('"data": [[2007, 7], ', s)
3552

ietf/release/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Copyright The IETF Trust 2016, All Rights Reserved
2+
# -*- coding: utf-8 -*-
3+
from __future__ import unicode_literals, print_function
4+
15
from django.views.generic import TemplateView
26

37
from ietf.release import views
@@ -7,6 +11,7 @@
711
url(r'^$', views.release),
812
url(r'^(?P<version>[0-9.]+.*)/$', views.release),
913
url(r'^about/?$', TemplateView.as_view(template_name='release/about.html')),
14+
url(r'^stats/?$', views.stats),
1015
url(r'^todo/?$', TemplateView.as_view(template_name='release/todo.html')),
1116
]
1217

ietf/release/views.py

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
# Copyright The IETF Trust 2016, All Rights Reserved
2+
# -*- coding: utf-8 -*-
3+
from __future__ import unicode_literals, print_function
4+
15
import os
26
import re
37
import json
48
import datetime
59
import gzip
10+
from tzparse import tzparse
11+
from calendar import timegm
612

713
from django.shortcuts import render
814
from django.conf import settings
15+
from django.core.cache import cache
916
from django.http import HttpResponse
1017
from django.utils.html import escape
18+
from django.utils.safestring import mark_safe
1119

1220
import changelog
1321
import debug # pyflakes:ignore
@@ -16,6 +24,8 @@
1624
import time
1725
time.strptime('1984', '%Y') # we do this to force lib loading, instead of it happening lazily when changelog calls tzparse later
1826

27+
import ietf
28+
1929
def trac_links(text):
2030
# changeset links
2131
text = re.sub(r'\[(\d+)\]', r'<a href="https://wiki.tools.ietf.org/tools/ietfdb/changeset/\1">[\1]</a>', text)
@@ -24,11 +34,34 @@ def trac_links(text):
2434
return text
2535

2636

37+
def get_coverage_data():
38+
key = 'ietf:release:get_coverage_data:%s' % ietf.__version__
39+
coverage_data = cache.get(key)
40+
if not coverage_data:
41+
coverage_data = {}
42+
if os.path.exists(settings.TEST_COVERAGE_MASTER_FILE):
43+
if settings.TEST_COVERAGE_MASTER_FILE.endswith(".gz"):
44+
with gzip.open(settings.TEST_COVERAGE_MASTER_FILE, "rb") as file:
45+
coverage_data = json.load(file)
46+
else:
47+
with open(settings.TEST_COVERAGE_MASTER_FILE) as file:
48+
coverage_data = json.load(file)
49+
cache.set(key, coverage_data, 60*60*24)
50+
return coverage_data
51+
52+
def get_changelog_entries():
53+
key = 'ietf:release:get_changelog_entries:%s' % ietf.__version__
54+
log_entries = cache.get(key)
55+
if not log_entries:
56+
if os.path.exists(settings.CHANGELOG_PATH):
57+
log_entries = changelog.parse(settings.CHANGELOG_PATH)
58+
cache.set(key, log_entries, 60*60*24)
59+
return log_entries
60+
2761
def release(request, version=None):
2862
entries = {}
29-
if os.path.exists(settings.CHANGELOG_PATH):
30-
log_entries = changelog.parse(settings.CHANGELOG_PATH)
31-
else:
63+
log_entries = get_changelog_entries()
64+
if not log_entries:
3265
return HttpResponse("Error: changelog file %s not found" % settings.CHANGELOG_PATH)
3366
next = None
3467
for entry in log_entries:
@@ -48,18 +81,12 @@ def release(request, version=None):
4881
code_coverage_time = datetime.datetime.fromtimestamp(os.path.getmtime(settings.TEST_CODE_COVERAGE_REPORT_FILE))
4982

5083
coverage = {}
51-
if os.path.exists(settings.TEST_COVERAGE_MASTER_FILE):
52-
if settings.TEST_COVERAGE_MASTER_FILE.endswith(".gz"):
53-
with gzip.open(settings.TEST_COVERAGE_MASTER_FILE, "rb") as file:
54-
coverage_data = json.load(file)
55-
else:
56-
with open(settings.TEST_COVERAGE_MASTER_FILE) as file:
57-
coverage_data = json.load(file)
58-
if version in coverage_data:
59-
coverage = coverage_data[version]
60-
for key in coverage:
61-
if "coverage" in coverage[key]:
62-
coverage[key]["percentage"] = coverage[key]["coverage"] * 100
84+
coverage_data = get_coverage_data()
85+
if version in coverage_data:
86+
coverage = coverage_data[version]
87+
for key in coverage:
88+
if "coverage" in coverage[key]:
89+
coverage[key]["percentage"] = coverage[key]["coverage"] * 100
6390

6491
return render(request, 'release/release.html',
6592
{
@@ -72,4 +99,51 @@ def release(request, version=None):
7299
} )
73100

74101

75-
102+
def stats(request):
103+
104+
coverage_chart_data = []
105+
frequency_chart_data = []
106+
107+
coverage_data = get_coverage_data()
108+
coverage_series_data = {}
109+
for version in coverage_data:
110+
if 'time' in coverage_data[version]:
111+
t = coverage_data[version]['time']
112+
secs = timegm(tzparse(t, "%Y-%m-%dT%H:%M:%SZ").timetuple()) * 1000
113+
for coverage_type in coverage_data[version]:
114+
if 'coverage' in coverage_data[version][coverage_type]:
115+
cov = coverage_data[version][coverage_type]['coverage']
116+
if not coverage_type in coverage_series_data:
117+
coverage_series_data[coverage_type] = []
118+
coverage_series_data[coverage_type].append([secs, cov])
119+
120+
for coverage_type in coverage_series_data:
121+
coverage_series_data[coverage_type].sort()
122+
# skip some early values
123+
coverage_series_data[coverage_type] = coverage_series_data[coverage_type][2:]
124+
coverage_chart_data.append({
125+
'data': coverage_series_data[coverage_type],
126+
'name': coverage_type,
127+
})
128+
129+
log_entries = get_changelog_entries()
130+
frequency = {}
131+
frequency_series_data = []
132+
for entry in log_entries:
133+
year = entry.time.year
134+
if not year in frequency:
135+
frequency[year] = 0
136+
frequency[year] += 1
137+
for year in frequency:
138+
frequency_series_data.append([year, frequency[year]])
139+
frequency_series_data.sort()
140+
frequency_chart_data.append({
141+
'data': frequency_series_data,
142+
'name': 'Releases',
143+
})
144+
145+
return render(request, 'release/stats.html',
146+
{
147+
'coverage_chart_data': mark_safe(json.dumps(coverage_chart_data)),
148+
'frequency_chart_data': mark_safe(json.dumps(frequency_chart_data)),
149+
})

ietf/templates/release/release.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ <h1>
1919
<a href="https://trac.tools.ietf.org/tools/ietfdb/browser/tags/{{entry.version}}">
2020
Version {{ entry.version }}</a>
2121
<br><small>Released {{ entry.date }}</small>
22+
<div class="pull-right"><a href="{% url "ietf.release.views.stats" %}" class="icon-link">&nbsp;<span class="small fa fa-bar-chart">&nbsp;</span></a></div>
2223
</h1>
2324

2425
<ul class="pager">

ietf/templates/release/stats.html

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
{# Copyright The IETF Trust 2017, All Rights Reserved #}
2+
{% extends "base.html" %}
3+
4+
{% load origin %}
5+
6+
{% load ietf_filters staticfiles bootstrap3 %}
7+
8+
{% block title %}Release Statistics{% endblock %}
9+
10+
{% block pagehead %}
11+
{% endblock %}
12+
13+
{% block content %}
14+
{% origin %}
15+
16+
<h1>Release Statistics</h1>
17+
18+
<!-- ------------------------------------------------------------------------ -->
19+
20+
<div id="coverage-chart">
21+
</div>
22+
23+
<script id="coverage-data">
24+
var coverageChartConf = {
25+
chart: {
26+
type: 'line',
27+
},
28+
credits: {
29+
enabled: false
30+
},
31+
exporting: {
32+
fallbackToExportServer: false
33+
},
34+
legend: {
35+
align: "right",
36+
verticalAlign: "middle",
37+
layout: "vertical",
38+
enabled: true
39+
},
40+
plotOptions: {
41+
line: {
42+
marker: {
43+
enabled: false
44+
},
45+
animation: false
46+
}
47+
},
48+
title: {
49+
text: 'Test coverage'
50+
},
51+
xAxis: {
52+
type: 'datetime',
53+
title: {
54+
text: 'Release date'
55+
},
56+
},
57+
units: [
58+
[ 'day', [1]],
59+
[ 'week', [1]],
60+
[ 'month', [1, 3, 6]],
61+
[ 'year', null ]
62+
],
63+
yAxis: {
64+
min: 0,
65+
title: {
66+
text: 'Test coverage'
67+
},
68+
labels: {
69+
formatter: function() {
70+
return this.value*100+"%";
71+
}
72+
}
73+
},
74+
series: {{ coverage_chart_data }}
75+
};
76+
</script>
77+
78+
<div id="frequency-chart">
79+
</div>
80+
81+
<script id="frequency-data">
82+
var frequencyChartConf = {
83+
chart: {
84+
type: 'column',
85+
},
86+
credits: {
87+
enabled: false
88+
},
89+
exporting: {
90+
fallbackToExportServer: false
91+
},
92+
legend: {
93+
align: "right",
94+
verticalAlign: "middle",
95+
layout: "vertical",
96+
enabled: true
97+
},
98+
plotOptions: {
99+
column: {
100+
animation: false
101+
}
102+
},
103+
title: {
104+
text: 'Releases per year'
105+
},
106+
xAxis: {
107+
type: 'category',
108+
title: {
109+
text: 'Year'
110+
},
111+
},
112+
yAxis: {
113+
min: 0,
114+
title: {
115+
text: 'Number of releases'
116+
}
117+
},
118+
series: {{ frequency_chart_data }}
119+
};
120+
</script>
121+
122+
123+
{% endblock %}
124+
125+
{% block js %}
126+
<script src="{% static 'highcharts/highcharts.js' %}"></script>
127+
<script src="{% static 'highcharts/modules/exporting.js' %}"></script>
128+
<script src="{% static 'highcharts/modules/offline-exporting.js' %}"></script>
129+
<script>
130+
$(document).ready(function () {
131+
Highcharts.chart('coverage-chart', window.coverageChartConf);
132+
Highcharts.chart('frequency-chart', window.frequencyChartConf);
133+
});
134+
</script>
135+
{% endblock %}

0 commit comments

Comments
 (0)