--- a/candolint/handlers.py Mon Oct 16 20:37:16 2017 +0800
+++ b/candolint/handlers.py Mon Oct 16 23:21:11 2017 +0800
from __future__ import absolute_import, division
+from collections import OrderedDict
from difflib import unified_diff
+ def get_files_and_codes(self, check, lines):
+ if not check.success or not (check.errors or check.warnings):
+ files = {'errors': OrderedDict(), 'warnings': OrderedDict()}
+ codes = {'errors': {}, 'warnings': {}}
+ for number, line in enumerate(lines, 1):
+ if line.get('cls') not in ('error', 'warning'):
+ kind = line['cls'] + 's'
+ for key, data in (('filename', files), ('code', codes)):
+ if value not in data[kind]:
+ data[kind][value] = {'count': 0, 'line': number}
+ data[kind][value]['count'] += 1
+ {'filename': k, 'count': v['count'], 'line': v['line']}
+ for k, v in files[kind].items()
+ {'code': k, 'count': v['count'], 'line': v['line']}
+ for k, v in sorted(codes[kind].items(), key=lambda i: (i[1], i[0]), reverse=True)
def get_html(self, project, check, lines):
adapter = project.get_adapter()
line['link'] = adapter.get_line_url(check.change, line)
reference = self.get_reference_check(check)
chart = self.get_chart_data(check)
- self.render('check.html', project=project, check=check, lines=lines, adapter=adapter, reference=reference, chart=chart)
+ files, codes = self.get_files_and_codes(check, lines)
+ self.render('check.html', project=project, check=check, lines=lines, adapter=adapter, reference=reference, chart=chart, files=files, codes=codes)
class CompareHandler(BaseHandler):
--- a/static/candolint.css Mon Oct 16 20:37:16 2017 +0800
+++ b/static/candolint.css Mon Oct 16 23:21:11 2017 +0800
--- a/static/candolint.js Mon Oct 16 20:37:16 2017 +0800
+++ b/static/candolint.js Mon Oct 16 23:21:11 2017 +0800
+ if ($('#check-files-data, #check-codes-data').length === 0) { return; }
+ if ($('#check-files-pie, #check-codes-pie').length === 0) { return; }
+ var $tooltip = $('.pie-tooltip');
+ .value(function(d) { return d.count; });
+ var render = function(title, svg, $data, formatTooltip) {
+ if ($data.length === 0) { return; }
+ var data = JSON.parse($data.text());
+ var width = +svg.attr('width');
+ var height = +svg.attr('height');
+ var radius = Math.min(width, height) / 2;
+ var thickness = radius / 3.5;
+ var g = svg.append('g')
+ .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
+ .attr('text-anchor', 'middle')
+ .attr('dominant-baseline', 'middle')
+ var outerArc = d3.arc()
+ .innerRadius(radius - thickness);
+ var innerArc = d3.arc()
+ .outerRadius(radius - thickness)
+ .innerRadius(radius - thickness * 2);
+ if (data.errors.length === 0) {
+ .innerRadius(radius - thickness * 2);
+ {name: 'warnings', color: '#faa732', arc: innerArc},
+ {name: 'errors', color: '#da314b', arc: outerArc}
+ $.each(config, function(i, series) {
+ .attr('fill', series.color)
+ .attr('stroke-width', 2)
+ .attr('stroke', 'white');
+ .data(pie(data[series.name]))
+ .attr('href', function(d) { return '#l' + d.data.line; })
+ .on('mousemove', function(d) {
+ $tooltip.removeClass('uk-hidden').css({
+ left: d3.event.layerX + 10,
+ top: d3.event.layerY + 10
+ .on('mouseover', function(d) {
+ $tooltip.text(formatTooltip(d));
+ .on('mouseout', function(d) {
+ $tooltip.addClass('uk-hidden');
+ render('files', d3.select('#check-files-pie'), $('#check-files-data'), function(d) {
+ return d.data.filename + ': ' + d.data.count;
+ render('codes', d3.select('#check-codes-pie'), $('#check-codes-data'), function(d) {
+ return d.data.code + ': ' + d.data.count;
--- a/templates/check.html Mon Oct 16 20:37:16 2017 +0800
+++ b/templates/check.html Mon Oct 16 23:21:11 2017 +0800
(<a href="{{ project.get_url() }}/{{ check.ordinal }}/compare/{{ reference.ordinal }}">compare</a>).
- <div class="uk-position-relative uk-margin-top chart-container">
- <svg id="check-chart"></svg>
- <table class="uk-display-inline-block uk-position-absolute uk-position-z-index uk-hidden chart-tooltip">
- <div class="uk-display-inline-block legend-dot" data-dot="warnings"></div>
- <td data-point="warnings">0</td>
- <div class="uk-display-inline-block legend-dot" data-dot="errors"></div>
- <td data-point="errors">0</td>
- <div class="uk-display-inline-block legend-dot" data-dot="duration"></div>
- <td data-point="duration">0</td>
+ {% if chart or files or codes %}
+ <div class="uk-flex uk-flex-middle uk-margin-top">
+ <div class="uk-position-relative uk-flex-item-auto chart-container">
+ <svg id="check-chart"></svg>
+ <table class="uk-display-inline-block uk-position-absolute uk-position-z-index uk-text-nowrap uk-hidden chart-tooltip">
+ <div class="uk-display-inline-block legend-dot" data-dot="warnings"></div>
+ <td data-point="warnings">0</td>
+ <div class="uk-display-inline-block legend-dot" data-dot="errors"></div>
+ <td data-point="errors">0</td>
+ <div class="uk-display-inline-block legend-dot" data-dot="duration"></div>
+ <td data-point="duration">0</td>
+ <script type="application/json" id="check-chart-data">{% raw json_encode(chart) %}</script>
+ {% if files or codes %}
+ <div class="uk-position-relative uk-flex-item-none uk-margin-left uk-margin-right">
+ {% if files.get('errors') or files.get('warnings') %}
+ <svg id="check-files-pie" width="120" height="120"></svg>
+ <script type="application/json" id="check-files-data">{% raw json_encode(files) %}</script>
+ {% if codes.get('errors') or codes.get('warnings') %}
+ <svg id="check-codes-pie" width="120" height="120"></svg>
+ <script type="application/json" id="check-codes-data">{% raw json_encode(codes) %}</script>
+ <div class="uk-display-inline-block uk-position-absolute uk-position-z-index uk-text-nowrap uk-hidden pie-tooltip"></div>
- <script type="application/json" id="check-chart-data">{% raw json_encode(chart) %}</script>
{% include ui/check-log.html %}
--- a/tests/test_viewer.py Mon Oct 16 20:37:16 2017 +0800
+++ b/tests/test_viewer.py Mon Oct 16 23:21:11 2017 +0800
'text': 'test.py:20:80: E501 line too long (82 > 79 characters)',
assert ('<time datetime="2016-07-19T22:23+0800"'
' title="2016-07-19 22:23 +0800">'
'2016-07-19 22:23 +0800</time>') in response.body
+ assert 'id="check-files-data"' in response.body
+ assert 'id="check-codes-data"' in response.body
+ assert '"filename": "test.py"' in response.body
+ assert '"code": "E501"' in response.body
response = self.fetch('/example.com/alice/test-viewer/latest/raw')
assert response.code == 200