From 47e33d42311b8951ba290047b7ab36b5eef361a4 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Tue, 6 Jul 2021 16:24:49 +0200 Subject: [PATCH] dataviz: add week filters (#55417) --- combo/apps/dataviz/forms.py | 1 + .../migrations/0012_auto_20201126_1557.py | 1 + .../migrations/0016_auto_20201215_1624.py | 10 +-- combo/apps/dataviz/models.py | 73 ++++++++++++++----- tests/test_dataviz.py | 45 ++++++++++-- 5 files changed, 99 insertions(+), 31 deletions(-) diff --git a/combo/apps/dataviz/forms.py b/combo/apps/dataviz/forms.py index 28ab0542..e312665e 100644 --- a/combo/apps/dataviz/forms.py +++ b/combo/apps/dataviz/forms.py @@ -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')), diff --git a/combo/apps/dataviz/migrations/0012_auto_20201126_1557.py b/combo/apps/dataviz/migrations/0012_auto_20201126_1557.py index 08745c73..1c8067a3 100644 --- a/combo/apps/dataviz/migrations/0012_auto_20201126_1557.py +++ b/combo/apps/dataviz/migrations/0012_auto_20201126_1557.py @@ -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.', ), ), ] diff --git a/combo/apps/dataviz/migrations/0016_auto_20201215_1624.py b/combo/apps/dataviz/migrations/0016_auto_20201215_1624.py index 5377f2d1..b0a0555f 100644 --- a/combo/apps/dataviz/migrations/0016_auto_20201215_1624.py +++ b/combo/apps/dataviz/migrations/0016_auto_20201215_1624.py @@ -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)', ), diff --git a/combo/apps/dataviz/models.py b/combo/apps/dataviz/models.py index 3b98e79a..44f58657 100644 --- a/combo/apps/dataviz/models.py +++ b/combo/apps/dataviz/models.py @@ -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 Y 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-Y'), + '_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': + week_difference = ( + max_date.isocalendar()[1] - min_date.isocalendar()[1] + (max_date.year - min_date.year) * 53 + ) + x_labels = [ + format_date(min_date + relativedelta(weeks=i), date_formats['_week']) + for i in range(week_difference) + ] 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)): diff --git a/tests/test_dataviz.py b/tests/test_dataviz.py index 4099e342..8730c2b8 100644 --- a/tests/test_dataviz.py +++ b/tests/test_dataviz.py @@ -1120,6 +1120,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 +1347,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 +1366,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 +1375,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 +1455,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 +1507,17 @@ 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'}), + ] -- 2.20.1