diff --git a/ietf/release/tests.py b/ietf/release/tests.py index e4981ecee..5e03351f5 100644 --- a/ietf/release/tests.py +++ b/ietf/release/tests.py @@ -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 django.urls import reverse @@ -32,4 +36,17 @@ class ReleasePagesTest(TestCase): r = self.client.get(url) 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) diff --git a/ietf/release/urls.py b/ietf/release/urls.py index d897eca35..d33fcbb73 100644 --- a/ietf/release/urls.py +++ b/ietf/release/urls.py @@ -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 ietf.release import views @@ -7,6 +11,7 @@ urlpatterns = [ url(r'^$', views.release), url(r'^(?P[0-9.]+.*)/$', views.release), 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')), ] diff --git a/ietf/release/views.py b/ietf/release/views.py index ec6c32bd4..6ad0a4763 100644 --- a/ietf/release/views.py +++ b/ietf/release/views.py @@ -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 re import json import datetime import gzip +from tzparse import tzparse +from calendar import timegm from django.shortcuts import render from django.conf import settings +from django.core.cache import cache from django.http import HttpResponse from django.utils.html import escape +from django.utils.safestring import mark_safe import changelog import debug # pyflakes:ignore @@ -16,6 +24,8 @@ import debug # pyflakes:ignore import time 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): # changeset links text = re.sub(r'\[(\d+)\]', r'[\1]', text) @@ -24,11 +34,34 @@ def trac_links(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): entries = {} - if os.path.exists(settings.CHANGELOG_PATH): - log_entries = changelog.parse(settings.CHANGELOG_PATH) - else: + log_entries = get_changelog_entries() + if not log_entries: return HttpResponse("Error: changelog file %s not found" % settings.CHANGELOG_PATH) next = None 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)) coverage = {} - 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) - 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 + coverage_data = get_coverage_data() + 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', { @@ -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)), + }) diff --git a/ietf/templates/release/release.html b/ietf/templates/release/release.html index 9e1aa659f..ed1f1688c 100644 --- a/ietf/templates/release/release.html +++ b/ietf/templates/release/release.html @@ -19,6 +19,7 @@ Version {{ entry.version }}
Released {{ entry.date }} +