combo/tests/test_dataviz.py

1560 lines
52 KiB
Python

import json
import urllib.parse
from datetime import date, timedelta
import mock
import pytest
from django.apps import apps
from django.contrib.auth.models import Group, User
from django.db import connection
from django.db.migrations.executor import MigrationExecutor
from django.test import override_settings
from django.utils import timezone
from httmock import HTTMock, remember_called, urlmatch, with_httmock
from requests.exceptions import HTTPError
from combo.apps.dataviz.models import ChartNgCell, Gauge, Statistic, UnsupportedDataSet
from combo.data.models import Page, ValidityInfo
from .test_public import login, normal_user
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',
},
]
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}
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"},
],
},
],
},
{
'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,
}
],
},
]
}
@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}
@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': u'web'}),
([134], {'title': u'mail'}),
([53], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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': u'foo'}),
([35.5, 46, 100, 38.4], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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()
@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': u'web'}),
([134], {'title': u'mail'}),
([53], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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': u'email'}),
([134], {'title': u'mail'}),
([222], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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(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': u'web'}),
([134], {'title': u'mail'}),
([53], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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': u'foo'}),
([122, 114, 2, 33], {'title': u'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('/')
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
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) # get data in cache
resp = app.get('/')
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) # get data in cache
resp = app.get('/')
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
# 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):
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) # populate cache
resp = app.get('/')
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
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
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)
assert statistics_field.options[4][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'),
]
resp.form[field_prefix + 'ou'] = 'default'
resp = resp.form.submit().follow()
assert resp.form[field_prefix + 'ou'].value == 'default'
cell.refresh_from_db()
assert cell.filter_params == {'ou': 'default', 'time_interval': 'month'}
resp.form[field_prefix + 'ou'] = ''
resp = resp.form.submit().follow()
assert resp.form[field_prefix + 'ou'].value == ''
cell.refresh_from_db()
assert cell.filter_params == {'time_interval': 'month'}
resp.form[field_prefix + 'time_range'] = 'previous-year'
resp = resp.form.submit().follow()
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'
resp = resp.form.submit().follow()
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'] = ''
resp = resp.form.submit().follow()
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
resp = resp.form.submit().follow()
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 == ''
@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.text
@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)
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
@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)
resp = app.get('/')
assert resp.text.count('Total') == 1
cell.statistic = Statistic.objects.get(slug='two-series')
cell.save()
resp = app.get('/')
assert '21' in resp.text
assert resp.text.count('Total') == 2
cell.statistic = Statistic.objects.get(slug='no-data')
cell.save()
resp = app.get('/')
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(new_api_statistics, nocache, freezer, date):
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()
chart = 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'}
cell.save()
chart = cell.get_chart()
request = new_api_mock.call['requests'][1]
assert 'time_interval=month' in request.url
assert 'ou=default' in request.url
freezer.move_to(date)
cell.time_range = 'previous-year'
cell.save()
chart = 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()
chart = 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()
chart = 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()
chart = 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()
chart = 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()
chart = 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()
chart = 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
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_mock(url, request):
return {'content': json.dumps({'data': [], 'err': 1}), 'status_code': 404}
with HTTMock(url_mock):
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'),
]
resp.form.submit()
cell.refresh_from_db()
chart = cell.get_chart()
assert len(chart.x_labels) == 484
assert chart.x_labels[:3] == ['06-10-2020', '07-10-2020', '08-10-2020']
assert chart.x_labels[-3:] == ['30-01-2022', '31-01-2022', '01-02-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.value = '_month'
resp.form.submit()
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] == ['10-2020', '11-2020', '12-2020']
assert chart.x_labels[-3:] == ['12-2021', '01-2022', '02-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.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.value = '_weekday'
resp.form.submit()
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.value = '_week'
resp.form.submit()
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()
resp = app.get('/manage/pages/%s/' % page.id)
resp.form.submit()
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'})]