dataviz: add week filters (#55417)
This commit is contained in:
parent
dbc0fee3ad
commit
aac7f4c63a
|
@ -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')),
|
||||
|
|
|
@ -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.',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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)',
|
||||
),
|
||||
|
|
|
@ -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)):
|
||||
|
|
|
@ -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'})]
|
||||
|
|
Loading…
Reference in New Issue