combo/tests/test_dataviz.py

2568 lines
90 KiB
Python

import json
import urllib.parse
from datetime import date
from unittest import mock
import pytest
from django.apps import apps
from django.contrib.auth.models import Group
from django.test import override_settings
from httmock import HTTMock, remember_called, urlmatch, with_httmock
from requests.exceptions import HTTPError
from combo.apps.dataviz.models import ChartFiltersCell, ChartNgCell, Gauge, Statistic, UnsupportedDataSet
from combo.data.models import Page, ValidityInfo
from combo.utils.spooler import refresh_statistics_data
from .test_public import login
from .utils import manager_submit_cell
pytestmark = pytest.mark.django_db
@pytest.fixture
def cell():
page = Page(title='One', slug='index')
page.save()
cell = Gauge(page=page, order=0, placeholder='content')
cell.data_source = '[test_url]/XXX'
cell.save()
return cell
def test_jsonp_gauge(app, cell):
with override_settings(TEMPLATE_VARS={'test_url': 'http://www.example.net'}):
resp = app.get('/')
assert 'data-gauge-count-jsonp-url="http://www.example.net/XXX"' in resp.text
def test_json_gauge(app, cell):
cell.jsonp_data_source = False
cell.save()
with override_settings(TEMPLATE_VARS={'test_url': 'http://www.example.net'}):
with mock.patch('combo.apps.dataviz.views.requests.get') as requests_get:
requests_get.return_value = mock.Mock(content='xxx', status_code=200)
resp = app.get('/')
assert 'data-gauge-count-url="/ajax/gauge-count/%s/"' % cell.id in resp.text
resp = app.get('/ajax/gauge-count/%s/' % cell.id)
assert resp.text == 'xxx'
assert requests_get.call_args[0][0] == 'http://www.example.net/XXX'
VISUALIZATION_JSON = [
{
'data-url': 'https://bijoe.example.com/visualization/1/json/',
'path': 'https://bijoe.example.com/visualization/1/iframe/?signature=123',
'name': 'example visualization (X)',
'slug': 'example',
},
{
'data-url': 'https://bijoe.example.com/visualization/2/json/',
'path': 'https://bijoe.example.com/visualization/2/iframe/?signature=123',
'name': 'second visualization (Y)',
'slug': 'second',
},
{
'data-url': 'https://bijoe.example.com/visualization/3/json/',
'path': 'https://bijoe.example.com/visualization/3/iframe/?signature=123',
'name': 'third visualization (X/Y)',
'slug': 'third',
},
{
'data-url': 'https://bijoe.example.com/visualization/4/json/',
'path': 'https://bijoe.example.com/visualization/4/iframe/?signature=123',
'name': 'fourth visualization (no axis)',
'slug': 'fourth',
},
{
'data-url': 'https://bijoe.example.com/visualization/5/json/',
'path': 'https://bijoe.example.com/visualization/5/iframe/?signature=123',
'name': 'fifth visualization (loop/X)',
'slug': 'fifth',
},
{
'data-url': 'https://bijoe.example.com/visualization/6/json/',
'path': 'https://bijoe.example.com/visualization/6/iframe/?signature=123',
'name': 'sixth visualization (loop/Y)',
'slug': 'sixth',
},
{
'data-url': 'https://bijoe.example.com/visualization/7/json/',
'path': 'https://bijoe.example.com/visualization/7/iframe/?signature=123',
'name': 'seventh visualization (loop/X/Y)',
'slug': 'seventh',
},
{
'data-url': 'https://bijoe.example.com/visualization/8/json/',
'path': 'https://bijoe.example.com/visualization/8/iframe/?signature=123',
'name': 'eighth visualization (duration)',
'slug': 'eighth',
},
{
'data-url': 'https://bijoe.example.com/visualization/9/json/',
'path': 'https://bijoe.example.com/visualization/9/iframe/?signature=123',
'name': 'nineth visualization (loop over varying dimensions)',
'slug': 'nineth',
},
{
'data-url': 'https://bijoe.example.com/visualization/10/json/',
'path': 'https://bijoe.example.com/visualization/10/iframe/?signature=123',
'name': 'tenth visualization (percents)',
'slug': 'tenth',
},
{
'data-url': 'https://bijoe.example.com/visualization/11/json/',
'path': 'https://bijoe.example.com/visualization/11/iframe/?signature=123',
'name': 'eleventh visualization (not found)',
'slug': 'eleventh',
},
{
'data-url': 'https://bijoe.example.com/visualization/12/json/',
'path': 'https://bijoe.example.com/visualization/12/iframe/?signature=123',
'name': 'twelth visualization (all null)',
'slug': 'twelth',
},
{
'data-url': 'https://bijoe.example.com/visualization/13/json/',
'path': 'https://bijoe.example.com/visualization/13/iframe/?signature=123',
'name': 'thirteenth visualization (loop with empty x_labels)',
'slug': 'thirteenth',
},
{
'data-url': 'https://bijoe.example.com/visualization/14/json/',
'path': 'https://bijoe.example.com/visualization/14/iframe/?signature=123',
'name': 'fourteenth visualization (empty data)',
'slug': 'fourteenth',
},
{
'data-url': 'https://bijoe.example.com/visualization/15/json/',
'path': 'https://bijoe.example.com/visualization/15/iframe/?signature=123',
'name': 'fifteenth visualization (only loop labels)',
'slug': 'fifteenth',
},
{
'data-url': 'https://bijoe.example.com/visualization/16/json/',
'path': 'https://bijoe.example.com/visualization/16/iframe/?signature=123',
'name': 'sixteenth visualization (numerical labels)',
'slug': 'sixteenth',
},
]
@remember_called
def bijoe_mock(url, request):
if url.path == '/visualization/json/':
return {'content': json.dumps(VISUALIZATION_JSON), 'request': request, 'status_code': 200}
if url.path == '/visualization/1/json/':
response = {
'format': '1',
'data': [222, 134, 0, 53],
'axis': {'x_labels': ['web', 'mail', 'phone', 'email']},
'measure': 'integer',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/2/json/':
response = {
'format': '1',
'data': [222, 134, 0, 53],
'axis': {'y_labels': ['web', 'mail', 'phone', 'email']},
'measure': 'integer',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/3/json/':
response = {
'format': '1',
'data': [
[222, 134, 0, 53],
[122, 114, 2, 33],
],
'axis': {
'x_labels': ['web', 'mail', 'phone', 'email'],
'y_labels': ['foo', 'bar'],
},
'measure': 'integer',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/4/json/':
response = {
'format': '1',
'data': 222,
'axis': {},
'measure': 'integer',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/5/json/':
response = {
'format': '1',
'data': [
[222, 134, 0, 53],
[122, 114, 2, 33],
],
'axis': {
'x_labels': ['web', 'mail', 'phone', 'email'],
'loop': ['foo', 'bar'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/6/json/':
response = {
'format': '1',
'data': [
[222, 134, 0, 53],
[122, 114, 2, 33],
],
'axis': {
'y_labels': ['web', 'mail', 'phone', 'email'],
'loop': ['foo', 'bar'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/7/json/':
response = {
'format': '1',
'data': [
[[222, 134, 0, 53], [122, 114, 2, 33]],
[[222, 134, 0, 53], [122, 114, 2, 33]],
[[222, 134, 0, 53], [122, 114, 2, 33]],
[[222, 134, 0, 53], [122, 114, 2, 33]],
],
'axis': {
'x_labels': ['foo', 'bar'],
'y_labels': ['web', 'mail', 'phone', 'email'],
'loop': ['a', 'b', 'c', 'd'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/8/json/':
response = {
'format': '1',
'data': [1000, 123000, 8600, 86400],
'axis': {'y_labels': ['web', 'mail', 'email', 'fax']},
'unit': 'seconds',
'measure': 'duration',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/9/json/':
response = {
'format': '1',
'data': [
[1, 1, 1, 1],
[1],
[1, 1],
],
'axis': {
'y_labels': ['web', 'mail', 'email'],
'loop': ['a', 'b', 'c', 'd'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/10/json/':
response = {
'format': '1',
'data': [10, 20, 30, 40, 0],
'axis': {'y_labels': ['web', 'mail', 'email', 'fax', 'phone']},
'unit': None,
'measure': 'percent',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/11/json/':
response = {
'detail': 'not found',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 404}
if url.path == '/visualization/12/json/':
response = {
'format': '1',
'data': [None, None],
'axis': {'x_labels': ['web', 'mail']},
'measure': 'integer',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/13/json/':
response = {
'format': '1',
'data': [[[], []], [[], []], [[], []]],
'axis': {
'x_labels': [],
'y_labels': ['web', 'mail'],
'loop': ['a', 'b', 'c'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/14/json/':
response = {
'format': '1',
'data': [],
'axis': {
'x_labels': ['a', 'b', 'c'],
'y_labels': [],
},
'measure': 'integer',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/15/json/':
response = {
'format': '1',
'data': [1, 2, 3],
'axis': {
'loop': ['a', 'b', 'c'],
},
'measure': 'integer',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/visualization/16/json/':
response = {
'format': '1',
'data': [1, 2, 3, 4, 5, 6, 7, 8, 9],
'axis': {
'x_labels': ['item2', 'foo', 'item20', 'item10', 'foo2', 'Item3', '10', '4', 'item1'],
'y_labels': [],
},
'measure': 'integer',
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
STATISTICS_LIST = {
'data': [
{
'url': 'https://authentic.example.com/api/statistics/one-serie/',
'name': 'One serie stat',
'id': 'one-serie',
"filters": [
{
"default": "month",
"id": "time_interval",
"label": "Time interval",
"options": [
{"id": "day", "label": "Day"},
{"id": "month", "label": "Month"},
{"id": "year", "label": "Year"},
],
"required": True,
},
{
"id": "ou",
"label": "Organizational Unit",
"options": [
{"id": "default", "label": "Default OU"},
{"id": "other", "label": "Other OU"},
],
},
{
"id": "service",
"label": "Service",
"options": [
{"id": "chrono", "label": "Chrono"},
{"id": "combo", "label": "Combo"},
],
"default": "chrono",
},
],
},
{
'url': 'https://authentic.example.com/api/statistics/two-series/',
'name': 'Two series stat',
'id': 'two-series',
},
{
'url': 'https://authentic.example.com/api/statistics/no-data/',
'name': 'No data stat',
'id': 'no-data',
},
{
'url': 'https://authentic.example.com/api/statistics/not-found/',
'name': '404 not found stat',
'id': 'not-found',
},
{
'url': 'https://authentic.example.com/api/statistics/daily/',
'name': 'Daily discontinuous serie',
'id': 'daily',
"filters": [
{
"default": "day",
"id": "time_interval",
"label": "Time interval",
"options": [
{"id": "day", "label": "Day"},
],
"required": True,
}
],
},
{
'url': 'https://authentic.example.com/api/statistics/leap-week/',
'name': 'Same week spanning two years',
'id': 'leap-week',
"filters": [
{
"default": "day",
"id": "time_interval",
"label": "Time interval",
"options": [
{"id": "day", "label": "Day"},
],
"required": True,
}
],
},
{
'url': 'https://authentic.example.com/api/statistics/filter-multiple/',
'name': 'Filter on multiple values',
'id': 'filter-multiple',
"filters": [
{
"id": "color",
"label": "Color",
"options": [
{"id": "red", "label": "Red"},
{"id": "green", "label": "Green"},
{"id": "blue", "label": "Blue"},
],
"multiple": True,
}
],
},
{
'url': 'https://authentic.example.com/api/statistics/with-subfilter/',
'name': 'With subfilter',
'id': 'with-subfilter',
'filters': [
{
'id': 'form',
'label': 'Form',
'has_subfilters': True,
'options': [
{'id': 'food-request', 'label': 'Food request'},
{'id': 'contact', 'label': 'Contact'},
{'id': 'error', 'label': 'Error'},
],
},
{
'id': 'other',
'label': 'Other',
'options': [
{'id': 'one', 'label': 'One'},
{'id': 'two', 'label': 'two'},
],
},
],
},
{
'url': 'https://authentic.example.com/api/statistics/future-data/',
'name': 'With future data',
'id': 'with-future-data',
'future_data': True,
},
{
'url': 'https://authentic.example.com/api/statistics/option-groups/',
'name': 'Option groups',
'id': 'option-groups',
"filters": [
{
"id": "form",
"label": "Form",
"options": [
[None, [{'id': 'all', 'label': 'All'}]],
['Category A', [{'id': 'test', 'label': 'Test'}]],
['Category B', [{'id': 'test-2', 'label': 'test 2'}]],
],
}
],
},
{
'url': 'https://authentic.example.com/api/statistics/deprecated-filter/',
'name': 'Deprecated filter',
'id': 'deprecated-filter',
"filters": [
{
"id": "form",
"label": "Form",
'deprecated': True,
'deprecation_hint': 'This field should not be used',
'options': [
{'id': 'one', 'label': 'One'},
{'id': 'two', 'label': 'two'},
],
},
{
"id": "card",
"label": "Card",
'deprecated': True,
'options': [
{'id': 'one', 'label': 'One'},
{'id': 'two', 'label': 'two'},
],
'default': 'one',
},
],
},
]
}
@remember_called
def new_api_mock(url, request):
if url.path == '/api/statistics/':
return {'content': json.dumps(STATISTICS_LIST), 'request': request, 'status_code': 200}
if url.path == '/api/statistics/one-serie/':
response = {
'data': {
'series': [{'data': [None, 16, 2], 'label': 'Serie 1'}],
'x_labels': ['2020-10', '2020-11', '2020-12'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/api/statistics/two-series/':
response = {
'data': {
'series': [
{'data': [None, 16, 2], 'label': 'Serie 1'},
{'data': [2, 1, None], 'label': 'Serie 2'},
],
'x_labels': ['2020-10', '2020-11', '2020-12'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/api/statistics/no-data/':
response = {
'data': {'series': [], 'x_labels': []},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/api/statistics/not-found/':
return {'content': b'', 'request': request, 'status_code': 404}
if url.path == '/api/statistics/daily/':
response = {
'data': {
'series': [
{'data': [None, 1, 16, 2], 'label': 'Serie 1'},
{'data': [2, 2, 1, None], 'label': 'Serie 2'},
],
'x_labels': ['2020-10-06', '2020-10-13', '2020-11-30', '2022-02-01'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/api/statistics/leap-week/':
response = {
'data': {
'series': [
{'data': [None, 1, 16, 2], 'label': 'Serie 1'},
],
'x_labels': ['2020-12-30', '2020-12-31', '2021-01-01', '2021-01-02'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/api/statistics/filter-multiple/':
response = {
'data': {
'series': [
{'data': [None, 1], 'label': 'Red / Green'},
{'data': [1, 4], 'label': 'Red / Blue'},
],
'x_labels': ['2020-12-30', '2020-12-31'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/api/statistics/with-subfilter/':
response = {
'data': {
'series': [{'data': [None, 16, 2], 'label': 'Serie 1'}],
'x_labels': ['2020-10', '2020-11', '2020-12'],
'subfilters': [],
},
}
if 'form=food-request' in url.query:
response['data']['subfilters'] = [
{
"id": "menu",
"label": "Menu",
"options": [
{"id": "meat", "label": "Meat"},
{"id": "vegan", "label": "Vegan"},
],
}
]
if 'form=error' in url.query:
return {'content': b'', 'request': request, 'status_code': 404}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
if url.path == '/api/statistics/future-data/':
response = {
'data': {
'series': [{'data': [None, 16, 2], 'label': 'Serie 1'}],
'x_labels': ['2020-10', '2020-11', '2020-12'],
},
}
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
@pytest.fixture
@with_httmock(bijoe_mock)
def statistics(settings):
settings.KNOWN_SERVICES = {
"bijoe": {
"plop": {"title": "test", "url": "https://bijoe.example.com", "secret": "combo", "orig": "combo"}
}
}
settings.STATISTICS_PROVIDERS = ['bijoe']
appconfig = apps.get_app_config('dataviz')
appconfig.hourly()
assert Statistic.objects.count() == len(VISUALIZATION_JSON)
@pytest.fixture
@with_httmock(new_api_mock)
def new_api_statistics(settings):
settings.KNOWN_SERVICES = {
'authentic': {
'connection': {
'title': 'Connection',
'url': 'https://authentic.example.com',
'secret': 'combo',
'orig': 'combo',
}
}
}
settings.STATISTICS_PROVIDERS = ['authentic']
appconfig = apps.get_app_config('dataviz')
appconfig.hourly()
assert Statistic.objects.count() == len(STATISTICS_LIST['data'])
@with_httmock(bijoe_mock)
def test_chartng_cell(app, statistics):
page = Page(title='One', slug='index')
page.save()
cell = ChartNgCell(page=page, order=1)
cell.statistic = Statistic.objects.get(slug='example')
cell.save()
# bar
chart = cell.get_chart()
assert chart.__class__.__name__ == 'Bar'
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [([222, 134, 0, 53], {'title': ''})]
# horizontal bar
cell.chart_type = 'horizontal-bar'
chart = cell.get_chart()
assert chart.__class__.__name__ == 'HorizontalBar'
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [([222, 134, 0, 53], {'title': ''})]
# pie
cell.chart_type = 'pie'
chart = cell.get_chart()
assert chart.__class__.__name__ == 'Pie'
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222], {'title': 'web'}),
([134], {'title': 'mail'}),
([53], {'title': 'email'}),
]
# data in Y
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='second')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [([222, 134, 0, 53], {'title': ''})]
# data in X/Y
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='third')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# stacked bar with percent
cell.chart_type = 'stacked-bar-percent'
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([64.5, 54, 0, 61.6], {'title': 'foo'}),
([35.5, 46, 100, 38.4], {'title': 'bar'}),
]
assert all(x + y == 100 for x, y in zip(chart.raw_series[0][0], chart.raw_series[1][0]))
# single data point
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='fourth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['']
assert chart.raw_series == [([222], {'title': ''})]
# loop/X
cell.statistic = Statistic.objects.get(slug='fifth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# loop/Y
cell.statistic = Statistic.objects.get(slug='sixth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# loop/X/Y
cell.statistic = Statistic.objects.get(slug='seventh')
cell.save()
with pytest.raises(UnsupportedDataSet):
chart = cell.get_chart()
# duration
cell.statistic = Statistic.objects.get(slug='eighth')
cell.save()
chart = cell.get_chart()
# loop/X/Y
cell.statistic = Statistic.objects.get(slug='nineth')
cell.save()
with pytest.raises(UnsupportedDataSet):
chart = cell.get_chart()
# deleted visualization
cell.statistic = Statistic.objects.get(slug='eleventh')
cell.save()
with pytest.raises(HTTPError):
chart = cell.get_chart()
# loop and empty x_labels
cell.statistic = Statistic.objects.get(slug='thirteenth')
cell.save()
with pytest.raises(UnsupportedDataSet):
chart = cell.get_chart()
# only loop labels
cell.statistic = Statistic.objects.get(slug='fifteenth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['a', 'b', 'c']
assert chart.raw_series == [
([1, 2, 3], {'title': ''}),
]
@with_httmock(new_api_mock)
def test_chartng_cell_new_api(app, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1)
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
chart = cell.get_chart()
assert chart.__class__.__name__ == 'Bar'
assert chart.x_labels == ['2020-10', '2020-11', '2020-12']
assert chart.raw_series == [
(
[None, 16, 2],
{'title': 'Serie 1'},
)
]
cell.chart_type = 'pie'
chart = cell.get_chart()
assert chart.__class__.__name__ == 'Pie'
assert chart.x_labels == ['2020-10', '2020-11', '2020-12']
assert chart.raw_series == [
([16], {'title': '2020-11'}),
([2], {'title': '2020-12'}),
]
cell.statistic = Statistic.objects.get(slug='two-series')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['2020-10', '2020-11', '2020-12']
assert chart.raw_series == [([None, 16, 2], {'title': 'Serie 1'}), ([2, 1, None], {'title': 'Serie 2'})]
# stacked bar with percent
cell.chart_type = 'stacked-bar-percent'
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['2020-10', '2020-11', '2020-12']
assert chart.raw_series == [
([None, 94.1, 100], {'title': 'Serie 1'}),
([100, 5.9, None], {'title': 'Serie 2'}),
]
assert all(x + y == 100 for x, y in zip(chart.raw_series[0][0], chart.raw_series[1][0]) if x and y)
cell.statistic = Statistic.objects.get(slug='no-data')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == []
assert chart.raw_series == []
cell.statistic = Statistic.objects.get(slug='not-found')
cell.save()
with pytest.raises(HTTPError):
chart = cell.get_chart()
@with_httmock(bijoe_mock)
def test_chartng_cell_hide_null_values(app, statistics):
page = Page(title='One', slug='index')
page.save()
cell = ChartNgCell(page=page, order=1)
cell.statistic = Statistic.objects.get(slug='example')
cell.hide_null_values = True
cell.save()
# bar
chart = cell.get_chart()
assert chart.__class__.__name__ == 'Bar'
assert chart.x_labels == ['web', 'mail', 'email']
assert chart.raw_series == [([222, 134, 53], {'title': ''})]
# horizontal bar
cell.chart_type = 'horizontal-bar'
chart = cell.get_chart()
assert chart.__class__.__name__ == 'HorizontalBar'
assert chart.x_labels == ['web', 'mail', 'email']
assert chart.raw_series == [([222, 134, 53], {'title': ''})]
# pie
cell.chart_type = 'pie'
chart = cell.get_chart()
assert chart.__class__.__name__ == 'Pie'
assert chart.x_labels == ['web', 'mail', 'email']
assert chart.raw_series == [
([222], {'title': 'web'}),
([134], {'title': 'mail'}),
([53], {'title': 'email'}),
]
# data in Y
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='second')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'email']
assert chart.raw_series == [([222, 134, 53], {'title': ''})]
# data in X/Y
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='third')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# single data point
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='fourth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['']
assert chart.raw_series == [([222], {'title': ''})]
# loop/X
cell.statistic = Statistic.objects.get(slug='fifth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# loop/Y
cell.statistic = Statistic.objects.get(slug='sixth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# all null
cell.statistic = Statistic.objects.get(slug='twelth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == []
assert chart.raw_series == [([], {'title': ''})]
@with_httmock(new_api_mock)
def test_chartng_cell_hide_null_values_new_api(app, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, hide_null_values=True)
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.hide_null_values = True
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['2020-11', '2020-12']
assert chart.raw_series == [
(
[16, 2],
{'title': 'Serie 1'},
)
]
@with_httmock(bijoe_mock)
def test_chartng_cell_sort_order_alpha(app, statistics):
page = Page(title='One', slug='index')
page.save()
cell = ChartNgCell(page=page, order=1)
cell.statistic = Statistic.objects.get(slug='example')
cell.sort_order = 'alpha'
cell.save()
# bar
chart = cell.get_chart()
assert chart.__class__.__name__ == 'Bar'
assert chart.x_labels == ['email', 'mail', 'phone', 'web']
assert chart.raw_series == [([53, 134, 0, 222], {'title': ''})]
# horizontal bar
cell.chart_type = 'horizontal-bar'
chart = cell.get_chart()
assert chart.__class__.__name__ == 'HorizontalBar'
assert chart.x_labels == ['email', 'mail', 'phone', 'web']
assert chart.raw_series == [([53, 134, 0, 222], {'title': ''})]
# pie
cell.chart_type = 'pie'
chart = cell.get_chart()
assert chart.__class__.__name__ == 'Pie'
assert chart.x_labels == ['email', 'mail', 'phone', 'web']
assert chart.raw_series == [
([53], {'title': 'email'}),
([134], {'title': 'mail'}),
([222], {'title': 'web'}),
]
# data in Y
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='second')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['email', 'mail', 'phone', 'web']
assert chart.raw_series == [([53, 134, 0, 222], {'title': ''})]
# data in X/Y
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='third')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# single data point
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='fourth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['']
assert chart.raw_series == [([222], {'title': ''})]
# loop/X
cell.statistic = Statistic.objects.get(slug='fifth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# loop/Y
cell.statistic = Statistic.objects.get(slug='sixth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# empty data
cell.statistic = Statistic.objects.get(slug='fourteenth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['a', 'b', 'c']
assert chart.raw_series == [([], {'title': ''})]
cell.statistic = Statistic.objects.get(slug='sixteenth')
cell.sort_order = 'alpha'
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['4', '10', 'foo', 'foo2', 'item1', 'item2', 'Item3', 'item10', 'item20']
assert chart.raw_series == [([8, 7, 2, 5, 9, 1, 6, 4, 3], {'title': ''})]
@with_httmock(bijoe_mock)
def test_chartng_cell_sort_order_desc(app, statistics):
page = Page(title='One', slug='index')
page.save()
cell = ChartNgCell(page=page, order=1)
cell.statistic = Statistic.objects.get(slug='example')
cell.sort_order = 'desc'
cell.save()
# bar
chart = cell.get_chart()
assert chart.__class__.__name__ == 'Bar'
assert chart.x_labels == ['web', 'mail', 'email', 'phone']
assert chart.raw_series == [([222, 134, 53, 0], {'title': ''})]
# horizontal bar
cell.chart_type = 'horizontal-bar'
chart = cell.get_chart()
assert chart.__class__.__name__ == 'HorizontalBar'
assert chart.x_labels == ['web', 'mail', 'email', 'phone']
assert chart.raw_series == [([222, 134, 53, 0], {'title': ''})]
# pie
cell.chart_type = 'pie'
chart = cell.get_chart()
assert chart.__class__.__name__ == 'Pie'
assert chart.x_labels == ['web', 'mail', 'email', 'phone']
assert chart.raw_series == [
([222], {'title': 'web'}),
([134], {'title': 'mail'}),
([53], {'title': 'email'}),
]
# data in Y
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='second')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'email', 'phone']
assert chart.raw_series == [([222, 134, 53, 0], {'title': ''})]
# data in X/Y
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='third')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# single data point
cell.chart_type = 'bar'
cell.statistic = Statistic.objects.get(slug='fourth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['']
assert chart.raw_series == [([222], {'title': ''})]
# loop/X
cell.statistic = Statistic.objects.get(slug='fifth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# loop/Y
cell.statistic = Statistic.objects.get(slug='sixth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['web', 'mail', 'phone', 'email']
assert chart.raw_series == [
([222, 134, 0, 53], {'title': 'foo'}),
([122, 114, 2, 33], {'title': 'bar'}),
]
# empty data
cell.statistic = Statistic.objects.get(slug='fourteenth')
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['a', 'b', 'c']
assert chart.raw_series == [([], {'title': ''})]
@with_httmock(new_api_mock)
def test_chartng_cell_sort_order_new_api(app, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1)
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.sort_order = 'desc'
cell.save()
chart = cell.get_chart()
assert chart.x_labels == ['2020-11', '2020-12', '2020-10']
assert chart.raw_series == [
(
[16, 2, None],
{'title': 'Serie 1'},
)
]
@with_httmock(bijoe_mock)
def test_chartng_cell_view(app, normal_user, statistics):
page = Page(title='One', slug='index')
page.save()
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='example')
cell.save()
location = '/api/dataviz/graph/%s/' % cell.id
resp = app.get(location) # get data in cache
resp = app.get('/')
assert 'min-height: 250px' in resp.text
assert location in resp.text
resp = app.get(location + '?width=400')
assert resp.content_type == 'image/svg+xml'
resp = app.get(location + '?width=') # no crash
assert resp.content_type == 'image/svg+xml'
page.public = False
page.save()
resp = app.get(location + '?width=400', status=403)
page.public = True
page.save()
group = Group(name='plop')
group.save()
cell.public = False
cell.groups.set([group])
cell.save()
resp = app.get(location + '?width=400', status=403)
app = login(app, username='normal-user', password='normal-user')
resp = app.get(location + '?width=400', status=403)
normal_user.groups.set([group])
normal_user.save()
resp = app.get(location + '?width=400', status=200)
# table visualization
cell.chart_type = 'table'
cell.save()
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, status=200)
assert '<p>Unsupported dataset.</p>' in resp.text
cell.chart_type = 'bar'
cell.save()
resp = app.get(location + '?width=400', status=200)
assert 'Unsupported dataset' in resp.text
# durations
cell.statistic = Statistic.objects.get(slug='eighth')
cell.chart_type = 'table'
cell.save()
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
assert '<td>1 day</td>' in resp.text
cell.chart_type = 'bar'
cell.save()
resp = app.get(location + '?width=400', status=200)
assert '>Less than an hour<' in resp.text
assert '>1 day and 10 hours<' in resp.text
assert '>2 hours<' in resp.text
assert '>1 day<' in resp.text
# percents
cell.statistic = Statistic.objects.get(slug='tenth')
cell.chart_type = 'table'
cell.save()
resp = app.get(location, status=200)
assert '<td>10.0%</td>' in resp.text
cell.chart_type = 'bar'
cell.save()
resp = app.get(location + '?width=400', status=200)
assert '>10.0%<' in resp.text
# deleted visualization
cell.statistic = Statistic.objects.get(slug='eleventh')
cell.save()
resp = app.get(location)
assert 'not found' in resp.text
@with_httmock(new_api_mock)
def test_chartng_cell_view_new_api(app, normal_user, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
location = '/api/dataviz/graph/%s/' % cell.id
resp = app.get('/')
assert 'min-height: 250px' in resp.text
assert location in resp.text
# table visualization
cell.chart_type = 'table'
cell.save()
resp = app.get(location, status=200)
assert '<td>18</td>' in resp.text
# deleted visualization
cell.statistic = Statistic.objects.get(slug='not-found')
cell.save()
resp = app.get(location)
assert 'not found' in resp.text
cell.statistic.url = ''
cell.statistic.save()
resp = app.get(location, status=404)
@with_httmock(bijoe_mock)
def test_chartng_cell_manager(app, admin_user, statistics):
page = Page(title='One', slug='index')
page.save()
Statistic.objects.create(
slug='unavailable-stat', label='Unavailable Stat', site_slug='plop', available=False
)
app = login(app)
cell = ChartNgCell.objects.create(page=page, order=1, placeholder='content')
resp = app.get('/manage/pages/%s/' % page.id)
assert 'time_range' not in resp.form.fields
assert 'time_range_start' not in resp.form.fields
assert 'time_range_end' not in resp.form.fields
assert 'time_range_start_template' not in resp.form.fields
assert 'time_range_end_template_end' not in resp.form.fields
cell.statistic = Statistic.objects.get(slug='example')
cell.save()
resp = app.get('/manage/pages/%s/' % page.id)
statistics_field = resp.form['cdataviz_chartngcell-%s-statistic' % cell.id]
# available visualizations and a blank choice
assert len(statistics_field.options) == len(VISUALIZATION_JSON) + 1
assert statistics_field.value == str(cell.statistic.pk)
assert statistics_field.options[1][2] == 'test: eighth visualization (duration)'
assert not 'Unavailable Stat' in resp.text
assert 'time_range' not in resp.form.fields
assert 'time_range_start' not in resp.form.fields
assert 'time_range_end' not in resp.form.fields
assert 'time_range_start_template' not in resp.form.fields
assert 'time_range_end_template_end' not in resp.form.fields
cell.statistic = Statistic.objects.get(slug='unavailable-stat')
cell.save()
resp = app.get('/manage/pages/%s/' % page.id)
statistics_field = resp.form['cdataviz_chartngcell-%s-statistic' % cell.id]
# available visualizations, a blank choice and the current unavailable visualization
assert len(statistics_field.options) == len(VISUALIZATION_JSON) + 2
assert 'Unavailable Stat' in resp.text
@with_httmock(new_api_mock)
def test_chartng_cell_manager_new_api(app, admin_user, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
field_prefix = 'cdataviz_chartngcell-%s-' % cell.id
statistics_field = resp.form[field_prefix + 'statistic']
assert len(statistics_field.options) == len(STATISTICS_LIST['data']) + 1
assert statistics_field.value == str(cell.statistic.pk)
selected_options = [option for option in statistics_field.options if option[1] is True]
assert len(selected_options) == 1
assert selected_options[0][2] == 'Connection: One serie stat'
time_interval_field = resp.form[field_prefix + 'time_interval']
assert time_interval_field.pos == statistics_field.pos + 1
assert time_interval_field.value == 'month'
assert time_interval_field.options == [
('day', False, 'Day'),
('month', True, 'Month'),
('year', False, 'Year'),
('week', False, 'Week'),
('weekday', False, 'Week day'),
]
ou_field = resp.form[field_prefix + 'ou']
assert ou_field.pos == statistics_field.pos + 2
assert ou_field.value == ''
assert ou_field.options == [
('', True, '---------'),
('default', False, 'Default OU'),
('other', False, 'Other OU'),
]
service_field = resp.form[field_prefix + 'service']
assert service_field.pos == statistics_field.pos + 3
assert service_field.value == 'chrono'
assert service_field.options == [
('', False, '---------'),
('chrono', True, 'Chrono'),
('combo', False, 'Combo'),
]
resp.form[field_prefix + 'service'] = ''
manager_submit_cell(resp.form)
assert resp.form[field_prefix + 'service'].value == ''
resp.form[field_prefix + 'ou'] = 'default'
manager_submit_cell(resp.form)
assert resp.form[field_prefix + 'ou'].value == 'default'
cell.refresh_from_db()
assert cell.get_filter_params() == {'ou': 'default', 'time_interval': 'month'}
ou_filter = next(x for x in cell.statistic.filters if x['id'] == 'ou')
ou_filter['options'] = [{'id': 'new', 'label': 'New'}]
cell.statistic.save()
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.form[field_prefix + 'ou'].value == 'default'
assert resp.form[field_prefix + 'ou'].options == [
('', False, '---------'),
('new', False, 'New'),
('default', True, 'default (unavailable)'),
]
resp.form[field_prefix + 'ou'] = ''
manager_submit_cell(resp.form)
assert resp.form[field_prefix + 'ou'].value == ''
cell.refresh_from_db()
assert cell.get_filter_params() == {'time_interval': 'month'}
resp.form[field_prefix + 'time_range'] = 'previous-year'
manager_submit_cell(resp.form)
cell.refresh_from_db()
assert cell.time_range == 'previous-year'
resp.form[field_prefix + 'time_range'] = 'range'
resp.form[field_prefix + 'time_range_start'] = '2020-10-01'
resp.form[field_prefix + 'time_range_end'] = '2020-11-03'
manager_submit_cell(resp.form)
cell.refresh_from_db()
assert cell.time_range == 'range'
assert cell.time_range_start == date(year=2020, month=10, day=1)
assert cell.time_range_end == date(year=2020, month=11, day=3)
resp.form[field_prefix + 'time_range_start'] = ''
resp.form[field_prefix + 'time_range_end'] = ''
manager_submit_cell(resp.form)
cell.refresh_from_db()
assert cell.time_range_start is None
assert cell.time_range_end is None
no_filters_stat = Statistic.objects.get(slug='two-series')
resp.form[field_prefix + 'statistic'] = no_filters_stat.pk
manager_submit_cell(resp.form)
assert resp.form[field_prefix + 'statistic'].value == str(no_filters_stat.pk)
assert field_prefix + 'time_interval' not in resp.form.fields
assert field_prefix + 'ou' not in resp.form.fields
cell.refresh_from_db()
assert cell.filter_params == {}
assert cell.time_range == ''
filter_multiple_stat = Statistic.objects.get(slug='filter-multiple')
resp.form[field_prefix + 'statistic'] = filter_multiple_stat.pk
manager_submit_cell(resp.form)
resp.form[field_prefix + 'color'].select_multiple(texts=['Blue', 'Green'])
manager_submit_cell(resp.form)
assert resp.form[field_prefix + 'color'].value == ['green', 'blue']
cell.refresh_from_db()
assert cell.filter_params == {'color': ['green', 'blue']}
color_filter = next(x for x in cell.statistic.filters if x['id'] == 'color')
color_filter['options'] = [{'id': 'black', 'label': 'Black'}, {'id': 'green', 'label': 'Green'}]
cell.statistic.save()
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.form[field_prefix + 'color'].value == ['green', 'blue']
assert resp.form[field_prefix + 'color'].options == [
('black', False, 'Black'),
('green', True, 'Green'),
('blue', True, 'blue (unavailable)'),
]
resp.form[field_prefix + 'color'].select_multiple(texts=[])
manager_submit_cell(resp.form)
assert resp.form[field_prefix + 'color'].value is None
cell.refresh_from_db()
assert cell.get_filter_params() == {}
option_groups_stat = Statistic.objects.get(slug='option-groups')
resp.form[field_prefix + 'statistic'] = option_groups_stat.pk
manager_submit_cell(resp.form)
assert resp.form[field_prefix + 'form'].options == [
('', True, '---------'),
('all', False, 'All'),
('test', False, 'Test'),
('test-2', False, 'test 2'),
]
assert resp.pyquery('optgroup[label="Category A"] option').val() == 'test'
assert resp.pyquery('optgroup[label="Category B"] option').val() == 'test-2'
deprecated_stat = Statistic.objects.get(slug='deprecated-filter')
resp.form[field_prefix + 'statistic'] = deprecated_stat.pk
manager_submit_cell(resp.form)
assert field_prefix + 'form' not in resp.form.fields
assert field_prefix + 'card' not in resp.form.fields
cell.refresh_from_db()
cell.filter_params = {'form': 'one', 'card': 'one'}
cell.save()
resp = app.get('/manage/pages/%s/' % page.id)
assert field_prefix + 'form' in resp.form.fields
assert field_prefix + 'card' not in resp.form.fields
assert 'Form (deprecated)' in resp.text
assert 'This field should not be used' in resp.text
@with_httmock(new_api_mock)
def test_chartng_cell_manager_future_data(app, admin_user, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
field_prefix = 'cdataviz_chartngcell-%s-' % cell.id
time_range_field = resp.form[field_prefix + 'time_range']
assert time_range_field.value == ''
assert time_range_field.options == [
('', True, '---------'),
('previous-year', False, 'Previous year'),
('current-year', False, 'Current year'),
('previous-month', False, 'Previous month'),
('current-month', False, 'Current month'),
('previous-week', False, 'Previous week'),
('current-week', False, 'Current week'),
('range', False, 'Free range (date)'),
('range-template', False, 'Free range (template)'),
]
stat_with_future_data = Statistic.objects.get(slug='with-future-data')
resp.form[field_prefix + 'statistic'] = stat_with_future_data.pk
manager_submit_cell(resp.form)
time_range_field = resp.form[field_prefix + 'time_range']
assert time_range_field.value == ''
assert time_range_field.options == [
('', True, '---------'),
('previous-year', False, 'Previous year'),
('current-year', False, 'Current year'),
('next-year', False, 'Next year'),
('previous-month', False, 'Previous month'),
('current-month', False, 'Current month'),
('next-month', False, 'Next month'),
('previous-week', False, 'Previous week'),
('current-week', False, 'Current week'),
('next-week', False, 'Next week'),
('range', False, 'Free range (date)'),
('range-template', False, 'Free range (template)'),
]
@with_httmock(new_api_mock)
def test_chartng_cell_manager_subfilters(app, admin_user, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='with-subfilter')
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
field_prefix = 'cdataviz_chartngcell-%s-' % cell.id
# choice with no subfilter
resp.form[field_prefix + 'form'] = 'contact'
manager_submit_cell(resp.form)
assert len(new_api_mock.call['requests']) == 1
assert 'menu' not in resp.form.fields
resp.form[field_prefix + 'form'] = 'error'
manager_submit_cell(resp.form)
assert len(new_api_mock.call['requests']) == 2
assert 'menu' not in resp.form.fields
# choice with subfilter
resp.form[field_prefix + 'form'] = 'food-request'
manager_submit_cell(resp.form)
assert len(new_api_mock.call['requests']) == 3
menu_field = resp.form[field_prefix + 'menu']
assert menu_field.value == ''
assert menu_field.options == [
('', True, '---------'),
('meat', False, 'Meat'),
('vegan', False, 'Vegan'),
]
resp.form[field_prefix + 'menu'] = 'meat'
manager_submit_cell(resp.form)
assert resp.form[field_prefix + 'menu'].value == 'meat'
cell.refresh_from_db()
assert cell.get_filter_params() == {'form': 'food-request', 'menu': 'meat'}
# choice with no subfilter
resp.form[field_prefix + 'form'] = 'contact'
manager_submit_cell(resp.form)
assert len(new_api_mock.call['requests']) == 4
assert 'menu' not in resp.form.fields
cell.refresh_from_db()
assert cell.get_filter_params() == {'form': 'contact'}
# changing another filter doesn't trigger request
resp.form[field_prefix + 'other'] = 'one'
resp = resp.form.submit()
assert len(new_api_mock.call['requests']) == 4
@with_httmock(new_api_mock)
@pytest.mark.freeze_time('2021-10-06')
def test_chartng_cell_manager_new_api_time_range_templates(app, admin_user, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
field_prefix = 'cdataviz_chartngcell-%s-' % cell.id
resp.form[field_prefix + 'time_range'] = 'range-template'
resp.form[field_prefix + 'time_range_start_template'] = 'today|add_days:"7"|adjust_to_week_monday'
resp.form[field_prefix + 'time_range_end_template'] = 'now|add_days:"14"|adjust_to_week_monday'
manager_submit_cell(resp.form)
cell.refresh_from_db()
assert cell.time_range == 'range-template'
assert cell.time_range_start_template == 'today|add_days:"7"|adjust_to_week_monday'
assert cell.time_range_end_template == 'now|add_days:"14"|adjust_to_week_monday'
resp.form[field_prefix + 'time_range_start_template'] = ''
resp.form[field_prefix + 'time_range_end_template'] = ''
manager_submit_cell(resp.form)
cell.refresh_from_db()
assert cell.time_range_start_template == ''
assert cell.time_range_end_template == ''
resp.form[field_prefix + 'time_range_start_template'] = 'xxx'
manager_submit_cell(resp.form, expect_errors=True)
cell.refresh_from_db()
assert cell.time_range_start_template == 'xxx'
resp = app.get('/manage/pages/%s/' % page.id)
resp.form[field_prefix + 'time_range_start_template'] = 'today|xxx'
manager_submit_cell(resp.form, expect_errors=True)
assert 'Invalid filter' in resp.text
resp = app.get('/manage/pages/%s/' % page.id)
resp.form[field_prefix + 'time_range_start_template'] = 'today|date:xxx'
manager_submit_cell(resp.form, expect_errors=True)
assert 'Failed lookup for key [xxx]' in resp.text
@with_httmock(new_api_mock)
def test_chartng_cell_manager_new_api_dynamic_fields(app, admin_user, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell.objects.create(page=page, order=1, placeholder='content')
statistic = Statistic.objects.get(slug='one-serie')
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
field_prefix = 'cdataviz_chartngcell-%s-' % cell.id
resp.form[field_prefix + 'statistic'] = statistic.pk
resp = app.post(resp.form.action, params=resp.form.submit_fields(), xhr=True)
assert 'time_interval' in resp.json['tabs']['general']
@with_httmock(new_api_mock)
def test_chartng_cell_manager_new_api_page_variables(app, admin_user, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
assert '<optgroup label="Page variables">' not in resp.text
page.extra_variables = {'foo': 'bar', 'bar_id': '{{ 40|add:2 }}'}
page.save()
resp = app.get('/manage/pages/%s/' % page.id)
assert '<optgroup label="Page variables">' in resp.text
field_prefix = 'cdataviz_chartngcell-%s-' % cell.id
assert resp.form[field_prefix + 'ou'].options == [
('', True, '---------'),
('default', False, 'Default OU'),
('other', False, 'Other OU'),
('variable:bar_id', False, 'bar_id'),
('variable:foo', False, 'foo'),
]
assert resp.form[field_prefix + 'service'].options == [
('', False, '---------'),
('chrono', True, 'Chrono'),
('combo', False, 'Combo'),
('variable:bar_id', False, 'bar_id'),
('variable:foo', False, 'foo'),
]
resp.form[field_prefix + 'ou'] = 'variable:foo'
manager_submit_cell(resp.form)
assert resp.form[field_prefix + 'ou'].value == 'variable:foo'
cell.refresh_from_db()
assert cell.filter_params['ou'] == 'variable:foo'
del page.extra_variables['foo']
page.save()
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.form[field_prefix + 'ou'].options == [
('', False, '---------'),
('default', False, 'Default OU'),
('other', False, 'Other OU'),
('variable:bar_id', False, 'bar_id'),
('variable:foo', True, 'foo (unavailable)'),
]
# no variables allowed for time_interval
time_interval_field = resp.form[field_prefix + 'time_interval']
assert [x[0] for x in time_interval_field.options] == ['day', 'month', 'year', 'week', 'weekday']
# no variables allowed for multiple choice field
cell.statistic = Statistic.objects.get(slug='filter-multiple')
cell.save()
resp = app.get('/manage/pages/%s/' % page.id)
color_field = resp.form[field_prefix + 'color']
assert [x[0] for x in color_field.options] == ['red', 'green', 'blue']
@with_httmock(bijoe_mock)
def test_table_cell(app, admin_user, statistics):
page = Page(title='One', slug='index')
page.save()
app = login(app)
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='example')
cell.chart_type = 'table'
cell.save()
location = '/api/dataviz/graph/%s/' % cell.id
resp = app.get(location)
assert resp.text.count('Total') == 1
cell.statistic = Statistic.objects.get(slug='second')
cell.save()
resp = app.get(location)
assert resp.text.count('Total') == 1
cell.statistic = Statistic.objects.get(slug='third')
cell.save()
resp = app.get(location)
assert '114' in resp.text
assert resp.text.count('Total') == 2
cell.statistic = Statistic.objects.get(slug='fourth')
cell.save()
resp = app.get(location)
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)
assert resp.text.count('Total') == 0
@with_httmock(new_api_mock)
def test_table_cell_new_api(app, admin_user, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.chart_type = 'table'
cell.save()
app = login(app)
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(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(location)
assert resp.text.count('Total') == 0
def test_dataviz_hourly_unavailable_statistic(statistics, nocache):
all_stats_count = Statistic.objects.count()
assert Statistic.objects.filter(available=True).count() == all_stats_count
def bijoe_mock_unavailable(url, request):
visualization_json = VISUALIZATION_JSON[2:]
return {'content': json.dumps(visualization_json), 'request': request, 'status_code': 200}
appconfig = apps.get_app_config('dataviz')
with HTTMock(bijoe_mock_unavailable):
appconfig.hourly()
assert Statistic.objects.filter(available=True).count() == all_stats_count - 2
def test_dataviz_import_cell():
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell.objects.create(page=page, order=1, slug='test', placeholder='content')
statistic = Statistic.objects.create(
slug='example', site_slug='plop', service_slug='bijoe', url='https://example.org'
)
cell.statistic = statistic
cell.save()
site_export = [page.get_serialized_page()]
cell.delete()
Page.load_serialized_pages(site_export)
cell = ChartNgCell.objects.get(slug='test')
assert cell.statistic.pk == statistic.pk
cell.delete()
statistic.delete()
Page.load_serialized_pages(site_export)
cell = ChartNgCell.objects.get(slug='test')
assert cell.statistic.slug == statistic.slug
assert cell.statistic.site_slug == statistic.site_slug
assert cell.statistic.service_slug == statistic.service_slug
@with_httmock(new_api_mock)
def test_dataviz_api_list_statistics(new_api_statistics, settings):
statistic = Statistic.objects.get(slug='one-serie')
assert statistic.label == 'One serie stat'
assert statistic.site_slug == 'connection'
assert statistic.service_slug == 'authentic'
assert statistic.site_title == 'Connection'
assert statistic.url == 'https://authentic.example.com/api/statistics/one-serie/'
assert statistic.available
# try with external url
statistics_count = Statistic.objects.count()
settings.STATISTICS_PROVIDERS.append(
{'url': 'https://stat.com/stats/', 'id': 'example', 'name': 'Example Provider'}
)
catalog = {'data': [{'url': 'https://stat.com/stats/1/', 'name': 'Test', 'id': 'test'}]}
@urlmatch(scheme='https', netloc=r'stat.com', path='/stats/')
def server_error(url, request):
return {'content': 'error', 'status_code': 500}
appconfig = apps.get_app_config('dataviz')
with HTTMock(server_error):
appconfig.hourly()
assert Statistic.objects.count() == statistics_count
@urlmatch(scheme='https', netloc=r'stat.com', path='/stats/')
def success(url, request):
return {'content': json.dumps(catalog), 'status_code': 200}
with HTTMock(success):
appconfig.hourly()
assert Statistic.objects.count() == statistics_count + 1
statistic = Statistic.objects.get(slug='test')
assert statistic.label == 'Test'
assert statistic.site_slug == 'example'
assert statistic.service_slug == 'example'
assert statistic.site_title == 'Example Provider'
assert statistic.url == 'https://stat.com/stats/1/'
assert statistic.available
settings.STATISTICS_PROVIDERS.append('unknown')
appconfig = apps.get_app_config('dataviz')
with HTTMock(success):
appconfig.hourly() # unknown provider is ignored
@with_httmock(new_api_mock)
@pytest.mark.parametrize('date', ['2020-03-02 12:01', '2020-03-05 12:01']) # Monday and Thursday
def test_chartng_cell_new_api_filter_params(app, new_api_statistics, nocache, freezer, date):
page = Page.objects.create(
title='One',
slug='index',
extra_variables={
'custom_date': '{{ "2021-02-03"|parse_date }}',
'not-a-date': 'not-a-date',
'syntax-error': '{% for %}',
},
)
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][0]
assert 'time_interval=' not in request.url
assert 'ou=' not in request.url
cell.filter_params = {'time_interval': 'month', 'ou': 'default', 'color': ['green', 'blue']}
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][1]
assert 'time_interval=month' in request.url
assert 'ou=default' in request.url
assert 'color=green&color=blue' in request.url
freezer.move_to(date)
cell.time_range = 'previous-year'
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][2]
assert 'time_interval=month' in request.url
assert 'ou=default' in request.url
assert 'start=2019-01-01' in request.url and 'end=2020-01-01' in request.url
cell.time_range = 'current-week'
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][-1]
assert 'start=2020-03-02' in request.url and 'end=2020-03-09' in request.url
cell.time_range = 'previous-week'
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][-1]
assert 'start=2020-02-24' in request.url and 'end=2020-03-02' in request.url
cell.time_range = 'next-week'
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][-1]
assert 'start=2020-03-09' in request.url and 'end=2020-03-16' in request.url
cell.time_range = 'range'
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][-1]
assert 'start' not in urllib.parse.parse_qs(urllib.parse.urlparse(request.url).query)
assert 'end' not in urllib.parse.parse_qs(urllib.parse.urlparse(request.url).query)
cell.time_range_start = '2020-10-01'
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][-1]
assert 'start=2020-10-01' in request.url
cell.time_range_end = '2020-11-03'
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][-1]
assert 'start=2020-10-01' in request.url and 'end=2020-11-03' in request.url
location = '/api/dataviz/graph/%s/' % cell.pk
cell.time_range = 'range-template'
cell.save()
app.get(location)
request = new_api_mock.call['requests'][-1]
assert 'start' not in urllib.parse.parse_qs(urllib.parse.urlparse(request.url).query)
assert 'end' not in urllib.parse.parse_qs(urllib.parse.urlparse(request.url).query)
cell.time_range_start_template = 'today|add_days:"7"|adjust_to_week_monday'
cell.save()
app.get(location)
request = new_api_mock.call['requests'][-1]
assert 'start=2020-03-09' in request.url
assert 'end' not in urllib.parse.parse_qs(urllib.parse.urlparse(request.url).query)
cell.time_range_end_template = 'today|add_days:"14"|adjust_to_week_monday'
cell.save()
app.get(location)
request = new_api_mock.call['requests'][-1]
assert 'start=2020-03-09' in request.url and 'end=2020-03-16' in request.url
cell.time_range_start_template = 'xxx'
cell.save()
app.get(location)
request = new_api_mock.call['requests'][-1]
assert 'start' not in request.url
# use page variables
cell.time_range_start_template = 'custom_date'
cell.save()
app.get(location)
request = new_api_mock.call['requests'][-1]
assert 'start=2021-02-03' in request.url
cell.time_range_start_template = 'not-a-date'
cell.save()
app.get(location)
request = new_api_mock.call['requests'][-1]
assert 'start' not in request.url
cell.time_range_start_template = 'syntax-error'
cell.save()
app.get(location)
request = new_api_mock.call['requests'][-1]
assert 'start' not in request.url
@with_httmock(new_api_mock)
def test_chartng_cell_new_api_filter_params_month(new_api_statistics, nocache, freezer):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
cell.filter_params = {'time_interval': 'month', 'ou': 'default'}
cell.time_range = 'current-month'
cell.save()
freezer.move_to('2021-01-02')
cell.get_chart()
request = new_api_mock.call['requests'][0]
assert 'start=2021-01-01' in request.url and 'end=2021-02-01' in request.url
cell.time_range = 'previous-month'
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][1]
assert 'start=2020-12-01' in request.url and 'end=2021-01-01' in request.url
freezer.move_to('2021-11-02')
cell.time_range = 'next-month'
cell.save()
cell.get_chart()
request = new_api_mock.call['requests'][2]
assert 'start=2021-12-01' in request.url and 'end=2022-01-01' in request.url
@with_httmock(new_api_mock)
def test_chartng_cell_new_api_filter_params_page_variables(app, admin_user, new_api_statistics, nocache):
Page.objects.create(title='One', slug='index')
page = Page.objects.create(
title='One',
slug='cards',
sub_slug='card_id',
extra_variables={
'foo': 'bar',
'bar_id': '{{ 40|add:2 }}',
'syntax_error': '{% for %}',
'subslug_dependant': '{{ 40|add:card_id }}',
},
)
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.filter_params = {'service': 'chrono', 'ou': 'variable:foo'}
cell.save()
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
cell.filter_params = {'service': 'chrono', 'ou': 'variable:bar_id'}
cell.save()
app.get(location)
request = new_api_mock.call['requests'][1]
assert 'service=chrono' in request.url
assert 'ou=42' in request.url
# unknown variable
cell.filter_params = {'service': 'chrono', 'ou': 'variable:unknown'}
cell.save()
resp = app.get(location)
assert len(new_api_mock.call['requests']) == 2
assert 'Page variable not found.' in resp.text
# variable with invalid syntax
cell.filter_params = {'service': 'chrono', 'ou': 'variable:syntax_error'}
cell.save()
resp = app.get(location)
assert len(new_api_mock.call['requests']) == 2
assert 'Syntax error in page variable.' in resp.text
# variable with missing context
cell.filter_params = {'service': 'chrono', 'ou': 'variable:subslug_dependant'}
cell.save()
resp = app.get(location)
assert len(new_api_mock.call['requests']) == 2
assert 'Cannot evaluate page variable.' in resp.text
# simulate call from page view
app = login(app)
resp = app.get('/cards/2/')
ctx = resp.pyquery('.chartngcell').attr('data-extra-context')
app.get(location + '?ctx=%s' % ctx)
request = new_api_mock.call['requests'][2]
assert 'service=chrono' in request.url
assert 'ou=42' in request.url
# reste à tester missing variable
# et avec display table
@with_httmock(new_api_mock)
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',
slug='cards',
sub_slug='card_id',
extra_variables={
'foo': 'bar',
'syntax_error': '{% for %}',
'subslug_dependant': '{{ 40|add:card_id }}',
},
)
cell = ChartNgCell(page=page, order=1, placeholder='content', chart_type='table')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.filter_params = {'service': 'chrono', 'ou': 'variable:foo'}
cell.save()
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
# unknown variable
cell.filter_params = {'service': 'chrono', 'ou': 'variable:unknown'}
cell.save()
resp = app.get(location)
assert len(new_api_mock.call['requests']) == 1
assert 'Page variable not found.' in resp.text
# variable with invalid syntax
cell.filter_params = {'service': 'chrono', 'ou': 'variable:syntax_error'}
cell.save()
resp = app.get(location)
assert len(new_api_mock.call['requests']) == 1
assert 'Syntax error in page variable.' in resp.text
# variable with missing context
cell.filter_params = {'service': 'chrono', 'ou': 'variable:subslug_dependant'}
cell.save()
resp = app.get(location)
assert len(new_api_mock.call['requests']) == 1
assert 'Cannot evaluate page variable.' in resp.text
def test_dataviz_check_validity(nocache):
page = Page.objects.create(title='One', slug='index')
stat = Statistic.objects.create(url='https://stat.com/stats/1/')
cell = ChartNgCell.objects.create(page=page, order=1, placeholder='content', statistic=stat)
@urlmatch(scheme='https', netloc=r'stat.com', path='/stats/1/')
def url_mock(url, request):
return {'content': json.dumps({'data': [], 'err': 0}), 'status_code': 200}
with HTTMock(url_mock):
cell.check_validity()
assert ValidityInfo.objects.exists() is False
@urlmatch(scheme='https', netloc=r'stat.com', path='/stats/1/')
def url_mock2(url, request):
return {'content': json.dumps({'data': [], 'err': 1}), 'status_code': 404}
with HTTMock(url_mock2):
cell.check_validity()
validity_info = ValidityInfo.objects.latest('pk')
assert validity_info.invalid_reason_code == 'statistic_data_not_found'
stat.url = ''
stat.save()
cell.check_validity()
validity_info = ValidityInfo.objects.latest('pk')
assert validity_info.invalid_reason_code == 'missing_statistic_url'
@with_httmock(new_api_mock)
def test_chartng_cell_new_api_aggregation(new_api_statistics, app, admin_user, nocache):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='daily')
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
time_interval_field = resp.form['cdataviz_chartngcell-%s-time_interval' % cell.id]
assert time_interval_field.value == 'day'
assert time_interval_field.options == [
('day', True, 'Day'),
('week', False, 'Week'),
('month', False, 'Month'),
('year', False, 'Year'),
('weekday', False, 'Week day'),
]
manager_submit_cell(resp.form)
cell.refresh_from_db()
chart = cell.get_chart()
assert len(chart.x_labels) == 484
assert chart.x_labels[:3] == ['06 Oct 2020', '07 Oct 2020', '08 Oct 2020']
assert chart.x_labels[-3:] == ['30 Jan 2022', '31 Jan 2022', '01 Feb 2022']
assert chart.raw_series[0][0][:8] == [0, 0, 0, 0, 0, 0, 0, 1]
assert chart.raw_series[1][0][:8] == [2, 0, 0, 0, 0, 0, 0, 2]
time_interval_field = resp.form['cdataviz_chartngcell-%s-time_interval' % cell.id]
time_interval_field.value = 'month'
manager_submit_cell(resp.form)
time_interval_field = resp.form['cdataviz_chartngcell-%s-time_interval' % cell.id]
assert time_interval_field.options == [
('day', False, 'Day'),
('week', False, 'Week'),
('month', True, 'Month'), # month choice is selected
('year', False, 'Year'),
('weekday', False, 'Week day'),
]
cell.refresh_from_db()
chart = cell.get_chart()
assert 'time_interval=day' in new_api_mock.call['requests'][1].url
assert len(chart.x_labels) == 17
assert chart.x_labels[:3] == ['Oct 2020', 'Nov 2020', 'Dec 2020']
assert chart.x_labels[-3:] == ['Dec 2021', 'Jan 2022', 'Feb 2022']
assert chart.raw_series == [
([1, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2], {'title': 'Serie 1'}),
([4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], {'title': 'Serie 2'}),
]
time_interval_field = resp.form['cdataviz_chartngcell-%s-time_interval' % cell.id]
time_interval_field.value = 'year'
resp.form.submit()
cell.refresh_from_db()
chart = cell.get_chart()
assert 'time_interval=day' in new_api_mock.call['requests'][2].url
assert chart.x_labels == ['2020', '2021', '2022']
assert chart.raw_series == [
([17, 0, 2], {'title': 'Serie 1'}),
([5, 0, 0], {'title': 'Serie 2'}),
]
time_interval_field = resp.form['cdataviz_chartngcell-%s-time_interval' % cell.id]
time_interval_field.value = 'weekday'
manager_submit_cell(resp.form)
cell.refresh_from_db()
chart = cell.get_chart()
assert 'time_interval=day' in new_api_mock.call['requests'][3].url
assert chart.x_labels == ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
assert chart.raw_series == [
([16, 3, 0, 0, 0, 0, 0], {'title': 'Serie 1'}),
([1, 4, 0, 0, 0, 0, 0], {'title': 'Serie 2'}),
]
time_interval_field = resp.form['cdataviz_chartngcell-%s-time_interval' % cell.id]
time_interval_field.value = 'week'
manager_submit_cell(resp.form)
cell.refresh_from_db()
chart = cell.get_chart()
assert 'time_interval=day' in new_api_mock.call['requests'][1].url
assert len(chart.x_labels) == 70
assert chart.x_labels[:3] == ['W41-2020', 'W42-2020', 'W43-2020']
assert chart.x_labels[-6:] == ['W52-2021', 'W1-2022', 'W2-2022', 'W3-2022', 'W4-2022', 'W5-2022']
assert chart.raw_series == [
([0, 1, 0, 0, 0, 0, 0, 0, 16] + [0] * 60 + [2], {'title': 'Serie 1'}),
([2, 2, 0, 0, 0, 0, 0, 0, 1] + [0] * 61, {'title': 'Serie 2'}),
]
cell.statistic = Statistic.objects.get(slug='leap-week')
cell.save()
manager_submit_cell(resp.form)
chart = cell.get_chart()
assert 'time_interval=day' in new_api_mock.call['requests'][1].url
assert len(chart.x_labels) == 1
assert chart.x_labels == ['W53-2020']
assert chart.raw_series == [([19], {'title': 'Serie 1'})]
@with_httmock(new_api_mock)
def test_chartng_cell_new_api_month_translation(app, admin_user, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
# populate filter params
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
resp.form.submit()
cell.refresh_from_db()
chart = cell.get_chart()
assert chart.x_labels == ['Oct 2020', 'Nov 2020', 'Dec 2020']
@with_httmock(new_api_mock)
def test_chart_filters_cell(new_api_statistics, app, admin_user, nocache):
page = Page.objects.create(title='One', slug='index')
ChartFiltersCell.objects.create(page=page, order=1, placeholder='content')
app = login(app)
resp = app.get('/')
assert 'No filters are available' in resp.text
# add unconfigured chart
first_cell = ChartNgCell.objects.create(page=page, order=2, placeholder='content')
resp = app.get('/')
assert 'No filters are available' in resp.text
# add statistics to chart
first_cell.statistic = Statistic.objects.get(slug='one-serie')
first_cell.save()
resp = app.get('/')
assert len(resp.form.fields) == 7
assert 'time_range_start' in resp.form.fields
assert 'time_range_end' in resp.form.fields
time_range_field = resp.form['time_range']
assert time_range_field.value == ''
assert time_range_field.options == [
('', True, '---------'),
('previous-year', False, 'Previous year'),
('current-year', False, 'Current year'),
('previous-month', False, 'Previous month'),
('current-month', False, 'Current month'),
('previous-week', False, 'Previous week'),
('current-week', False, 'Current week'),
('range', False, 'Free range (date)'),
]
time_interval_field = resp.form['time_interval']
assert time_interval_field.value == 'month'
assert time_interval_field.options == [
('day', False, 'Day'),
('month', True, 'Month'),
('year', False, 'Year'),
('week', False, 'Week'),
('weekday', False, 'Week day'),
]
service_field = resp.form['service']
assert service_field.value == 'chrono'
assert service_field.options == [
('', False, '---------'),
('chrono', True, 'Chrono'),
('combo', False, 'Combo'),
]
ou_field = resp.form['ou']
assert ou_field.value == ''
assert ou_field.options == [
('', True, '---------'),
('default', False, 'Default OU'),
('other', False, 'Other OU'),
]
# adding new cell with same statistics changes nothing
cell = ChartNgCell(page=page, order=3, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
old_resp = resp
resp = app.get('/')
for field in ('time_range', 'time_interval', 'service', 'ou'):
assert resp.form[field].options == old_resp.form[field].options
# changing one filter value makes it disappear
cell.filter_params = {'ou': 'default'}
cell.save()
resp = app.get('/')
assert 'ou' not in resp.form.fields
for field in ('time_range', 'time_interval', 'service'):
assert resp.form[field].options == old_resp.form[field].options
# setting the same value for the other cell makes it appear again
first_cell.filter_params = {'ou': 'default'}
first_cell.save()
resp = app.get('/')
assert resp.form['ou'].value == 'default'
# changing statistics type of cell remove some fields
cell.statistic = Statistic.objects.get(slug='daily')
cell.save()
resp = app.get('/')
assert 'ou' not in resp.form.fields
assert 'service' not in resp.form.fields
for field in ('time_range', 'time_interval'):
assert resp.form[field].options == old_resp.form[field].options
# changing time_interval value makes interval fields disappear
cell.time_range = 'previous-year'
cell.save()
old_resp = resp
resp = app.get('/')
assert 'time_range' not in resp.form.fields
assert 'time_range_start' not in resp.form.fields
assert 'time_range_end' not in resp.form.fields
assert resp.form['time_interval'].options == old_resp.form['time_interval'].options
# setting the same value for the other cell makes it appear again
first_cell.time_range = 'previous-year'
first_cell.save()
resp = app.get('/')
assert resp.form['time_range'].value == 'previous-year'
assert resp.form['time_interval'].options == old_resp.form['time_interval'].options
# only common choices are shown
first_cell.statistic.filters[0]['options'].remove({'id': 'day', 'label': 'Day'})
first_cell.statistic.save()
resp = app.get('/')
assert resp.form['time_interval'].options == [
('month', True, 'Month'),
('year', False, 'Year'),
]
# if no common choices exist, field is removed
first_cell.statistic.filters[0]['options'] = [{'id': 'random', 'label': 'Random'}]
first_cell.statistic.save()
resp = app.get('/')
assert 'time_interval' not in resp.form.fields
assert resp.form['time_range'].value == 'previous-year'
# form is not shown if no common filters exist
first_cell.time_range = 'current-year'
first_cell.save()
resp = app.get('/')
assert 'No filters are available' in resp.text
@with_httmock(new_api_mock)
def test_chart_filters_cell_future_data(app, admin_user, new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='with-future-data')
cell.save()
ChartFiltersCell.objects.create(page=page, order=2, placeholder='content')
app = login(app)
resp = app.get('/')
time_range_field = resp.form['time_range']
assert time_range_field.value == ''
assert time_range_field.options == [
('', True, '---------'),
('previous-year', False, 'Previous year'),
('current-year', False, 'Current year'),
('next-year', False, 'Next year'),
('previous-month', False, 'Previous month'),
('current-month', False, 'Current month'),
('next-month', False, 'Next month'),
('previous-week', False, 'Previous week'),
('current-week', False, 'Current week'),
('next-week', False, 'Next week'),
('range', False, 'Free range (date)'),
]
# adding cell without future data makes choice disappear
cell = ChartNgCell(page=page, order=3, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
resp = app.get('/')
time_range_field = resp.form['time_range']
assert time_range_field.value == ''
assert time_range_field.options == [
('', True, '---------'),
('previous-year', False, 'Previous year'),
('current-year', False, 'Current year'),
('previous-month', False, 'Previous month'),
('current-month', False, 'Current month'),
('previous-week', False, 'Previous week'),
('current-week', False, 'Current week'),
('range', False, 'Free range (date)'),
]
@with_httmock(new_api_mock)
def test_chart_filters_cell_with_subfilters(new_api_statistics, app, admin_user, nocache):
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 'form' in resp.form.fields
assert 'menu' not in resp.form.fields
# select a choice with subfilters in manager
resp = app.get('/manage/pages/%s/' % page.id)
resp.forms[1]['cdataviz_chartngcell-%s-form' % cell.id] = 'food-request'
manager_submit_cell(resp.forms[1])
resp = app.get('/')
assert 'form' in resp.form.fields
assert 'menu' in resp.form.fields
@with_httmock(new_api_mock)
@pytest.mark.freeze_time('2021-10-06')
def test_chartng_cell_api_view_get_parameters(app, normal_user, new_api_statistics, nocache):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
location = '/api/dataviz/graph/%s/' % cell.id
app.get(location)
request = new_api_mock.call['requests'][0]
assert 'time_interval=' not in request.url
assert 'ou=' not in request.url
cell.filter_params = {'time_interval': 'month', 'ou': 'default'}
cell.save()
app.get(location)
request = new_api_mock.call['requests'][1]
assert 'time_interval=month' in request.url
assert 'ou=default' in request.url
app.get(location + '?time_interval=year')
request = new_api_mock.call['requests'][2]
assert 'time_interval=year' in request.url
assert 'ou=default' in request.url
cell.time_range = 'range'
cell.time_range_start = '2022-01-01'
cell.save()
app.get(location)
request = new_api_mock.call['requests'][3]
assert 'start=2022-01-01' in request.url
assert 'ou=default' in request.url
app.get(location + '?time_range=current-month')
request = new_api_mock.call['requests'][4]
assert 'start=2021-10-01' in request.url
assert 'end=2021-11-01' in request.url
assert 'ou=default' in request.url
cell.filter_params.clear()
cell.statistic = Statistic.objects.get(slug='filter-multiple')
cell.save()
app.get(location + '?color=green&color=blue')
request = new_api_mock.call['requests'][5]
assert 'color=green&color=blue' in request.url
cell.filter_params.clear()
cell.statistic = Statistic.objects.get(slug='with-subfilter')
cell.filter_params = {'form': 'food-request'}
cell.save()
cell.update_subfilters()
app.get(location + '?menu=vegan')
request = new_api_mock.call['requests'][7]
assert 'menu=vegan' in request.url
# unknown params
app.get(location + '?time_interval=month&ou=default')
request = new_api_mock.call['requests'][8]
assert 'time_interval=' not in request.url
assert 'ou=' not in request.url
# wrong params
resp = app.get(location + '?time_range_start=xxx')
assert 'Wrong parameters' in resp.text
assert len(new_api_mock.call['requests']) == 9
@with_httmock(new_api_mock)
def test_spooler_refresh_statistics_data(new_api_statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='one-serie')
cell.save()
refresh_statistics_data(cell.pk)
assert len(new_api_mock.call['requests']) == 1
refresh_statistics_data(cell.pk)
assert len(new_api_mock.call['requests']) == 2
# variables cannot be evaluated in spooler
page.extra_variables = {'test': 'test'}
page.save()
cell.filter_params = {'ou': 'variable:test'}
cell.save()
refresh_statistics_data(cell.pk)
assert len(new_api_mock.call['requests']) == 2
ChartNgCell.objects.all().delete()
refresh_statistics_data(cell.pk)
assert len(new_api_mock.call['requests']) == 2
@with_httmock(bijoe_mock)
def test_spooler_refresh_statistics_data_bijoe(statistics):
page = Page.objects.create(title='One', slug='index')
cell = ChartNgCell(page=page, order=1, placeholder='content')
cell.statistic = Statistic.objects.get(slug='example')
cell.save()
refresh_statistics_data(cell.pk)
assert len(bijoe_mock.call['requests']) == 1