combo/tests/test_dataviz.py

1267 lines
43 KiB
Python

import json
import urllib.parse
import mock
import pytest
from datetime import timedelta, date
from httmock import HTTMock, with_httmock, remember_called, urlmatch
from requests.exceptions import HTTPError
from django.apps import apps
from django.contrib.auth.models import User, Group
from django.db import connection
from django.db.migrations.executor import MigrationExecutor
from django.test import override_settings
from django.utils import timezone
from combo.data.models import Page, ValidityInfo
from combo.apps.dataviz.models import Gauge, ChartNgCell, UnsupportedDataSet, Statistic
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',
},
]
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}
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',
},
]
}
@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}
@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'}),
]
# 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()
@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'})]
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'}),
]
@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'}),
]
@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('/')
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
@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[3][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'),
]
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)
def test_chartng_cell_new_api_filter_params(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()
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': 'day', 'ou': 'default'}
cell.save()
chart = cell.get_chart()
request = new_api_mock.call['requests'][1]
assert 'time_interval=day' in request.url
assert 'ou=default' in request.url
freezer.move_to('2020-03-02 12:01')
cell.time_range = 'previous-year'
cell.save()
chart = cell.get_chart()
request = new_api_mock.call['requests'][2]
assert 'time_interval=day' 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 = 'range'
cell.save()
chart = cell.get_chart()
request = new_api_mock.call['requests'][3]
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'][4]
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'][5]
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'