Projet

Général

Profil

0001-dataviz-load-table-charts-asynchronously-64315.patch

Valentin Deniaud, 20 avril 2022 16:19

Télécharger (13 ko)

Voir les différences:

Subject: [PATCH] dataviz: load table charts asynchronously (#64315)

 combo/apps/dataviz/models.py                  | 33 +-----------
 .../dataviz/templates/combo/chartngcell.html  | 22 +++++++-
 combo/apps/dataviz/views.py                   | 30 ++++++++---
 tests/test_dataviz.py                         | 52 +++++++------------
 4 files changed, 63 insertions(+), 74 deletions(-)
combo/apps/dataviz/models.py
36 36
from django.utils.translation import gettext
37 37
from django.utils.translation import ugettext_lazy as _
38 38
from django.utils.translation import ungettext
39
from requests.exceptions import HTTPError, RequestException
39
from requests.exceptions import RequestException
40 40

  
41 41
from combo.data.library import register_cell_class
42 42
from combo.data.models import CellBase, django_template_validator
......
308 308
            resp, not_found_code='statistic_data_not_found', invalid_code='statistic_url_invalid'
309 309
        )
310 310

  
311
    def get_cell_extra_context(self, context):
312
        ctx = super().get_cell_extra_context(context)
313
        if self.chart_type == 'table' and self.statistic and self.statistic.url:
314
            self._context = context
315
            try:
316
                chart = self.get_chart(raise_if_not_cached=not (context.get('synchronous')))
317
            except UnsupportedDataSet:
318
                ctx['table'] = '<p>%s</p>' % _('Unsupported dataset.')
319
            except MissingVariable:
320
                ctx['table'] = '<p>%s</p>' % _('Page variable not found.')
321
            except TemplateSyntaxError:
322
                ctx['table'] = '<p>%s</p>' % _('Syntax error in page variable.')
323
            except VariableDoesNotExist:
324
                ctx['table'] = '<p>%s</p>' % _('Cannot evaluate page variable.')
325
            except HTTPError as e:
326
                if e.response.status_code == 404:
327
                    ctx['table'] = '<p>%s</p>' % _('Visualization not found.')
328
            else:
329
                if not chart.raw_series:
330
                    ctx['table'] = '<p>%s</p>' % _('No data.')
331
                else:
332
                    ctx['table'] = chart.render_table(
333
                        transpose=bool(chart.axis_count == 2),
334
                        total=getattr(chart, 'compute_sum', True),
335
                    )
336
                    ctx['table'] = ctx['table'].replace('<table>', '<table class="main">')
337
        return ctx
338

  
339 311
    def get_statistic_data(self, raise_if_not_cached=False, invalidate_cache=False):
340 312
        return requests.get(
341 313
            self.statistic.url,
......
485 457

  
486 458
    @cached_property
487 459
    def request_context(self):
488
        if hasattr(self, '_context'):
489
            return Context(self._context)
490

  
491 460
        if not hasattr(self, '_request'):
492 461
            raise MissingRequest
493 462

  
combo/apps/dataviz/templates/combo/chartngcell.html
1 1
{% load i18n %}
2 2
{% if cell.title %}<h2>{{cell.title}}</h2>{% endif %}
3 3
{% if cell.chart_type == "table" %}
4
{{table|safe}}
4
<div id="chart-{{cell.id}}"></div>
5
<script>
6
$(function() {
7
  var extra_context = $('#chart-{{cell.id}}').parents('.cell').data('extra-context');
8
  var chart_filters_form = $('#chart-filters');
9
  $(window).on('combo:refresh-graphs', function() {
10
    qs = [];
11
    if(chart_filters_form)
12
        qs.push(chart_filters_form.serialize());
13
    if(extra_context)
14
        qs.push('ctx=' + extra_context);
15
    $.ajax({
16
      url : "{% url 'combo-dataviz-graph' cell=cell.id %}?" + qs.join('&'),
17
      type: 'GET',
18
      success: function(data) {
19
          $('#chart-{{cell.id}}').html(data);
20
      }
21
    });
22
  }).trigger('combo:refresh-graphs');
23
});
24
</script>
5 25
{% else %}
6 26
<div style="min-height: {{cell.height}}px">
7 27
<embed id="chart-{{cell.id}}" type="image/svg+xml" style="width: 100%"/>
combo/apps/dataviz/views.py
54 54
    def get(self, request, *args, **kwargs):
55 55
        form = ChartNgPartialForm(request.GET, instance=self.cell)
56 56
        if not form.is_valid():
57
            return self.svg_error(_('Wrong parameters.'))
57
            return self.error(_('Wrong parameters.'))
58 58

  
59 59
        request.extra_context = {}
60 60
        if request.GET.get('ctx'):
......
70 70
                height=int(request.GET['height']) if request.GET.get('height') else int(self.cell.height),
71 71
            )
72 72
        except UnsupportedDataSet:
73
            return self.svg_error(_('Unsupported dataset.'))
73
            return self.error(_('Unsupported dataset.'))
74 74
        except MissingVariable:
75
            return self.svg_error(_('Page variable not found.'))
75
            return self.error(_('Page variable not found.'))
76 76
        except TemplateSyntaxError:
77
            return self.svg_error(_('Syntax error in page variable.'))
77
            return self.error(_('Syntax error in page variable.'))
78 78
        except VariableDoesNotExist:
79
            return self.svg_error(_('Cannot evaluate page variable.'))
79
            return self.error(_('Cannot evaluate page variable.'))
80 80
        except HTTPError as e:
81 81
            if e.response.status_code == 404:
82
                return self.svg_error(_('Visualization not found.'))
82
                return self.error(_('Visualization not found.'))
83 83
            else:
84
                return self.svg_error(_('Unknown HTTP error: %s' % e))
84
                return self.error(_('Unknown HTTP error: %s' % e))
85

  
86
        if self.cell.chart_type == 'table':
87
            if not chart.raw_series:
88
                return self.error(_('No data'))
89

  
90
            rendered = chart.render_table(
91
                transpose=bool(chart.axis_count == 2),
92
                total=getattr(chart, 'compute_sum', True),
93
            )
94
            rendered = rendered.replace('<table>', '<table class="main">')
95
            return HttpResponse(rendered)
85 96

  
86 97
        return HttpResponse(chart.render(), content_type='image/svg+xml')
87 98

  
88
    def svg_error(self, error_text):
99
    def error(self, error_text):
100
        if self.cell.chart_type == 'table':
101
            return HttpResponse('<p>%s</p>' % error_text)
102

  
89 103
        context = {
90 104
            'width': self.request.GET.get('width', 200),
91 105
            'text': error_text,
tests/test_dataviz.py
1142 1142
    # table visualization
1143 1143
    cell.chart_type = 'table'
1144 1144
    cell.save()
1145
    resp = app.get('/')
1145
    resp = app.get(location, status=200)
1146 1146
    assert '<td>222</td>' in resp.text
1147 1147

  
1148 1148
    # unsupported dataset
1149 1149
    cell.statistic = Statistic.objects.get(slug='seventh')
1150 1150
    cell.save()
1151
    resp = app.get(location)  # get data in cache
1152
    resp = app.get('/')
1153
    assert 'Unsupported dataset' in resp.text
1151
    resp = app.get(location, status=200)
1152
    assert '<p>Unsupported dataset.</p>' in resp.text
1154 1153

  
1155 1154
    cell.chart_type = 'bar'
1156 1155
    cell.save()
......
1161 1160
    cell.statistic = Statistic.objects.get(slug='eighth')
1162 1161
    cell.chart_type = 'table'
1163 1162
    cell.save()
1164
    resp = app.get(location)  # get data in cache
1165
    resp = app.get('/')
1163
    resp = app.get(location, status=200)
1166 1164
    assert '<td>Less than an hour</td>' in resp.text
1167 1165
    assert '<td>1 day and 10 hours</td>' in resp.text
1168 1166
    assert '<td>2 hours</td>' in resp.text
......
1180 1178
    cell.statistic = Statistic.objects.get(slug='tenth')
1181 1179
    cell.chart_type = 'table'
1182 1180
    cell.save()
1183
    resp = app.get(location)  # get data in cache
1184
    resp = app.get('/')
1181
    resp = app.get(location, status=200)
1185 1182
    assert '<td>10.0%</td>' in resp.text
1186 1183

  
1187 1184
    cell.chart_type = 'bar'
......
1195 1192
    resp = app.get(location)
1196 1193
    assert 'not found' in resp.text
1197 1194

  
1198
    # cell with no statistic chosen
1199
    cell.chart_type = 'table'
1200
    cell.statistic = None
1201
    cell.save()
1202
    resp = app.get('/')
1203
    assert not 'cell' in resp.text
1204

  
1205 1195

  
1206 1196
@with_httmock(new_api_mock)
1207 1197
def test_chartng_cell_view_new_api(app, normal_user, new_api_statistics):
......
1218 1208
    # table visualization
1219 1209
    cell.chart_type = 'table'
1220 1210
    cell.save()
1221
    resp = app.get(location)  # populate cache
1222
    resp = app.get('/')
1211
    resp = app.get(location, status=200)
1223 1212
    assert '<td>18</td>' in resp.text
1224 1213

  
1225 1214
    # deleted visualization
......
1643 1632
    cell.save()
1644 1633
    location = '/api/dataviz/graph/%s/' % cell.id
1645 1634
    resp = app.get(location)
1646
    resp = app.get('/')
1647 1635
    assert resp.text.count('Total') == 1
1648 1636

  
1649 1637
    cell.statistic = Statistic.objects.get(slug='second')
1650 1638
    cell.save()
1651 1639
    resp = app.get(location)
1652
    resp = app.get('/')
1653 1640
    assert resp.text.count('Total') == 1
1654 1641

  
1655 1642
    cell.statistic = Statistic.objects.get(slug='third')
1656 1643
    cell.save()
1657 1644
    resp = app.get(location)
1658
    resp = app.get('/')
1659 1645
    assert '114' in resp.text
1660 1646
    assert resp.text.count('Total') == 2
1661 1647

  
1662 1648
    cell.statistic = Statistic.objects.get(slug='fourth')
1663 1649
    cell.save()
1664 1650
    resp = app.get(location)
1665
    resp = app.get('/')
1666 1651
    assert resp.text.count('Total') == 0
1667 1652

  
1668 1653
    # total of durations is not computed
1669 1654
    cell.statistic = Statistic.objects.get(slug='eighth')
1670 1655
    cell.save()
1671 1656
    resp = app.get(location)
1672
    resp = app.get('/')
1673 1657
    assert resp.text.count('Total') == 0
1674 1658

  
1675 1659

  
......
1682 1666
    cell.save()
1683 1667

  
1684 1668
    app = login(app)
1685
    resp = app.get('/')
1669
    location = '/api/dataviz/graph/%s/' % cell.id
1670
    resp = app.get(location)
1686 1671
    assert resp.text.count('Total') == 1
1687 1672

  
1688 1673
    cell.statistic = Statistic.objects.get(slug='two-series')
1689 1674
    cell.save()
1690
    resp = app.get('/')
1675
    resp = app.get(location)
1691 1676
    assert '21' in resp.text
1692 1677
    assert resp.text.count('Total') == 2
1693 1678

  
1694 1679
    cell.statistic = Statistic.objects.get(slug='no-data')
1695 1680
    cell.save()
1696
    resp = app.get('/')
1681
    resp = app.get(location)
1697 1682
    assert resp.text.count('Total') == 0
1698 1683

  
1699 1684

  
......
2009 1994

  
2010 1995

  
2011 1996
@with_httmock(new_api_mock)
2012
def test_chartng_cell_new_api_filter_params_page_variables_table(new_api_statistics, nocache):
1997
def test_chartng_cell_new_api_filter_params_page_variables_table(new_api_statistics, app, nocache):
2013 1998
    Page.objects.create(title='One', slug='index')
2014 1999
    page = Page.objects.create(
2015 2000
        title='One',
......
2026 2011
    cell.filter_params = {'service': 'chrono', 'ou': 'variable:foo'}
2027 2012
    cell.save()
2028 2013

  
2029
    cell.render({**page.extra_variables, 'synchronous': True})
2014
    location = '/api/dataviz/graph/%s/' % cell.pk
2015
    app.get(location)
2030 2016
    request = new_api_mock.call['requests'][0]
2031 2017
    assert 'service=chrono' in request.url
2032 2018
    assert 'ou=bar' in request.url
......
2035 2021
    cell.filter_params = {'service': 'chrono', 'ou': 'variable:unknown'}
2036 2022
    cell.save()
2037 2023

  
2038
    content = cell.render({'synchronous': True})
2024
    resp = app.get(location)
2039 2025
    assert len(new_api_mock.call['requests']) == 1
2040
    assert 'Page variable not found.' in content
2026
    assert 'Page variable not found.' in resp.text
2041 2027

  
2042 2028
    # variable with invalid syntax
2043 2029
    cell.filter_params = {'service': 'chrono', 'ou': 'variable:syntax_error'}
2044 2030
    cell.save()
2045 2031

  
2046
    content = cell.render({**page.extra_variables, 'synchronous': True})
2032
    resp = app.get(location)
2047 2033
    assert len(new_api_mock.call['requests']) == 1
2048
    assert 'Syntax error in page variable.' in content
2034
    assert 'Syntax error in page variable.' in resp.text
2049 2035

  
2050 2036
    # variable with missing context
2051 2037
    cell.filter_params = {'service': 'chrono', 'ou': 'variable:subslug_dependant'}
2052 2038
    cell.save()
2053 2039

  
2054
    content = cell.render({**page.extra_variables, 'synchronous': True})
2040
    resp = app.get(location)
2055 2041
    assert len(new_api_mock.call['requests']) == 1
2056
    assert 'Cannot evaluate page variable.' in content
2042
    assert 'Cannot evaluate page variable.' in resp.text
2057 2043

  
2058 2044

  
2059 2045
def test_dataviz_check_validity(nocache):
2060
-