dataviz: add week filters (#55417)

This commit is contained in:
Valentin Deniaud 2021-07-06 16:24:49 +02:00
parent dbc0fee3ad
commit aac7f4c63a
5 changed files with 135 additions and 31 deletions

View File

@ -56,6 +56,7 @@ def trigger_statistics_list_refresh():
class ChartNgForm(forms.ModelForm):
blank_choice = ('', '---------')
time_intervals = (
('_week', _('Week')),
('_month', _('Month')),
('_year', _('Year')),
('_weekday', _('Week day')),

View File

@ -46,6 +46,7 @@ class Migration(migrations.Migration):
related_name='cells',
to='dataviz.Statistic',
verbose_name='Data',
help_text='This list may take a few seconds to be updated, please refresh the page if an item is missing.',
),
),
]

View File

@ -4,6 +4,8 @@ from __future__ import unicode_literals
from django.db import migrations, models
from combo.apps.dataviz.models import TIME_FILTERS
class Migration(migrations.Migration):
@ -17,13 +19,7 @@ class Migration(migrations.Migration):
name='time_range',
field=models.CharField(
blank=True,
choices=[
('current-year', 'Current year'),
('previous-year', 'Previous year'),
('current-month', 'Current month'),
('previous-month', 'Previous month'),
('range', 'Free range'),
],
choices=TIME_FILTERS,
max_length=20,
verbose_name='Filtering (time)',
),

View File

@ -22,7 +22,7 @@ from datetime import date, datetime, timedelta
import pygal
import pygal.util
from dateutil.relativedelta import relativedelta
from dateutil.relativedelta import MO, relativedelta
from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.db import models, transaction
@ -149,16 +149,22 @@ class Statistic(models.Model):
return (self.slug, self.site_slug, self.service_slug)
TIME_FILTERS = (
('previous-year', _('Previous year')),
('current-year', _('Current year')),
('next-year', _('Next year')),
('previous-month', _('Previous month')),
('current-month', _('Current month')),
('next-month', _('Next month')),
('previous-week', _('Previous week')),
('current-week', _('Current week')),
('next-week', _('Next week')),
('range', _('Free range')),
)
@register_cell_class
class ChartNgCell(CellBase):
TIME_FILTERS = (
('current-year', _('Current year')),
('previous-year', _('Previous year')),
('current-month', _('Current month')),
('previous-month', _('Previous month')),
('range', _('Free range')),
)
statistic = models.ForeignKey(
verbose_name=_('Data'),
to=Statistic,
@ -176,13 +182,7 @@ class ChartNgCell(CellBase):
_('Filtering (time)'),
max_length=20,
blank=True,
choices=(
('current-year', _('Current year')),
('previous-year', _('Previous year')),
('current-month', _('Current month')),
('previous-month', _('Previous month')),
('range', _('Free range')),
),
choices=TIME_FILTERS,
)
time_range_start = models.DateField(_('From'), null=True, blank=True)
time_range_end = models.DateField(_('To'), null=True, blank=True)
@ -368,11 +368,26 @@ class ChartNgCell(CellBase):
elif self.time_range == 'previous-year':
params['start'] = date(year=now.year - 1, month=1, day=1)
params['end'] = date(year=now.year, month=1, day=1)
elif self.time_range == 'next-year':
params['start'] = date(year=now.year + 1, month=1, day=1)
params['end'] = date(year=now.year + 2, month=1, day=1)
elif self.time_range == 'current-month':
params['start'] = date(year=now.year, month=now.month, day=1)
elif self.time_range == 'previous-month':
params['start'] = date(year=now.year, month=now.month - 1, day=1)
params['end'] = date(year=now.year, month=now.month, day=1)
elif self.time_range == 'next-month':
params['start'] = date(year=now.year, month=now.month + 1, day=1)
params['end'] = date(year=now.year, month=now.month + 2, day=1)
elif self.time_range == 'current-week':
params['start'] = now + relativedelta(weekday=MO(-1))
params['end'] = now + relativedelta(weekday=MO(+1), days=+1)
elif self.time_range == 'previous-week':
params['start'] = now + relativedelta(weekday=MO(-2))
params['end'] = now + relativedelta(weekday=MO(-1))
elif self.time_range == 'next-week':
params['start'] = now + relativedelta(weekday=MO(+1), days=+1)
params['end'] = now + relativedelta(weekday=MO(+2), days=+1)
elif self.time_range == 'range':
if self.time_range_start:
params['start'] = self.time_range_start
@ -571,23 +586,43 @@ class ChartNgCell(CellBase):
dates = [datetime.strptime(label, '%Y-%m-%d') for label in data['x_labels']]
min_date, max_date = min(dates), max(dates)
date_formats = {
'day': 'd-m-Y',
# Translators: This indicates week number followed by year, for example it can yield W2-2021.
# First "W" is the first letter of the word "week" and should be translated accordingly, second
# "W" and "o" are interpreted by Django's date filter and should be left as is. First W is
# backslash escaped to prevent it from being interpreted, translators should refer to Django's
# documentation in order to know if the new letter resulting of translation should be escaped or not.
'_week': gettext('\WW-o'),
'_month': 'm-Y',
'_year': 'Y',
'_weekday': 'l',
}
if interval == 'day':
x_labels = [
(min_date + timedelta(days=i)).strftime('%d-%m-%Y')
format_date(min_date + timedelta(days=i), date_formats['day'])
for i in range((max_date - min_date).days + 1)
]
elif interval == '_month':
month_difference = max_date.month - min_date.month + (max_date.year - min_date.year) * 12
x_labels = [
(min_date + relativedelta(months=i)).strftime('%m-%Y') for i in range(month_difference + 1)
format_date(min_date + relativedelta(months=i), date_formats['_month'])
for i in range(month_difference + 1)
]
elif interval == '_year':
x_labels = [str(year) for year in range(min_date.year, max_date.year + 1)]
elif interval == '_weekday':
x_labels = [str(label) for label in WEEKDAYS.values()]
elif interval == '_week':
x_labels = []
date, last_date = min_date, max_date
if min_date.weekday() > max_date.weekday():
last_date += relativedelta(weeks=1)
while date <= last_date:
x_labels.append(format_date(date, date_formats['_week']))
date += relativedelta(weeks=1)
aggregates = OrderedDict((label, [0] * len(series_data)) for label in x_labels)
date_formats = {'day': 'd-m-Y', '_month': 'm-Y', '_year': 'Y', '_weekday': 'l'}
for i, date in enumerate(dates):
key = format_date(date, date_formats[interval])
for j in range(len(series_data)):

View File

@ -351,6 +351,22 @@ STATISTICS_LIST = {
}
],
},
{
'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,
}
],
},
]
}
@ -396,6 +412,16 @@ def new_api_mock(url, request):
},
}
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
@ -1120,6 +1146,7 @@ def test_chartng_cell_manager_new_api(app, admin_user, new_api_statistics):
('day', False, 'Day'),
('month', True, 'Month'),
('year', False, 'Year'),
('_week', False, 'Week'),
('_weekday', False, 'Week day'),
]
@ -1346,7 +1373,8 @@ def test_dataviz_api_list_statistics(new_api_statistics, settings):
@with_httmock(new_api_mock)
def test_chartng_cell_new_api_filter_params(new_api_statistics, nocache, freezer):
@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')
@ -1364,7 +1392,7 @@ def test_chartng_cell_new_api_filter_params(new_api_statistics, nocache, freezer
assert 'time_interval=month' in request.url
assert 'ou=default' in request.url
freezer.move_to('2020-03-02 12:01')
freezer.move_to(date)
cell.time_range = 'previous-year'
cell.save()
chart = cell.get_chart()
@ -1373,23 +1401,41 @@ def test_chartng_cell_new_api_filter_params(new_api_statistics, nocache, freezer
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'][3]
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'][4]
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'][5]
request = new_api_mock.call['requests'][-1]
assert 'start=2020-10-01' in request.url and 'end=2020-11-03' in request.url
@ -1435,6 +1481,7 @@ def test_chartng_cell_new_api_aggregation(new_api_statistics, app, admin_user, n
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'),
@ -1486,3 +1533,27 @@ def test_chartng_cell_new_api_aggregation(new_api_statistics, app, admin_user, n
([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'})]