Added charts for test coverage and release frequency.
- Legacy-Id: 13232
This commit is contained in:
parent
d6b9392f72
commit
e6c41879ac
|
@ -1,3 +1,7 @@
|
||||||
|
# Copyright The IETF Trust 2016, All Rights Reserved
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals, print_function
|
||||||
|
|
||||||
from pyquery import PyQuery
|
from pyquery import PyQuery
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -32,4 +36,17 @@ class ReleasePagesTest(TestCase):
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
def test_stats(self):
|
||||||
|
url = reverse('ietf.release.views.stats')
|
||||||
|
|
||||||
|
r = self.client.get(url)
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
# grab the script element text, split off the json data
|
||||||
|
s = q('#coverage-data').text()
|
||||||
|
self.assertIn("type: 'line',", s)
|
||||||
|
self.assertIn('"data": [[1426018457000, ', s)
|
||||||
|
|
||||||
|
s = q('#frequency-data').text()
|
||||||
|
self.assertIn("type: 'column',", s)
|
||||||
|
self.assertIn('"data": [[2007, 7], ', s)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Copyright The IETF Trust 2016, All Rights Reserved
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals, print_function
|
||||||
|
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from ietf.release import views
|
from ietf.release import views
|
||||||
|
@ -7,6 +11,7 @@ urlpatterns = [
|
||||||
url(r'^$', views.release),
|
url(r'^$', views.release),
|
||||||
url(r'^(?P<version>[0-9.]+.*)/$', views.release),
|
url(r'^(?P<version>[0-9.]+.*)/$', views.release),
|
||||||
url(r'^about/?$', TemplateView.as_view(template_name='release/about.html')),
|
url(r'^about/?$', TemplateView.as_view(template_name='release/about.html')),
|
||||||
|
url(r'^stats/?$', views.stats),
|
||||||
url(r'^todo/?$', TemplateView.as_view(template_name='release/todo.html')),
|
url(r'^todo/?$', TemplateView.as_view(template_name='release/todo.html')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
|
# Copyright The IETF Trust 2016, All Rights Reserved
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals, print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
import datetime
|
import datetime
|
||||||
import gzip
|
import gzip
|
||||||
|
from tzparse import tzparse
|
||||||
|
from calendar import timegm
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
import changelog
|
import changelog
|
||||||
import debug # pyflakes:ignore
|
import debug # pyflakes:ignore
|
||||||
|
@ -16,6 +24,8 @@ import debug # pyflakes:ignore
|
||||||
import time
|
import time
|
||||||
time.strptime('1984', '%Y') # we do this to force lib loading, instead of it happening lazily when changelog calls tzparse later
|
time.strptime('1984', '%Y') # we do this to force lib loading, instead of it happening lazily when changelog calls tzparse later
|
||||||
|
|
||||||
|
import ietf
|
||||||
|
|
||||||
def trac_links(text):
|
def trac_links(text):
|
||||||
# changeset links
|
# changeset links
|
||||||
text = re.sub(r'\[(\d+)\]', r'<a href="https://wiki.tools.ietf.org/tools/ietfdb/changeset/\1">[\1]</a>', text)
|
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):
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def get_coverage_data():
|
||||||
|
key = 'ietf:release:get_coverage_data:%s' % ietf.__version__
|
||||||
|
coverage_data = cache.get(key)
|
||||||
|
if not coverage_data:
|
||||||
|
coverage_data = {}
|
||||||
|
if os.path.exists(settings.TEST_COVERAGE_MASTER_FILE):
|
||||||
|
if settings.TEST_COVERAGE_MASTER_FILE.endswith(".gz"):
|
||||||
|
with gzip.open(settings.TEST_COVERAGE_MASTER_FILE, "rb") as file:
|
||||||
|
coverage_data = json.load(file)
|
||||||
|
else:
|
||||||
|
with open(settings.TEST_COVERAGE_MASTER_FILE) as file:
|
||||||
|
coverage_data = json.load(file)
|
||||||
|
cache.set(key, coverage_data, 60*60*24)
|
||||||
|
return coverage_data
|
||||||
|
|
||||||
|
def get_changelog_entries():
|
||||||
|
key = 'ietf:release:get_changelog_entries:%s' % ietf.__version__
|
||||||
|
log_entries = cache.get(key)
|
||||||
|
if not log_entries:
|
||||||
|
if os.path.exists(settings.CHANGELOG_PATH):
|
||||||
|
log_entries = changelog.parse(settings.CHANGELOG_PATH)
|
||||||
|
cache.set(key, log_entries, 60*60*24)
|
||||||
|
return log_entries
|
||||||
|
|
||||||
def release(request, version=None):
|
def release(request, version=None):
|
||||||
entries = {}
|
entries = {}
|
||||||
if os.path.exists(settings.CHANGELOG_PATH):
|
log_entries = get_changelog_entries()
|
||||||
log_entries = changelog.parse(settings.CHANGELOG_PATH)
|
if not log_entries:
|
||||||
else:
|
|
||||||
return HttpResponse("Error: changelog file %s not found" % settings.CHANGELOG_PATH)
|
return HttpResponse("Error: changelog file %s not found" % settings.CHANGELOG_PATH)
|
||||||
next = None
|
next = None
|
||||||
for entry in log_entries:
|
for entry in log_entries:
|
||||||
|
@ -48,18 +81,12 @@ def release(request, version=None):
|
||||||
code_coverage_time = datetime.datetime.fromtimestamp(os.path.getmtime(settings.TEST_CODE_COVERAGE_REPORT_FILE))
|
code_coverage_time = datetime.datetime.fromtimestamp(os.path.getmtime(settings.TEST_CODE_COVERAGE_REPORT_FILE))
|
||||||
|
|
||||||
coverage = {}
|
coverage = {}
|
||||||
if os.path.exists(settings.TEST_COVERAGE_MASTER_FILE):
|
coverage_data = get_coverage_data()
|
||||||
if settings.TEST_COVERAGE_MASTER_FILE.endswith(".gz"):
|
if version in coverage_data:
|
||||||
with gzip.open(settings.TEST_COVERAGE_MASTER_FILE, "rb") as file:
|
coverage = coverage_data[version]
|
||||||
coverage_data = json.load(file)
|
for key in coverage:
|
||||||
else:
|
if "coverage" in coverage[key]:
|
||||||
with open(settings.TEST_COVERAGE_MASTER_FILE) as file:
|
coverage[key]["percentage"] = coverage[key]["coverage"] * 100
|
||||||
coverage_data = json.load(file)
|
|
||||||
if version in coverage_data:
|
|
||||||
coverage = coverage_data[version]
|
|
||||||
for key in coverage:
|
|
||||||
if "coverage" in coverage[key]:
|
|
||||||
coverage[key]["percentage"] = coverage[key]["coverage"] * 100
|
|
||||||
|
|
||||||
return render(request, 'release/release.html',
|
return render(request, 'release/release.html',
|
||||||
{
|
{
|
||||||
|
@ -72,4 +99,51 @@ def release(request, version=None):
|
||||||
} )
|
} )
|
||||||
|
|
||||||
|
|
||||||
|
def stats(request):
|
||||||
|
|
||||||
|
coverage_chart_data = []
|
||||||
|
frequency_chart_data = []
|
||||||
|
|
||||||
|
coverage_data = get_coverage_data()
|
||||||
|
coverage_series_data = {}
|
||||||
|
for version in coverage_data:
|
||||||
|
if 'time' in coverage_data[version]:
|
||||||
|
t = coverage_data[version]['time']
|
||||||
|
secs = timegm(tzparse(t, "%Y-%m-%dT%H:%M:%SZ").timetuple()) * 1000
|
||||||
|
for coverage_type in coverage_data[version]:
|
||||||
|
if 'coverage' in coverage_data[version][coverage_type]:
|
||||||
|
cov = coverage_data[version][coverage_type]['coverage']
|
||||||
|
if not coverage_type in coverage_series_data:
|
||||||
|
coverage_series_data[coverage_type] = []
|
||||||
|
coverage_series_data[coverage_type].append([secs, cov])
|
||||||
|
|
||||||
|
for coverage_type in coverage_series_data:
|
||||||
|
coverage_series_data[coverage_type].sort()
|
||||||
|
# skip some early values
|
||||||
|
coverage_series_data[coverage_type] = coverage_series_data[coverage_type][2:]
|
||||||
|
coverage_chart_data.append({
|
||||||
|
'data': coverage_series_data[coverage_type],
|
||||||
|
'name': coverage_type,
|
||||||
|
})
|
||||||
|
|
||||||
|
log_entries = get_changelog_entries()
|
||||||
|
frequency = {}
|
||||||
|
frequency_series_data = []
|
||||||
|
for entry in log_entries:
|
||||||
|
year = entry.time.year
|
||||||
|
if not year in frequency:
|
||||||
|
frequency[year] = 0
|
||||||
|
frequency[year] += 1
|
||||||
|
for year in frequency:
|
||||||
|
frequency_series_data.append([year, frequency[year]])
|
||||||
|
frequency_series_data.sort()
|
||||||
|
frequency_chart_data.append({
|
||||||
|
'data': frequency_series_data,
|
||||||
|
'name': 'Releases',
|
||||||
|
})
|
||||||
|
|
||||||
|
return render(request, 'release/stats.html',
|
||||||
|
{
|
||||||
|
'coverage_chart_data': mark_safe(json.dumps(coverage_chart_data)),
|
||||||
|
'frequency_chart_data': mark_safe(json.dumps(frequency_chart_data)),
|
||||||
|
})
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
<a href="https://trac.tools.ietf.org/tools/ietfdb/browser/tags/{{entry.version}}">
|
<a href="https://trac.tools.ietf.org/tools/ietfdb/browser/tags/{{entry.version}}">
|
||||||
Version {{ entry.version }}</a>
|
Version {{ entry.version }}</a>
|
||||||
<br><small>Released {{ entry.date }}</small>
|
<br><small>Released {{ entry.date }}</small>
|
||||||
|
<div class="pull-right"><a href="{% url "ietf.release.views.stats" %}" class="icon-link"> <span class="small fa fa-bar-chart"> </span></a></div>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<ul class="pager">
|
<ul class="pager">
|
||||||
|
|
135
ietf/templates/release/stats.html
Normal file
135
ietf/templates/release/stats.html
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
{# Copyright The IETF Trust 2017, All Rights Reserved #}
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load origin %}
|
||||||
|
|
||||||
|
{% load ietf_filters staticfiles bootstrap3 %}
|
||||||
|
|
||||||
|
{% block title %}Release Statistics{% endblock %}
|
||||||
|
|
||||||
|
{% block pagehead %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% origin %}
|
||||||
|
|
||||||
|
<h1>Release Statistics</h1>
|
||||||
|
|
||||||
|
<!-- ------------------------------------------------------------------------ -->
|
||||||
|
|
||||||
|
<div id="coverage-chart">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script id="coverage-data">
|
||||||
|
var coverageChartConf = {
|
||||||
|
chart: {
|
||||||
|
type: 'line',
|
||||||
|
},
|
||||||
|
credits: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
exporting: {
|
||||||
|
fallbackToExportServer: false
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
align: "right",
|
||||||
|
verticalAlign: "middle",
|
||||||
|
layout: "vertical",
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
line: {
|
||||||
|
marker: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
animation: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: 'Test coverage'
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
title: {
|
||||||
|
text: 'Release date'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
units: [
|
||||||
|
[ 'day', [1]],
|
||||||
|
[ 'week', [1]],
|
||||||
|
[ 'month', [1, 3, 6]],
|
||||||
|
[ 'year', null ]
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
min: 0,
|
||||||
|
title: {
|
||||||
|
text: 'Test coverage'
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
formatter: function() {
|
||||||
|
return this.value*100+"%";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: {{ coverage_chart_data }}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="frequency-chart">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script id="frequency-data">
|
||||||
|
var frequencyChartConf = {
|
||||||
|
chart: {
|
||||||
|
type: 'column',
|
||||||
|
},
|
||||||
|
credits: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
exporting: {
|
||||||
|
fallbackToExportServer: false
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
align: "right",
|
||||||
|
verticalAlign: "middle",
|
||||||
|
layout: "vertical",
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
column: {
|
||||||
|
animation: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: 'Releases per year'
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
title: {
|
||||||
|
text: 'Year'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
min: 0,
|
||||||
|
title: {
|
||||||
|
text: 'Number of releases'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: {{ frequency_chart_data }}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script src="{% static 'highcharts/highcharts.js' %}"></script>
|
||||||
|
<script src="{% static 'highcharts/modules/exporting.js' %}"></script>
|
||||||
|
<script src="{% static 'highcharts/modules/offline-exporting.js' %}"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
Highcharts.chart('coverage-chart', window.coverageChartConf);
|
||||||
|
Highcharts.chart('frequency-chart', window.frequencyChartConf);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue