dataviz: load table charts asynchronously (#64315)
This commit is contained in:
parent
5f918b53c0
commit
a52491ea5e
|
@ -36,7 +36,7 @@ from django.utils.functional import cached_property
|
|||
from django.utils.translation import gettext
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext
|
||||
from requests.exceptions import HTTPError, RequestException
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from combo.data.library import register_cell_class
|
||||
from combo.data.models import CellBase, django_template_validator
|
||||
|
@ -308,34 +308,6 @@ class ChartNgCell(CellBase):
|
|||
resp, not_found_code='statistic_data_not_found', invalid_code='statistic_url_invalid'
|
||||
)
|
||||
|
||||
def get_cell_extra_context(self, context):
|
||||
ctx = super().get_cell_extra_context(context)
|
||||
if self.chart_type == 'table' and self.statistic and self.statistic.url:
|
||||
self._context = context
|
||||
try:
|
||||
chart = self.get_chart(raise_if_not_cached=not (context.get('synchronous')))
|
||||
except UnsupportedDataSet:
|
||||
ctx['table'] = '<p>%s</p>' % _('Unsupported dataset.')
|
||||
except MissingVariable:
|
||||
ctx['table'] = '<p>%s</p>' % _('Page variable not found.')
|
||||
except TemplateSyntaxError:
|
||||
ctx['table'] = '<p>%s</p>' % _('Syntax error in page variable.')
|
||||
except VariableDoesNotExist:
|
||||
ctx['table'] = '<p>%s</p>' % _('Cannot evaluate page variable.')
|
||||
except HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
ctx['table'] = '<p>%s</p>' % _('Visualization not found.')
|
||||
else:
|
||||
if not chart.raw_series:
|
||||
ctx['table'] = '<p>%s</p>' % _('No data.')
|
||||
else:
|
||||
ctx['table'] = chart.render_table(
|
||||
transpose=bool(chart.axis_count == 2),
|
||||
total=getattr(chart, 'compute_sum', True),
|
||||
)
|
||||
ctx['table'] = ctx['table'].replace('<table>', '<table class="main">')
|
||||
return ctx
|
||||
|
||||
def get_statistic_data(self, raise_if_not_cached=False, invalidate_cache=False):
|
||||
return requests.get(
|
||||
self.statistic.url,
|
||||
|
@ -485,9 +457,6 @@ class ChartNgCell(CellBase):
|
|||
|
||||
@cached_property
|
||||
def request_context(self):
|
||||
if hasattr(self, '_context'):
|
||||
return Context(self._context)
|
||||
|
||||
if not hasattr(self, '_request'):
|
||||
raise MissingRequest
|
||||
|
||||
|
|
|
@ -1,7 +1,27 @@
|
|||
{% load i18n %}
|
||||
{% if cell.title %}<h2>{{cell.title}}</h2>{% endif %}
|
||||
{% if cell.chart_type == "table" %}
|
||||
{{table|safe}}
|
||||
<div id="chart-{{cell.id}}"></div>
|
||||
<script>
|
||||
$(function() {
|
||||
var extra_context = $('#chart-{{cell.id}}').parents('.cell').data('extra-context');
|
||||
var chart_filters_form = $('#chart-filters');
|
||||
$(window).on('combo:refresh-graphs', function() {
|
||||
qs = [];
|
||||
if(chart_filters_form)
|
||||
qs.push(chart_filters_form.serialize());
|
||||
if(extra_context)
|
||||
qs.push('ctx=' + extra_context);
|
||||
$.ajax({
|
||||
url : "{% url 'combo-dataviz-graph' cell=cell.id %}?" + qs.join('&'),
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
$('#chart-{{cell.id}}').html(data);
|
||||
}
|
||||
});
|
||||
}).trigger('combo:refresh-graphs');
|
||||
});
|
||||
</script>
|
||||
{% else %}
|
||||
<div style="min-height: {{cell.height}}px">
|
||||
<embed id="chart-{{cell.id}}" type="image/svg+xml" style="width: 100%"/>
|
||||
|
|
|
@ -54,7 +54,7 @@ class DatavizGraphView(DetailView):
|
|||
def get(self, request, *args, **kwargs):
|
||||
form = ChartNgPartialForm(request.GET, instance=self.cell)
|
||||
if not form.is_valid():
|
||||
return self.svg_error(_('Wrong parameters.'))
|
||||
return self.error(_('Wrong parameters.'))
|
||||
|
||||
request.extra_context = {}
|
||||
if request.GET.get('ctx'):
|
||||
|
@ -70,22 +70,36 @@ class DatavizGraphView(DetailView):
|
|||
height=int(request.GET['height']) if request.GET.get('height') else int(self.cell.height),
|
||||
)
|
||||
except UnsupportedDataSet:
|
||||
return self.svg_error(_('Unsupported dataset.'))
|
||||
return self.error(_('Unsupported dataset.'))
|
||||
except MissingVariable:
|
||||
return self.svg_error(_('Page variable not found.'))
|
||||
return self.error(_('Page variable not found.'))
|
||||
except TemplateSyntaxError:
|
||||
return self.svg_error(_('Syntax error in page variable.'))
|
||||
return self.error(_('Syntax error in page variable.'))
|
||||
except VariableDoesNotExist:
|
||||
return self.svg_error(_('Cannot evaluate page variable.'))
|
||||
return self.error(_('Cannot evaluate page variable.'))
|
||||
except HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
return self.svg_error(_('Visualization not found.'))
|
||||
return self.error(_('Visualization not found.'))
|
||||
else:
|
||||
return self.svg_error(_('Unknown HTTP error: %s' % e))
|
||||
return self.error(_('Unknown HTTP error: %s' % e))
|
||||
|
||||
if self.cell.chart_type == 'table':
|
||||
if not chart.raw_series:
|
||||
return self.error(_('No data'))
|
||||
|
||||
rendered = chart.render_table(
|
||||
transpose=bool(chart.axis_count == 2),
|
||||
total=getattr(chart, 'compute_sum', True),
|
||||
)
|
||||
rendered = rendered.replace('<table>', '<table class="main">')
|
||||
return HttpResponse(rendered)
|
||||
|
||||
return HttpResponse(chart.render(), content_type='image/svg+xml')
|
||||
|
||||
def svg_error(self, error_text):
|
||||
def error(self, error_text):
|
||||
if self.cell.chart_type == 'table':
|
||||
return HttpResponse('<p>%s</p>' % error_text)
|
||||
|
||||
context = {
|
||||
'width': self.request.GET.get('width', 200),
|
||||
'text': error_text,
|
||||
|
|
|
@ -1142,15 +1142,14 @@ def test_chartng_cell_view(app, normal_user, statistics):
|
|||
# table visualization
|
||||
cell.chart_type = 'table'
|
||||
cell.save()
|
||||
resp = app.get('/')
|
||||
resp = app.get(location, status=200)
|
||||
assert '<td>222</td>' in resp.text
|
||||
|
||||
# unsupported dataset
|
||||
cell.statistic = Statistic.objects.get(slug='seventh')
|
||||
cell.save()
|
||||
resp = app.get(location) # get data in cache
|
||||
resp = app.get('/')
|
||||
assert 'Unsupported dataset' in resp.text
|
||||
resp = app.get(location, status=200)
|
||||
assert '<p>Unsupported dataset.</p>' in resp.text
|
||||
|
||||
cell.chart_type = 'bar'
|
||||
cell.save()
|
||||
|
@ -1161,8 +1160,7 @@ def test_chartng_cell_view(app, normal_user, statistics):
|
|||
cell.statistic = Statistic.objects.get(slug='eighth')
|
||||
cell.chart_type = 'table'
|
||||
cell.save()
|
||||
resp = app.get(location) # get data in cache
|
||||
resp = app.get('/')
|
||||
resp = app.get(location, status=200)
|
||||
assert '<td>Less than an hour</td>' in resp.text
|
||||
assert '<td>1 day and 10 hours</td>' in resp.text
|
||||
assert '<td>2 hours</td>' in resp.text
|
||||
|
@ -1180,8 +1178,7 @@ def test_chartng_cell_view(app, normal_user, statistics):
|
|||
cell.statistic = Statistic.objects.get(slug='tenth')
|
||||
cell.chart_type = 'table'
|
||||
cell.save()
|
||||
resp = app.get(location) # get data in cache
|
||||
resp = app.get('/')
|
||||
resp = app.get(location, status=200)
|
||||
assert '<td>10.0%</td>' in resp.text
|
||||
|
||||
cell.chart_type = 'bar'
|
||||
|
@ -1195,13 +1192,6 @@ def test_chartng_cell_view(app, normal_user, statistics):
|
|||
resp = app.get(location)
|
||||
assert 'not found' in resp.text
|
||||
|
||||
# cell with no statistic chosen
|
||||
cell.chart_type = 'table'
|
||||
cell.statistic = None
|
||||
cell.save()
|
||||
resp = app.get('/')
|
||||
assert not 'cell' in resp.text
|
||||
|
||||
|
||||
@with_httmock(new_api_mock)
|
||||
def test_chartng_cell_view_new_api(app, normal_user, new_api_statistics):
|
||||
|
@ -1218,8 +1208,7 @@ def test_chartng_cell_view_new_api(app, normal_user, new_api_statistics):
|
|||
# table visualization
|
||||
cell.chart_type = 'table'
|
||||
cell.save()
|
||||
resp = app.get(location) # populate cache
|
||||
resp = app.get('/')
|
||||
resp = app.get(location, status=200)
|
||||
assert '<td>18</td>' in resp.text
|
||||
|
||||
# deleted visualization
|
||||
|
@ -1643,33 +1632,28 @@ def test_table_cell(app, admin_user, statistics):
|
|||
cell.save()
|
||||
location = '/api/dataviz/graph/%s/' % cell.id
|
||||
resp = app.get(location)
|
||||
resp = app.get('/')
|
||||
assert resp.text.count('Total') == 1
|
||||
|
||||
cell.statistic = Statistic.objects.get(slug='second')
|
||||
cell.save()
|
||||
resp = app.get(location)
|
||||
resp = app.get('/')
|
||||
assert resp.text.count('Total') == 1
|
||||
|
||||
cell.statistic = Statistic.objects.get(slug='third')
|
||||
cell.save()
|
||||
resp = app.get(location)
|
||||
resp = app.get('/')
|
||||
assert '114' in resp.text
|
||||
assert resp.text.count('Total') == 2
|
||||
|
||||
cell.statistic = Statistic.objects.get(slug='fourth')
|
||||
cell.save()
|
||||
resp = app.get(location)
|
||||
resp = app.get('/')
|
||||
assert resp.text.count('Total') == 0
|
||||
|
||||
# total of durations is not computed
|
||||
cell.statistic = Statistic.objects.get(slug='eighth')
|
||||
cell.save()
|
||||
resp = app.get(location)
|
||||
resp = app.get('/')
|
||||
assert resp.text.count('Total') == 0
|
||||
|
||||
|
||||
|
@ -1682,18 +1666,19 @@ def test_table_cell_new_api(app, admin_user, new_api_statistics):
|
|||
cell.save()
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/')
|
||||
location = '/api/dataviz/graph/%s/' % cell.id
|
||||
resp = app.get(location)
|
||||
assert resp.text.count('Total') == 1
|
||||
|
||||
cell.statistic = Statistic.objects.get(slug='two-series')
|
||||
cell.save()
|
||||
resp = app.get('/')
|
||||
resp = app.get(location)
|
||||
assert '21' in resp.text
|
||||
assert resp.text.count('Total') == 2
|
||||
|
||||
cell.statistic = Statistic.objects.get(slug='no-data')
|
||||
cell.save()
|
||||
resp = app.get('/')
|
||||
resp = app.get(location)
|
||||
assert resp.text.count('Total') == 0
|
||||
|
||||
|
||||
|
@ -2009,7 +1994,7 @@ def test_chartng_cell_new_api_filter_params_page_variables(app, admin_user, new_
|
|||
|
||||
|
||||
@with_httmock(new_api_mock)
|
||||
def test_chartng_cell_new_api_filter_params_page_variables_table(new_api_statistics, nocache):
|
||||
def test_chartng_cell_new_api_filter_params_page_variables_table(new_api_statistics, app, nocache):
|
||||
Page.objects.create(title='One', slug='index')
|
||||
page = Page.objects.create(
|
||||
title='One',
|
||||
|
@ -2026,7 +2011,8 @@ def test_chartng_cell_new_api_filter_params_page_variables_table(new_api_statist
|
|||
cell.filter_params = {'service': 'chrono', 'ou': 'variable:foo'}
|
||||
cell.save()
|
||||
|
||||
cell.render({**page.extra_variables, 'synchronous': True})
|
||||
location = '/api/dataviz/graph/%s/' % cell.pk
|
||||
app.get(location)
|
||||
request = new_api_mock.call['requests'][0]
|
||||
assert 'service=chrono' in request.url
|
||||
assert 'ou=bar' in request.url
|
||||
|
@ -2035,25 +2021,25 @@ def test_chartng_cell_new_api_filter_params_page_variables_table(new_api_statist
|
|||
cell.filter_params = {'service': 'chrono', 'ou': 'variable:unknown'}
|
||||
cell.save()
|
||||
|
||||
content = cell.render({'synchronous': True})
|
||||
resp = app.get(location)
|
||||
assert len(new_api_mock.call['requests']) == 1
|
||||
assert 'Page variable not found.' in content
|
||||
assert 'Page variable not found.' in resp.text
|
||||
|
||||
# variable with invalid syntax
|
||||
cell.filter_params = {'service': 'chrono', 'ou': 'variable:syntax_error'}
|
||||
cell.save()
|
||||
|
||||
content = cell.render({**page.extra_variables, 'synchronous': True})
|
||||
resp = app.get(location)
|
||||
assert len(new_api_mock.call['requests']) == 1
|
||||
assert 'Syntax error in page variable.' in content
|
||||
assert 'Syntax error in page variable.' in resp.text
|
||||
|
||||
# variable with missing context
|
||||
cell.filter_params = {'service': 'chrono', 'ou': 'variable:subslug_dependant'}
|
||||
cell.save()
|
||||
|
||||
content = cell.render({**page.extra_variables, 'synchronous': True})
|
||||
resp = app.get(location)
|
||||
assert len(new_api_mock.call['requests']) == 1
|
||||
assert 'Cannot evaluate page variable.' in content
|
||||
assert 'Cannot evaluate page variable.' in resp.text
|
||||
|
||||
|
||||
def test_dataviz_check_validity(nocache):
|
||||
|
|
Loading…
Reference in New Issue