dataviz: reload chart filters cell to reflect subfilters (#62533)

This commit is contained in:
Valentin Deniaud 2022-04-27 18:03:08 +02:00
parent 7f7d75c509
commit b5132df5f5
7 changed files with 96 additions and 4 deletions

View File

@ -19,6 +19,7 @@ from collections import OrderedDict
from django import forms
from django.conf import settings
from django.core.cache import cache
from django.db import transaction
from django.db.models import Q
from django.db.models.fields import BLANK_CHOICE_DASH
@ -278,6 +279,7 @@ class ChartFiltersForm(ChartFiltersMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
page = kwargs.pop('page')
filters_cell_id = kwargs.pop('filters_cell_id', None)
super().__init__(*args, **kwargs)
chart_cells = list(ChartNgCell.objects.filter(page=page, statistic__isnull=False).order_by('order'))
@ -285,6 +287,10 @@ class ChartFiltersForm(ChartFiltersMixin, forms.ModelForm):
self.fields.clear()
return
if filters_cell_id:
for cell in chart_cells:
cell.subfilters = cache.get(cell.get_cache_key(filters_cell_id), cell.subfilters)
first_cell = chart_cells[0]
for field in self._meta.fields:
self.fields[field].initial = getattr(first_cell, field)

View File

@ -738,6 +738,9 @@ class ChartNgCell(CellBase):
self.filter_params = {k: v for k, v in self.filter_params.items() if k in subfilter_ids}
self.save()
def get_cache_key(self, filters_cell_id):
return 'dataviz:%s:%s' % (filters_cell_id, self.pk)
@register_cell_class
class ChartFiltersCell(CellBase):
@ -756,5 +759,13 @@ class ChartFiltersCell(CellBase):
from .forms import ChartFiltersForm
ctx = super().get_cell_extra_context(context)
ctx['form'] = ChartFiltersForm(page=self.page)
if 'filters_cell_id' in context['request'].GET: # detect refresh on submit
ctx['form'] = ChartFiltersForm(
data=context['request'].GET,
page=self.page,
filters_cell_id=context['request'].GET['filters_cell_id'],
)
else:
ctx['form'] = ChartFiltersForm(page=self.page)
return ctx

View File

@ -1,7 +1,9 @@
function get_graph_querystring(extra_context, width=undefined) {
qs = [];
if ($('#chart-filters'))
if ($('#chart-filters')) {
qs.push($('#chart-filters').serialize());
qs.push('filters_cell_id=' + $('body').data('filters-cell-id'));
}
if (extra_context)
qs.push('ctx=' + extra_context);
if (window.location.search)

View File

@ -23,6 +23,20 @@
<script>
$(function () {
if (!$('body').data('filters-cell-id')) {
$('body').data('filters-cell-id', Math.random().toString(36).slice(2, 7));
var loaded_cell_count = 0;
document.querySelectorAll('div.chartngcell embed').forEach(graph => {
graph.addEventListener('load', function() {
if (++loaded_cell_count == $('div.chartngcell embed').length) {
combo_load_cell($('.chart-filters-cell'));
loaded_cell_count = 0;
}
});
});
}
start_field = $('#id_time_range_start');
end_field = $('#id_time_range_end');
$('#id_time_range').change(function() {
@ -37,6 +51,9 @@
$('#chart-filters').submit(function(e) {
e.preventDefault();
$(window).trigger('combo:refresh-graphs');
chart_cell = $(this).parents('.cell');
new_url = chart_cell.data('ajax-cell-url') + '?filters_cell_id=' + $('body').data('filters-cell-id') + '&' + $(this).serialize();
chart_cell.data('ajax-cell-url', new_url);
});
});
</script>

View File

@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.core import signing
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpResponse, HttpResponseBadRequest
from django.shortcuts import render
@ -24,7 +25,7 @@ from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import DetailView
from requests.exceptions import HTTPError
from combo.utils import get_templated_url, requests
from combo.utils import NothingInCacheException, get_templated_url, requests
from .forms import ChartNgPartialForm
from .models import ChartNgCell, Gauge, MissingVariable, UnsupportedDataSet
@ -42,6 +43,7 @@ class DatavizGraphView(DetailView):
def dispatch(self, request, *args, **kwargs):
self.cell = self.get_object()
self.filters_cell_id = request.GET.get('filters_cell_id')
if not self.cell.page.is_visible(request.user):
raise PermissionDenied()
@ -50,6 +52,10 @@ class DatavizGraphView(DetailView):
if not self.cell.statistic or not self.cell.statistic.url:
raise Http404('misconfigured cell')
if self.filters_cell_id:
self.cell.subfilters = cache.get(
self.cell.get_cache_key(self.filters_cell_id), self.cell.subfilters
)
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
@ -83,6 +89,9 @@ class DatavizGraphView(DetailView):
else:
return self.error(_('Unknown HTTP error: %s' % e))
if self.filters_cell_id:
self.update_subfilters_cache(form.instance)
if self.cell.chart_type == 'table':
if not chart.raw_series:
return self.error(_('No data'))
@ -106,5 +115,15 @@ class DatavizGraphView(DetailView):
}
return render(self.request, 'combo/dataviz-error.svg', context=context, content_type='image/svg+xml')
def update_subfilters_cache(self, cell):
try:
data = cell.get_statistic_data(raise_if_not_cached=True)
except NothingInCacheException:
pass # should not happen
else:
cache.set(
cell.get_cache_key(self.filters_cell_id), data.json()['data'].get('subfilters', []), 300
)
dataviz_graph = xframe_options_sameorigin(DatavizGraphView.as_view())

View File

@ -4,7 +4,12 @@ function combo_load_cell(elem) {
var extra_context = $elem.data('extra-context');
$.support.cors = true; /* IE9 */
var qs;
if (window.location.search) {
if (url.includes('?')) {
qs = '&';
if (window.location.search) {
qs += window.location.search.slice(1) + '&';
}
} else if (window.location.search) {
qs = window.location.search + '&';
} else {
qs = '?';

View File

@ -2655,3 +2655,35 @@ def test_chartng_cell_subfilter_page_variable(new_api_statistics, app, admin_use
resp.form[field_prefix + 'form'] = 'contact'
manager_submit_cell(resp.form)
assert field_prefix + 'menu' in resp.form.fields
@with_httmock(new_api_mock)
def test_chart_filters_cell_dynamic_subfilters(new_api_statistics, app, admin_user):
page = Page.objects.create(title='One', slug='index')
ChartFiltersCell.objects.create(page=page, order=1, placeholder='content')
cell = ChartNgCell.objects.create(page=page, order=2, placeholder='content')
cell.statistic = Statistic.objects.get(slug='with-subfilter')
cell.save()
app = login(app)
resp = app.get('/')
assert 'filter-form' in resp.form.fields
assert 'filter-menu' not in resp.form.fields
# ensure choice exist
resp.form['filter-form'] = 'food-request'
# simulate chart cell ajax refresh on form submission
app.get('/api/dataviz/graph/%s/' % cell.pk + '?filter-form=food-request&filters_cell_id=xxx')
# simulate filters cell ajax refresh after cell refresh
location = resp.pyquery('.chartfilterscell').attr('data-ajax-cell-url')
resp = app.get(location + '?filter-form=food-request&filters_cell_id=xxx')
assert resp.form['filter-form'].value == 'food-request'
assert 'filter-menu' in resp.form.fields
# check isolation between pages by modifying filters_cell_id
app.get('/api/dataviz/graph/%s/' % cell.pk + '?filter-form=contact&filters_cell_id=yyy')
resp = app.get(location + '?filter-form=food-request&filters_cell_id=xxx')
assert 'filter-form' in resp.form.fields
assert 'filter-menu' in resp.form.fields