1768 lines
66 KiB
Python
1768 lines
66 KiB
Python
import datetime
|
|
import json
|
|
import os
|
|
|
|
import pytest
|
|
from django.utils.timezone import make_aware
|
|
|
|
from wcs import fields
|
|
from wcs.backoffice.management import format_time
|
|
from wcs.blocks import BlockDef
|
|
from wcs.carddef import CardDef
|
|
from wcs.categories import CardDefCategory, Category
|
|
from wcs.formdef import FormDef
|
|
from wcs.qommon.http_request import HTTPRequest
|
|
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
|
|
|
|
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app
|
|
from .utils import sign_uri
|
|
|
|
|
|
def get_humanized_duration_serie(json_resp):
|
|
return [format_time(x) for x in json_resp['data']['series'][0]['data']]
|
|
|
|
|
|
@pytest.fixture
|
|
def pub():
|
|
pub = create_temporary_pub()
|
|
BlockDef.wipe()
|
|
Category.wipe()
|
|
FormDef.wipe()
|
|
Workflow.wipe()
|
|
CardDef.wipe()
|
|
|
|
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
|
|
pub.set_app_dir(req)
|
|
pub.cfg['identification'] = {'methods': ['password']}
|
|
pub.cfg['language'] = {'language': 'en'}
|
|
pub.write_cfg()
|
|
|
|
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
|
fd.write(
|
|
'''\
|
|
[api-secrets]
|
|
coucou = 1234
|
|
'''
|
|
)
|
|
|
|
return pub
|
|
|
|
|
|
@pytest.fixture
|
|
def formdef(pub):
|
|
workflow = Workflow(name='Workflow One')
|
|
new_status = workflow.add_status(name='New status')
|
|
workflow.add_status(name='End status')
|
|
middle_status1 = workflow.add_status(name='Middle status 1')
|
|
middle_status2 = workflow.add_status(name='Middle status 2')
|
|
just_submitted_status = workflow.add_status(name='Just submitted', id='just_submitted')
|
|
jump = new_status.add_action('jump', id='_jump')
|
|
jump.status = '2'
|
|
jump.timeout = 86400
|
|
jump = new_status.add_action('jump', id='_jump')
|
|
jump.status = '3'
|
|
jump = middle_status1.add_action('jump', id='_jump')
|
|
jump.status = '4'
|
|
jump = middle_status2.add_action('jump', id='_jump')
|
|
jump.status = '2'
|
|
jump = just_submitted_status.add_action('jump', id='_jump')
|
|
jump.status = '1'
|
|
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
|
workflow.backoffice_fields_formdef.fields = [
|
|
fields.BoolField(id='1', varname='checkbox', label='Checkbox', display_locations=['statistics']),
|
|
]
|
|
workflow.store()
|
|
|
|
block = BlockDef()
|
|
block.name = 'foobar'
|
|
block.fields = [
|
|
fields.BoolField(id='1', label='Bool', varname='bool', display_locations=['statistics']),
|
|
fields.ItemsField(
|
|
id='2',
|
|
varname='block-items',
|
|
label='Block items',
|
|
items=['Foo', 'Bar', 'Baz'],
|
|
anonymise='no',
|
|
display_locations=['statistics'],
|
|
),
|
|
]
|
|
block.store()
|
|
|
|
data_source = {
|
|
'type': 'jsonvalue',
|
|
'value': json.dumps(
|
|
[{'id': 'foo', 'text': 'Foo'}, {'id': 'bar', 'text': 'Bar'}, {'id': 'baz', 'text': 'Baz'}]
|
|
),
|
|
}
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.workflow_id = workflow.id
|
|
item_field = fields.ItemField(id='2', varname='test-item', label='Test item', data_source=data_source)
|
|
item_field.display_locations = ['statistics']
|
|
items_field = fields.ItemsField(
|
|
id='3',
|
|
varname='test-items',
|
|
label='Test items',
|
|
data_source=data_source,
|
|
anonymise='no',
|
|
)
|
|
items_field.display_locations = ['statistics']
|
|
block_field = fields.BlockField(
|
|
id='4', label='Block Data', varname='blockdata', block_slug='foobar', anonymise='no'
|
|
)
|
|
formdef.fields = [item_field, items_field, block_field]
|
|
formdef.store()
|
|
formdef.data_class().wipe()
|
|
return formdef
|
|
|
|
|
|
def teardown_module(module):
|
|
clean_temporary_pub()
|
|
|
|
|
|
def test_statistics_index(pub):
|
|
get_app(pub).get('/api/statistics/', status=403)
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/'))
|
|
assert resp.json['data'][0]['name'] == 'Forms Count'
|
|
assert resp.json['data'][0]['url'] == 'http://example.net/api/statistics/forms/count/'
|
|
|
|
|
|
def test_statistics_index_forms(pub):
|
|
formdef = FormDef()
|
|
formdef.name = 'test 1'
|
|
formdef.fields = []
|
|
formdef.store()
|
|
formdef.data_class().wipe()
|
|
|
|
formdef2 = FormDef()
|
|
formdef2.name = 'test 2'
|
|
formdef2.fields = []
|
|
formdef2.store()
|
|
formdef2.data_class().wipe()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/'))
|
|
form_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'form'][0]
|
|
assert form_filter['options'] == [
|
|
{'id': '_all', 'label': 'All Forms'},
|
|
{'id': 'test-1', 'label': 'test 1'},
|
|
{'id': 'test-2', 'label': 'test 2'},
|
|
]
|
|
|
|
category_a = Category(name='Category A')
|
|
category_a.store()
|
|
category_b = Category(name='Category B')
|
|
category_b.store()
|
|
formdef2.category_id = category_a.id
|
|
formdef2.store()
|
|
|
|
formdef3 = FormDef()
|
|
formdef3.name = 'test 3'
|
|
formdef3.category_id = category_b.id
|
|
formdef3.store()
|
|
formdef3.data_class().wipe()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/'))
|
|
form_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'form'][0]
|
|
assert form_filter['options'] == [
|
|
[None, [{'id': '_all', 'label': 'All Forms'}]],
|
|
[
|
|
'Category A',
|
|
[
|
|
{'id': 'category:category-a', 'label': 'All forms of category Category A'},
|
|
{'id': 'test-2', 'label': 'test 2'},
|
|
],
|
|
],
|
|
[
|
|
'Category B',
|
|
[
|
|
{'id': 'category:category-b', 'label': 'All forms of category Category B'},
|
|
{'id': 'test-3', 'label': 'test 3'},
|
|
],
|
|
],
|
|
['Misc', [{'id': 'test-1', 'label': 'test 1'}]],
|
|
]
|
|
|
|
# check Misc is not shown if all forms have categories
|
|
formdef.category_id = category_a.id
|
|
formdef.store()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/'))
|
|
form_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'form'][0]
|
|
assert form_filter['options'] == [
|
|
[None, [{'id': '_all', 'label': 'All Forms'}]],
|
|
[
|
|
'Category A',
|
|
[
|
|
{'id': 'category:category-a', 'label': 'All forms of category Category A'},
|
|
{'id': 'test-1', 'label': 'test 1'},
|
|
{'id': 'test-2', 'label': 'test 2'},
|
|
],
|
|
],
|
|
[
|
|
'Category B',
|
|
[
|
|
{'id': 'category:category-b', 'label': 'All forms of category Category B'},
|
|
{'id': 'test-3', 'label': 'test 3'},
|
|
],
|
|
],
|
|
]
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/'))
|
|
form_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'channel'][0]
|
|
assert form_filter['options'] == [
|
|
{'id': '_all', 'label': 'All'},
|
|
{'id': 'backoffice', 'label': 'Backoffice'},
|
|
{'id': 'mail', 'label': 'Mail'},
|
|
{'id': 'email', 'label': 'Email'},
|
|
{'id': 'phone', 'label': 'Phone'},
|
|
{'id': 'counter', 'label': 'Counter'},
|
|
{'id': 'fax', 'label': 'Fax'},
|
|
{'id': 'web', 'label': 'Web'},
|
|
{'id': 'social-network', 'label': 'Social Network'},
|
|
]
|
|
|
|
|
|
def test_statistics_index_cards(pub):
|
|
carddef = CardDef()
|
|
carddef.name = 'test 1'
|
|
carddef.fields = []
|
|
carddef.store()
|
|
carddef.data_class().wipe()
|
|
|
|
carddef2 = CardDef()
|
|
carddef2.name = 'test 2'
|
|
carddef2.fields = []
|
|
carddef2.store()
|
|
carddef2.data_class().wipe()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/'))
|
|
form_filter = [x for x in resp.json['data'][1]['filters'] if x['id'] == 'form'][0]
|
|
assert form_filter['options'] == [
|
|
{'id': 'test-1', 'label': 'test 1'},
|
|
{'id': 'test-2', 'label': 'test 2'},
|
|
]
|
|
|
|
category_a = CardDefCategory(name='Category A')
|
|
category_a.store()
|
|
category_b = CardDefCategory(name='Category B')
|
|
category_b.store()
|
|
carddef2.category_id = category_a.id
|
|
carddef2.store()
|
|
|
|
carddef3 = CardDef()
|
|
carddef3.name = 'test 3'
|
|
carddef3.category_id = category_b.id
|
|
carddef3.store()
|
|
carddef3.data_class().wipe()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/'))
|
|
form_filter = [x for x in resp.json['data'][1]['filters'] if x['id'] == 'form'][0]
|
|
assert form_filter['options'] == [
|
|
['Category A', [{'id': 'test-2', 'label': 'test 2'}]],
|
|
['Category B', [{'id': 'test-3', 'label': 'test 3'}]],
|
|
['Misc', [{'id': 'test-1', 'label': 'test 1'}]],
|
|
]
|
|
|
|
|
|
def test_statistics_index_resolution_time(pub):
|
|
formdef = FormDef()
|
|
formdef.name = 'test 1'
|
|
formdef.fields = []
|
|
formdef.store()
|
|
formdef.data_class().wipe()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/'))
|
|
resolution_time_stat = [x for x in resp.json['data'] if x['id'] == 'resolution_time'][0]
|
|
form_filter = [x for x in resolution_time_stat['filters'] if x['id'] == 'form'][0]
|
|
assert form_filter['options'] == [{'id': 'test-1', 'label': 'test 1'}]
|
|
|
|
|
|
def test_statistics_index_resolution_time_cards(pub):
|
|
carddef = CardDef()
|
|
carddef.name = 'test 1'
|
|
carddef.fields = []
|
|
carddef.store()
|
|
carddef.data_class().wipe()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/'))
|
|
resolution_time_stat = [x for x in resp.json['data'] if x['id'] == 'resolution_time_cards'][0]
|
|
card_filter = [x for x in resolution_time_stat['filters'] if x['id'] == 'form'][0]
|
|
assert card_filter['options'] == [{'id': 'test-1', 'label': 'test 1'}]
|
|
|
|
|
|
def test_statistics_forms_count(pub):
|
|
category_a = Category(name='Category A')
|
|
category_a.store()
|
|
category_b = Category(name='Category B')
|
|
category_b.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'test 1'
|
|
formdef.category_id = category_a.id
|
|
formdef.fields = []
|
|
formdef.store()
|
|
formdef.data_class().wipe()
|
|
|
|
formdef2 = FormDef()
|
|
formdef2.name = 'test 2'
|
|
formdef2.category_id = category_b.id
|
|
formdef2.fields = []
|
|
formdef2.store()
|
|
formdef2.data_class().wipe()
|
|
|
|
for i in range(20):
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
# "Web" channel has three equivalent values
|
|
if i == 0:
|
|
formdata.submission_channel = 'web'
|
|
elif i == 1:
|
|
formdata.submission_channel = ''
|
|
else:
|
|
formdata.submission_channel = None
|
|
formdata.backoffice_submission = bool(i % 3 == 0)
|
|
formdata.store()
|
|
|
|
for i in range(30):
|
|
formdata = formdef2.data_class()()
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
|
formdata.backoffice_submission = bool(i % 3)
|
|
formdata.submission_channel = 'mail'
|
|
formdata.store()
|
|
|
|
# draft should not be counted
|
|
formdata = formdef.data_class()()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
|
formdata.status = 'draft'
|
|
formdata.store()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/'))
|
|
assert resp.json['data']['series'] == [{'data': [20, 0, 30], 'label': 'Forms Count'}]
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?time_interval=year'))
|
|
assert resp.json['data']['series'] == [{'data': [50], 'label': 'Forms Count'}]
|
|
assert resp.json['data']['x_labels'] == ['2021']
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?time_interval=weekday'))
|
|
assert resp.json['data']['series'] == [{'data': [30, 0, 0, 0, 20, 0, 0], 'label': 'Forms Count'}]
|
|
assert resp.json['data']['x_labels'] == [
|
|
'Monday',
|
|
'Tuesday',
|
|
'Wednesday',
|
|
'Thursday',
|
|
'Friday',
|
|
'Saturday',
|
|
'Sunday',
|
|
]
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?time_interval=hour'))
|
|
assert resp.json['data']['series'] == [
|
|
{
|
|
'data': [20, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
'label': 'Forms Count',
|
|
}
|
|
]
|
|
assert resp.json['data']['x_labels'] == list(range(24))
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?time_interval=none'))
|
|
assert resp.json['data']['series'] == [{'data': [50], 'label': 'Forms Count'}]
|
|
assert resp.json['data']['x_labels'] == ['']
|
|
|
|
# time_interval=day is not supported
|
|
get_app(pub).get(sign_uri('/api/statistics/forms/count/?time_interval=day'), status=400)
|
|
|
|
# apply category filter through form parameter
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=category:category-a'))
|
|
assert resp.json['data']['series'] == [{'data': [20], 'label': 'Forms Count'}]
|
|
assert resp.json['data']['x_labels'] == ['2021-01']
|
|
|
|
# apply form filter
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name))
|
|
assert resp.json['data']['series'] == [{'data': [20], 'label': 'Forms Count'}]
|
|
assert resp.json['data']['x_labels'] == ['2021-01']
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % 'invalid'), status=404)
|
|
|
|
# apply period filter
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?end=2021-02-01'))
|
|
assert resp.json['data']['series'] == [{'data': [20], 'label': 'Forms Count'}]
|
|
assert resp.json['data']['x_labels'] == ['2021-01']
|
|
|
|
# apply channel filter
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?channel=mail'))
|
|
assert resp.json['data']['series'] == [{'data': [30], 'label': 'Forms Count'}]
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?channel=web'))
|
|
assert resp.json['data']['series'] == [{'data': [14], 'label': 'Forms Count'}]
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?channel=backoffice'))
|
|
assert resp.json['data']['series'] == [{'data': [6], 'label': 'Forms Count'}]
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?channel=_all'))
|
|
assert resp.json['data']['series'] == [{'data': [20, 0, 30], 'label': 'Forms Count'}]
|
|
|
|
|
|
def test_statistics_forms_count_subfilters(pub, formdef):
|
|
for i in range(2):
|
|
formdata = formdef.data_class()()
|
|
formdata.data['2'] = 'foo' if i % 2 else 'baz'
|
|
formdata.data['2_display'] = 'Foo' if i % 2 else 'Baz'
|
|
formdata.data['3'] = ['foo'] if i % 2 else ['bar', 'baz']
|
|
formdata.data['3_display'] = 'Foo' if i % 2 else 'Bar, Baz'
|
|
formdata.data['4'] = {'data': [{'2': ['foo', 'bar'], '2_display': 'Foo, Bar'}]}
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.store()
|
|
|
|
url = '/api/statistics/forms/count/?form=%s&time_interval=year' % formdef.url_name
|
|
resp = get_app(pub).get(sign_uri(url))
|
|
|
|
# check group-by subfilter
|
|
assert resp.json['data']['subfilters'][0] == {
|
|
'id': 'group-by',
|
|
'label': 'Group by',
|
|
'options': [
|
|
{'id': 'channel', 'label': 'Channel'},
|
|
{'id': 'simple-status', 'label': 'Simplified status'},
|
|
{'id': 'test-item', 'label': 'Test item'},
|
|
{'id': 'test-items', 'label': 'Test items'},
|
|
{'id': 'blockdata_bool', 'label': 'Bool'},
|
|
{'id': 'blockdata_block-items', 'label': 'Block items'},
|
|
{'id': 'checkbox', 'label': 'Checkbox'},
|
|
{'id': 'status', 'label': 'Status'},
|
|
],
|
|
'has_subfilters': True,
|
|
}
|
|
|
|
# check item field subfilter
|
|
assert resp.json['data']['subfilters'][1] == {
|
|
'id': 'filter-test-item',
|
|
'label': 'Test item',
|
|
'options': [{'id': 'baz', 'label': 'Baz'}, {'id': 'foo', 'label': 'Foo'}],
|
|
'required': False,
|
|
}
|
|
|
|
# check items field subfilter
|
|
assert resp.json['data']['subfilters'][2] == {
|
|
'id': 'filter-test-items',
|
|
'label': 'Test items',
|
|
'options': [
|
|
{'id': 'bar', 'label': 'Bar'},
|
|
{'id': 'baz', 'label': 'Baz'},
|
|
{'id': 'foo', 'label': 'Foo'},
|
|
],
|
|
'required': False,
|
|
}
|
|
|
|
# check block boolean field subfilter
|
|
assert resp.json['data']['subfilters'][3] == {
|
|
'id': 'filter-blockdata_bool',
|
|
'label': 'Bool',
|
|
'options': [{'id': 'true', 'label': 'Yes'}, {'id': 'false', 'label': 'No'}],
|
|
'required': False,
|
|
}
|
|
|
|
# check block items field subfilter
|
|
assert resp.json['data']['subfilters'][4] == {
|
|
'id': 'filter-blockdata_block-items',
|
|
'label': 'Block items',
|
|
'options': [
|
|
{'id': 'Foo', 'label': 'Foo'},
|
|
{'id': 'Bar', 'label': 'Bar'},
|
|
{'id': 'Baz', 'label': 'Baz'},
|
|
],
|
|
'required': False,
|
|
}
|
|
|
|
# check boolean backoffice field subfilter
|
|
assert resp.json['data']['subfilters'][5] == {
|
|
'id': 'filter-checkbox',
|
|
'label': 'Checkbox',
|
|
'options': [{'id': 'true', 'label': 'Yes'}, {'id': 'false', 'label': 'No'}],
|
|
'required': False,
|
|
}
|
|
|
|
# check status subfilter
|
|
assert resp.json['data']['subfilters'][-1] == {
|
|
'default': '_all',
|
|
'id': 'filter-status',
|
|
'label': 'Status',
|
|
'options': [
|
|
{'id': '_all', 'label': 'All'},
|
|
{'id': 'pending', 'label': 'Open'},
|
|
{'id': 'done', 'label': 'Done'},
|
|
{'id': '1', 'label': 'New status'},
|
|
{'id': '2', 'label': 'End status'},
|
|
],
|
|
'required': True,
|
|
}
|
|
|
|
# group by triggers new subfilter
|
|
new_resp = get_app(pub).get(sign_uri(url + '&group-by=test-item'))
|
|
assert new_resp.json['data']['subfilters'][1] == {
|
|
'id': 'hide_none_label',
|
|
'label': 'Ignore forms where "Test item" is empty.',
|
|
'options': [{'id': 'true', 'label': 'Yes'}, {'id': 'false', 'label': 'No'}],
|
|
'required': True,
|
|
'default': 'false',
|
|
}
|
|
assert len(new_resp.json['data']['subfilters']) == len(resp.json['data']['subfilters']) + 1
|
|
|
|
# month time_interval triggers new subfilter
|
|
new_resp = get_app(pub).get(sign_uri(url.replace('year', 'month')))
|
|
assert new_resp.json['data']['subfilters'][0] == {
|
|
'id': 'months_to_show',
|
|
'label': 'Number of months to show',
|
|
'options': [
|
|
{'id': '_all', 'label': 'All'},
|
|
{'id': '6', 'label': 'Last six months'},
|
|
{'id': '12', 'label': 'Last twelve months'},
|
|
],
|
|
'required': True,
|
|
'default': '_all',
|
|
}
|
|
assert len(new_resp.json['data']['subfilters']) == len(resp.json['data']['subfilters']) + 1
|
|
|
|
# add item field with datasource and no formdata, it should not appear
|
|
item_field = fields.ItemField(
|
|
id='20',
|
|
varname='test-item-no-formdata',
|
|
label='Test item no formdata',
|
|
data_source={
|
|
'type': 'jsonvalue',
|
|
'value': json.dumps(
|
|
[{'id': 'foo', 'text': 'Foo'}, {'id': 'bar', 'text': 'Bar'}, {'id': 'baz', 'text': 'Baz'}]
|
|
),
|
|
},
|
|
display_locations=['statistics'],
|
|
)
|
|
formdef.fields.append(item_field)
|
|
formdef.store()
|
|
new_resp = get_app(pub).get(sign_uri(url))
|
|
assert new_resp.json == resp.json
|
|
|
|
# add boolean field with no varname, it should not appear
|
|
bool_field = fields.BoolField(id='21', label='Checkbox', display_locations=['statistics'])
|
|
formdef.fields.append(bool_field)
|
|
formdef.store()
|
|
new_resp = get_app(pub).get(sign_uri(url))
|
|
assert new_resp.json == resp.json
|
|
|
|
# add boolean field with no display location, it should not appear
|
|
bool_field = fields.BoolField(
|
|
id='22', varname='checkbox', label='Checkbox', display_locations=['validation']
|
|
)
|
|
formdef.fields.append(bool_field)
|
|
formdef.store()
|
|
new_resp = get_app(pub).get(sign_uri(url))
|
|
assert new_resp.json == resp.json
|
|
|
|
# add not filterable field, it should not appear
|
|
formdef.fields.append(fields.StringField(id='23', varname='test string', label='Test'))
|
|
formdef.store()
|
|
new_resp = get_app(pub).get(sign_uri(url))
|
|
assert new_resp.json == resp.json
|
|
|
|
# remove fields and statuses
|
|
workflow = Workflow(name='Empty wf')
|
|
workflow.add_status('New')
|
|
workflow.store()
|
|
formdef.workflow = workflow
|
|
formdef.fields.clear()
|
|
formdef.store()
|
|
formdef.data_class().wipe()
|
|
|
|
resp = get_app(pub).get(sign_uri(url))
|
|
assert resp.json['data'] == {
|
|
'series': [{'data': [], 'label': 'Forms Count'}],
|
|
'x_labels': [],
|
|
'subfilters': [
|
|
{
|
|
'has_subfilters': True,
|
|
'id': 'group-by',
|
|
'label': 'Group by',
|
|
'options': [
|
|
{'id': 'channel', 'label': 'Channel'},
|
|
{'id': 'simple-status', 'label': 'Simplified status'},
|
|
{'id': 'status', 'label': 'Status'},
|
|
],
|
|
},
|
|
{
|
|
'default': '_all',
|
|
'id': 'filter-status',
|
|
'label': 'Status',
|
|
'options': [
|
|
{'id': '_all', 'label': 'All'},
|
|
{'id': 'pending', 'label': 'Open'},
|
|
{'id': 'done', 'label': 'Done'},
|
|
{'id': '1', 'label': 'New'},
|
|
],
|
|
'required': True,
|
|
},
|
|
],
|
|
}
|
|
|
|
|
|
def test_statistics_forms_count_subfilters_empty_block_items_field(pub, formdef):
|
|
carddef = CardDef()
|
|
carddef.name = 'foo'
|
|
carddef.store()
|
|
|
|
formdef.fields[2].block.fields[1].data_source = {'type': 'carddef:foo'}
|
|
formdef.fields[2].block.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.data['4'] = {'data': [{'1': 'a', '1_display': 'B'}]}
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.store()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name))
|
|
|
|
# check block items field subfilter
|
|
assert not any(x['id'] == 'filter-blockdata_block-items' for x in resp.json['data']['subfilters'])
|
|
|
|
|
|
def test_statistics_forms_count_subfilters_empty_item_field_no_datasource(pub, formdef):
|
|
formdef.workflow.backoffice_fields_formdef.fields.append(
|
|
fields.ItemField(
|
|
id='10',
|
|
varname='empty-item-field',
|
|
label='Empty item field',
|
|
anonymise='no',
|
|
display_locations=['statistics'],
|
|
),
|
|
)
|
|
formdef.workflow.store()
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.data['10'] = 'extra-option'
|
|
formdata.data['10_display'] = 'Extra option'
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.store()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name))
|
|
filter_dict = [x for x in resp.json['data']['subfilters'] if x['id'] == 'filter-empty-item-field'][0]
|
|
assert filter_dict['options'] == [{'id': 'extra-option', 'label': 'Extra option'}]
|
|
|
|
|
|
def test_statistics_forms_count_subfilters_query(pub, formdef):
|
|
for i in range(20):
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
if i % 3:
|
|
formdata.data['1'] = True
|
|
formdata.data['2'] = 'foo'
|
|
formdata.data['3'] = ['bar', 'baz']
|
|
formdata.data['4'] = {
|
|
'data': [
|
|
{'1': True, '2': ['baz'], '2_display': 'Baz'},
|
|
{'1': False, '2': ['foo'], '2_display': 'Foo'},
|
|
]
|
|
}
|
|
elif i % 2:
|
|
formdata.data['1'] = False
|
|
formdata.data['2'] = 'baz'
|
|
formdata.data['3'] = ['baz']
|
|
formdata.data['4'] = {'data': [{'1': False, '2': ['foo', 'bar'], '2_display': 'Foo, Bar'}]}
|
|
formdata.jump_status('2')
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.store()
|
|
|
|
# query all formdata
|
|
url = '/api/statistics/forms/count/?form=%s' % formdef.url_name
|
|
resp = get_app(pub).get(sign_uri(url))
|
|
assert resp.json['data']['series'][0]['data'][0] == 20
|
|
|
|
# filter on boolean field
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-checkbox=true'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 13
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-checkbox=false'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 3
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-checkbox='))
|
|
assert resp.json['data']['series'][0]['data'][0] == 20
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-checkbox=xxx'), status=400)
|
|
assert resp.text == 'Invalid value "xxx" for "filter-checkbox"'
|
|
|
|
# filter on item field
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=foo'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 13
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=baz'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 3
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=bar'))
|
|
assert resp.json['data']['series'][0]['data'] == []
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-item='))
|
|
assert resp.json['data']['series'][0]['data'][0] == 20
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=xxx'))
|
|
assert resp.json['data']['series'][0]['data'] == []
|
|
|
|
# filter on item field, with a carddef datasource
|
|
carddef = CardDef()
|
|
carddef.name = 'foo'
|
|
carddef.store()
|
|
|
|
formdef.fields[0].data_source = {'type': 'carddef:foo'}
|
|
formdef.store()
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=xxx'))
|
|
assert resp.json['data']['series'][0]['data'] == []
|
|
assert pub.loggederror_class.count() == 0
|
|
|
|
# filter on items field
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-items=foo'))
|
|
assert resp.json['data']['series'][0]['data'] == []
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-items=bar'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 13
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-items=baz'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 16
|
|
|
|
# filter on block boolean field
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-blockdata_bool=true'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 13
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-blockdata_bool=false'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 16
|
|
|
|
# filter on block items field
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-blockdata_block-items=foo'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 16
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-blockdata_block-items=bar'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 3
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-blockdata_block-items=baz'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 13
|
|
|
|
# filter on status
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=_all'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 20
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=1'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 17
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=pending'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 17
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=2'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 3
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=done'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 3
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status='))
|
|
assert resp.json['data']['series'][0]['data'][0] == 20
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=xxx'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 20
|
|
|
|
# invalid filter
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-xxx=yyy'))
|
|
assert resp.json['data']['series'][0]['data'] == []
|
|
|
|
|
|
def test_statistics_forms_count_subfilters_query_same_varname(pub, formdef):
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = [
|
|
fields.ItemField(id='1', varname='test', label='Test', items=['foo', 'bar']),
|
|
fields.ItemField(
|
|
id='2',
|
|
varname='test',
|
|
label='Test',
|
|
items=['foo', 'bar'],
|
|
display_locations=['statistics'],
|
|
),
|
|
]
|
|
formdef.store()
|
|
|
|
formdatas = []
|
|
for i in range(5):
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
if i == 0:
|
|
formdata.data['1'] = 'foo'
|
|
if i == 1:
|
|
formdata.data['1'] = 'bar'
|
|
formdata.data['2'] = 'foo'
|
|
formdata.store()
|
|
formdatas.append(formdata)
|
|
|
|
url = '/api/statistics/forms/count/?form=%s' % formdef.url_name
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test=foo'))
|
|
assert resp.json['data']['series'] == [{'data': [5], 'label': 'Forms Count'}]
|
|
|
|
formdef.fields[0].display_locations = ['statistics']
|
|
formdef.store()
|
|
for formdata in formdatas:
|
|
formdata.store() # refresh statistics_data column
|
|
|
|
# first non empty value is used : 4 are 'foo' and one is 'bar' hence 4 results
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test=foo'))
|
|
assert resp.json['data']['series'] == [{'data': [4], 'label': 'Forms Count'}]
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test=bar'))
|
|
assert resp.json['data']['series'] == [{'data': [1], 'label': 'Forms Count'}]
|
|
|
|
|
|
def test_statistics_forms_count_subfilters_query_integer_items(pub, formdef):
|
|
for i in range(10):
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
if i % 2:
|
|
formdata.data['3'] = ['1', '2']
|
|
else:
|
|
formdata.data['3'] = ['1']
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.store()
|
|
|
|
url = '/api/statistics/forms/count/?form=%s' % formdef.url_name
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-items=1'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 10
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-items=2'))
|
|
assert resp.json['data']['series'][0]['data'][0] == 5
|
|
|
|
|
|
@pytest.mark.parametrize('anonymise', [False, True])
|
|
def test_statistics_forms_count_group_by(pub, formdef, anonymise):
|
|
for i in range(20):
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
if i % 3:
|
|
formdata.data['1'] = True
|
|
formdata.data['2'] = 'foo'
|
|
formdata.data['2_display'] = 'Foo'
|
|
formdata.data['3'] = ['bar', 'baz']
|
|
formdata.data['3_display'] = 'Bar, Baz'
|
|
formdata.data['4'] = {
|
|
'data': [
|
|
{'1': True, '2': ['Baz'], '2_display': 'Baz'},
|
|
{'1': False, '2': ['Foo'], '2_display': 'Foo'},
|
|
]
|
|
}
|
|
# "Web" channel has three equivalent values
|
|
if i == 1:
|
|
formdata.submission_channel = 'web'
|
|
elif i == 2:
|
|
formdata.submission_channel = ''
|
|
else:
|
|
formdata.submission_channel = None
|
|
formdata.backoffice_submission = bool(i % 2)
|
|
elif i % 2:
|
|
formdata.data['1'] = False
|
|
formdata.data['2'] = 'baz'
|
|
formdata.data['3'] = ['baz']
|
|
formdata.data['4'] = {'data': [{'1': False, '2': ['Foo', 'Bar'], '2_display': 'Foo, Bar'}]}
|
|
if i == 3:
|
|
formdata.jump_status('3')
|
|
elif i == 9:
|
|
formdata.jump_status('3')
|
|
formdata.jump_status('4')
|
|
else:
|
|
formdata.jump_status('2')
|
|
formdata.submission_channel = 'mail'
|
|
formdata.backoffice_submission = bool(i % 3)
|
|
else:
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
|
formdata.store()
|
|
if anonymise:
|
|
formdata.anonymise()
|
|
|
|
# group by item field
|
|
url = '/api/statistics/forms/count/?form=%s' % formdef.url_name
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test-item'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
assert resp.json['data']['series'] == [
|
|
{'data': [13, None, None], 'label': 'Foo'},
|
|
{'data': [3, None, None], 'label': 'baz'},
|
|
{'data': [None, None, 4], 'label': 'None'},
|
|
]
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test-item&time_interval=year'))
|
|
assert resp.json['data']['x_labels'] == ['2021']
|
|
assert resp.json['data']['series'] == [
|
|
{'label': 'Foo', 'data': [13]},
|
|
{'label': 'baz', 'data': [3]},
|
|
{'label': 'None', 'data': [4]},
|
|
]
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test-item&time_interval=hour'))
|
|
assert resp.json['data']['x_labels'] == list(range(24))
|
|
assert resp.json['data']['series'][0]['data'][0] == 13
|
|
assert resp.json['data']['series'][1]['data'][0] == 3
|
|
assert resp.json['data']['series'][2]['data'][2] == 4
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test-item&time_interval=weekday'))
|
|
assert len(resp.json['data']['x_labels']) == 7
|
|
assert resp.json['data']['series'] == [
|
|
{'label': 'Foo', 'data': [None, None, None, None, 13, None, None]},
|
|
{'label': 'baz', 'data': [None, None, None, None, 3, None, None]},
|
|
{'label': 'None', 'data': [4, None, None, None, None, None, None]},
|
|
]
|
|
|
|
# hide None label
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test-item&hide_none_label=true'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01']
|
|
assert resp.json['data']['series'] == [
|
|
{'data': [13], 'label': 'Foo'},
|
|
{'data': [3], 'label': 'baz'},
|
|
]
|
|
|
|
# group by items field
|
|
url = '/api/statistics/forms/count/?form=%s' % formdef.url_name
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test-items'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
assert resp.json['data']['series'] == [
|
|
{'label': 'Bar', 'data': [13, None, None]},
|
|
{'label': 'Baz', 'data': [16, None, None]},
|
|
{'label': 'None', 'data': [None, None, 4]},
|
|
]
|
|
|
|
# group by boolean field
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=checkbox'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
assert resp.json['data']['series'] == [
|
|
{'data': [13, None, None], 'label': 'Yes'},
|
|
{'data': [3, None, None], 'label': 'No'},
|
|
{'data': [None, None, 4], 'label': 'None'},
|
|
]
|
|
|
|
# group by boolean field inside block
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=blockdata_bool'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
assert resp.json['data']['series'] == [
|
|
{'label': 'Yes', 'data': [13, None, None]},
|
|
{'label': 'No', 'data': [16, None, None]},
|
|
{'label': 'None', 'data': [None, None, 4]},
|
|
]
|
|
|
|
# group by items field inside block
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=blockdata_block-items'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
assert resp.json['data']['series'] == [
|
|
{'label': 'Foo', 'data': [16, None, None]},
|
|
{'label': 'Bar', 'data': [3, None, None]},
|
|
{'label': 'Baz', 'data': [13, None, None]},
|
|
{'label': 'None', 'data': [None, None, 4]},
|
|
]
|
|
|
|
# group by status
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=status'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
assert resp.json['data']['series'] == [
|
|
{'label': 'New status', 'data': [13, None, 4]},
|
|
{'label': 'End status', 'data': [1, None, None]},
|
|
{'label': 'Middle status 1', 'data': [1, None, None]},
|
|
{'label': 'Middle status 2', 'data': [1, None, None]},
|
|
]
|
|
|
|
# group by simplified status
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=simple-status'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
assert resp.json['data']['series'] == [
|
|
{'label': 'New', 'data': [13, None, 4]},
|
|
{'label': 'Done', 'data': [1, None, None]},
|
|
{'label': 'In progress', 'data': [2, None, None]},
|
|
]
|
|
|
|
# group by channel
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=channel'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
assert resp.json['data']['series'] == [
|
|
{'data': [3, None, None], 'label': 'Mail'},
|
|
{'data': [7, None, 4], 'label': 'Web'},
|
|
{'data': [6, None, None], 'label': 'Backoffice'},
|
|
]
|
|
|
|
# group by channel without form filter
|
|
new_resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?group-by=channel'))
|
|
assert new_resp.json['data']['series'] == resp.json['data']['series']
|
|
|
|
# group by item field without time interval
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test-item&time_interval=none'))
|
|
# Foo is first because it has a display value, baz is second because it has not, None is always last
|
|
assert resp.json['data']['x_labels'] == ['Foo', 'baz', 'None']
|
|
assert resp.json['data']['series'] == [{'data': [13, 3, 4], 'label': 'Forms Count'}]
|
|
|
|
# group by items field without time interval
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test-items&time_interval=none'))
|
|
assert resp.json['data']['x_labels'] == ['Bar', 'Baz', 'None']
|
|
assert resp.json['data']['series'] == [{'label': 'Forms Count', 'data': [13, 16, 4]}]
|
|
|
|
# group by submission channel without time interval
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=channel&time_interval=none'))
|
|
assert resp.json['data']['x_labels'] == ['Mail', 'Web', 'Backoffice']
|
|
assert resp.json['data']['series'] == [{'data': [3, 11, 6], 'label': 'Forms Count'}]
|
|
|
|
# group by status without time interval
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=status&time_interval=none'))
|
|
assert resp.json['data']['x_labels'] == ['New status', 'End status', 'Middle status 1', 'Middle status 2']
|
|
assert resp.json['data']['series'] == [{'data': [17, 1, 1, 1], 'label': 'Forms Count'}]
|
|
|
|
# group by simplfified status without time interval
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=simple-status&time_interval=none'))
|
|
assert resp.json['data']['x_labels'] == ['New', 'Done', 'In progress']
|
|
assert resp.json['data']['series'] == [{'label': 'Forms Count', 'data': [17, 1, 2]}]
|
|
|
|
# check statuses order
|
|
formdef.workflow.possible_status = list(reversed(formdef.workflow.possible_status))
|
|
formdef.workflow.store()
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=status&time_interval=none'))
|
|
assert resp.json['data']['x_labels'] == ['Middle status 2', 'Middle status 1', 'End status', 'New status']
|
|
assert resp.json['data']['series'] == [{'data': [1, 1, 1, 17], 'label': 'Forms Count'}]
|
|
|
|
# invalid field
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=xxx'))
|
|
assert resp.json['data']['series'] == [{'data': [16, 0, 4], 'label': 'Forms Count'}]
|
|
|
|
|
|
def test_statistics_forms_count_group_by_same_varname(pub, formdef):
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = [
|
|
fields.ItemField(id='1', varname='test', label='Test', items=['foo']),
|
|
fields.ItemField(
|
|
id='2', varname='test', label='Test', items=['bar'], display_locations=['statistics']
|
|
),
|
|
]
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.data['1'] = 'foo'
|
|
formdata.data['2'] = 'bar'
|
|
formdata.store()
|
|
|
|
url = '/api/statistics/forms/count/?form=%s' % formdef.url_name
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test'))
|
|
assert resp.json['data']['series'] == [{'data': [1], 'label': 'bar'}]
|
|
|
|
formdef.fields[0].display_locations = ['statistics']
|
|
formdef.store()
|
|
formdata.store() # refresh statistics_data column
|
|
|
|
# group by uses first field marked for statistics
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test'))
|
|
assert resp.json['data']['series'] == [{'data': [1], 'label': 'foo'}]
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.data['2'] = 'foo'
|
|
formdata.store()
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test'))
|
|
assert resp.json['data']['series'] == [{'data': [2], 'label': 'foo'}]
|
|
|
|
|
|
def test_statistics_forms_count_group_by_form(pub):
|
|
category_a = Category(name='Category A')
|
|
category_a.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'A'
|
|
formdef.category_id = category_a.id
|
|
formdef.store()
|
|
|
|
for i in range(10):
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2022, 1, 1, 0, 0))
|
|
formdata.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'B'
|
|
formdef.category_id = category_a.id
|
|
formdef.store()
|
|
|
|
for i in range(5):
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.store()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/'))
|
|
assert len(resp.json['data']['subfilters']) == 2
|
|
assert resp.json['data']['subfilters'][1] == {
|
|
'id': 'group-by',
|
|
'label': 'Group by',
|
|
'options': [
|
|
{'id': 'channel', 'label': 'Channel'},
|
|
{'id': 'form', 'label': 'Form'},
|
|
],
|
|
'has_subfilters': True,
|
|
}
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?time_interval=year'))
|
|
assert resp.json['data']['x_labels'] == ['2021', '2022']
|
|
assert resp.json['data']['series'] == [{'data': [5, 10], 'label': 'Forms Count'}]
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?time_interval=year&group-by=form'))
|
|
assert resp.json['data']['x_labels'] == ['2021', '2022']
|
|
assert resp.json['data']['series'] == [
|
|
{'data': [None, 10], 'label': 'A'},
|
|
{'data': [5, None], 'label': 'B'},
|
|
]
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?time_interval=none&group-by=form'))
|
|
assert resp.json['data']['x_labels'] == ['A', 'B']
|
|
assert resp.json['data']['series'] == [{'data': [10, 5], 'label': 'Forms Count'}]
|
|
|
|
resp = get_app(pub).get(
|
|
sign_uri('/api/statistics/forms/count/?time_interval=none&group-by=form&form=category:category-a')
|
|
)
|
|
assert resp.json['data']['x_labels'] == ['A', 'B']
|
|
assert resp.json['data']['series'] == [{'data': [10, 5], 'label': 'Forms Count'}]
|
|
|
|
resp = get_app(pub).get(
|
|
sign_uri('/api/statistics/forms/count/?time_interval=none&group-by=form&form=a&form=b')
|
|
)
|
|
assert resp.json['data']['x_labels'] == ['A', 'B']
|
|
assert resp.json['data']['series'] == [{'data': [10, 5], 'label': 'Forms Count'}]
|
|
|
|
|
|
def test_statistics_forms_count_months_to_show(pub, formdef):
|
|
for i in range(24):
|
|
formdata = formdef.data_class()()
|
|
formdata.data['2'] = 'foo' if i % 2 else 'baz'
|
|
formdata.data['2_display'] = 'Foo' if i % 2 else 'Baz'
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2022 + i // 12, i % 12 + 1, 1, 0, 0))
|
|
formdata.store()
|
|
|
|
url = '/api/statistics/forms/count/'
|
|
resp = get_app(pub).get(sign_uri(url))
|
|
assert len(resp.json['data']['x_labels']) == 24
|
|
assert resp.json['data']['x_labels'][0] == '2022-01'
|
|
assert resp.json['data']['x_labels'][1] == '2022-02'
|
|
assert resp.json['data']['x_labels'][12] == '2023-01'
|
|
assert resp.json['data']['x_labels'][23] == '2023-12'
|
|
assert resp.json['data']['series'][0]['data'] == [1] * 24
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '?months_to_show=12'))
|
|
assert len(resp.json['data']['x_labels']) == 12
|
|
assert resp.json['data']['x_labels'][0] == '2023-01'
|
|
assert resp.json['data']['x_labels'][11] == '2023-12'
|
|
assert resp.json['data']['series'][0]['data'] == [1] * 12
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '?months_to_show=6'))
|
|
assert resp.json['data']['x_labels'] == ['2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12']
|
|
assert resp.json['data']['series'][0]['data'] == [1] * 6
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '?group-by=test-item&form=%s' % formdef.url_name))
|
|
assert len(resp.json['data']['x_labels']) == 24
|
|
assert resp.json['data']['series'][0]['label'] == 'Baz'
|
|
assert resp.json['data']['series'][0]['data'] == [1, None] * 12
|
|
assert resp.json['data']['series'][1]['label'] == 'Foo'
|
|
assert resp.json['data']['series'][1]['data'] == [None, 1] * 12
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '?months_to_show=6&group-by=test-item&form=%s' % formdef.url_name))
|
|
assert resp.json['data']['x_labels'] == ['2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12']
|
|
assert resp.json['data']['series'][0]['data'] == [1, None, 1, None, 1, None]
|
|
assert resp.json['data']['series'][1]['data'] == [None, 1, None, 1, None, 1]
|
|
|
|
|
|
def test_statistics_cards_count(pub):
|
|
carddef = CardDef()
|
|
carddef.name = 'test 1'
|
|
carddef.fields = []
|
|
carddef.store()
|
|
carddef.data_class().wipe()
|
|
|
|
for _i in range(20):
|
|
carddata = carddef.data_class()()
|
|
carddata.just_created()
|
|
carddata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
carddata.store()
|
|
|
|
# apply (required) card filter
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/cards/count/?form=%s' % carddef.url_name))
|
|
assert resp.json['data']['series'] == [{'data': [20], 'label': 'Cards Count'}]
|
|
assert resp.json['data']['x_labels'] == ['2021-01']
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/cards/count/?card=%s' % 'invalid'), status=404)
|
|
|
|
|
|
def test_statistics_resolution_time(pub, freezer):
|
|
workflow = Workflow(name='Workflow One')
|
|
new_status = workflow.add_status(name='New status')
|
|
middle_status = workflow.add_status(name='Middle status')
|
|
end_status1 = workflow.add_status(name='End status')
|
|
end_status2 = workflow.add_status(name='End status 2')
|
|
|
|
# add jump from new to end
|
|
jump = new_status.add_action('jump', id='_jump')
|
|
jump.status = '3'
|
|
|
|
# add jump form new to middle and from middle to end 2
|
|
jump = new_status.add_action('jump', id='_jump')
|
|
jump.status = '2'
|
|
jump = middle_status.add_action('jump', id='_jump')
|
|
jump.status = '4'
|
|
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
freezer.move_to(datetime.date(2021, 1, 1))
|
|
formdata_list = []
|
|
for i in range(3):
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata_list.append(formdata)
|
|
|
|
# one formdata resolved in one day
|
|
freezer.move_to(datetime.date(2021, 1, 2))
|
|
formdata_list[0].jump_status('3')
|
|
formdata_list[0].store()
|
|
|
|
# one formdata resolved in two days, passing by middle status
|
|
formdata_list[1].jump_status('2')
|
|
freezer.move_to(datetime.date(2021, 1, 3))
|
|
formdata_list[1].jump_status('4')
|
|
formdata_list[1].store()
|
|
|
|
# one formdata blocked in middle status for three days
|
|
freezer.move_to(datetime.date(2021, 1, 4))
|
|
formdata_list[2].jump_status('2')
|
|
formdata_list[2].store()
|
|
|
|
# by default, count forms between initial status and final statuses
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test'))
|
|
assert resp.json['data'] == {
|
|
'series': [
|
|
{
|
|
'data': [86400, 172800, 129600, 129600],
|
|
'label': 'Time between "New status" and any final status',
|
|
}
|
|
],
|
|
'subfilters': [
|
|
{
|
|
'id': 'start_status',
|
|
'label': 'Start status',
|
|
'options': [
|
|
{'id': '1', 'label': 'New status'},
|
|
{'id': '2', 'label': 'Middle status'},
|
|
{'id': '3', 'label': 'End status'},
|
|
{'id': '4', 'label': 'End status 2'},
|
|
],
|
|
'required': True,
|
|
'default': '1',
|
|
},
|
|
{
|
|
'default': 'done',
|
|
'id': 'end_status',
|
|
'label': 'End status',
|
|
'options': [
|
|
{'id': 'done', 'label': 'Any final status'},
|
|
{'id': '2', 'label': 'Middle status'},
|
|
{'id': '3', 'label': 'End status'},
|
|
{'id': '4', 'label': 'End status 2'},
|
|
],
|
|
'required': True,
|
|
},
|
|
],
|
|
'x_labels': ['Minimum time', 'Maximum time', 'Mean', 'Median'],
|
|
}
|
|
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'1 day(s) and 0 hour(s)',
|
|
'2 day(s) and 0 hour(s)',
|
|
'1 day(s) and 12 hour(s)',
|
|
'1 day(s) and 12 hour(s)',
|
|
]
|
|
|
|
# specify end status
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test&end_status=3'))
|
|
assert resp.json['data']['series'][0]['label'] == 'Time between "New status" and "End status"'
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
]
|
|
|
|
# specify start status
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test&start_status=2'))
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
]
|
|
|
|
# specify start and end statuses
|
|
resp = get_app(pub).get(
|
|
sign_uri('/api/statistics/resolution-time/?form=test&start_status=2&end_status=4')
|
|
)
|
|
assert resp.json['data']['series'][0]['label'] == 'Time between "Middle status" and "End status 2"'
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
]
|
|
|
|
resp = get_app(pub).get(
|
|
sign_uri('/api/statistics/resolution-time/?form=test&start_status=1&end_status=2')
|
|
)
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'1 day(s) and 0 hour(s)',
|
|
'3 day(s) and 0 hour(s)',
|
|
'2 day(s) and 0 hour(s)',
|
|
'2 day(s) and 0 hour(s)',
|
|
]
|
|
|
|
# unknown statuses
|
|
default_resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test'))
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test&start_status=42'))
|
|
assert resp.json == default_resp.json
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test&end_status=42'))
|
|
assert resp.json == default_resp.json
|
|
|
|
# specify start and end statuses which does not match any formdata
|
|
resp = get_app(pub).get(
|
|
sign_uri('/api/statistics/resolution-time/?form=test&start_status=2&end_status=3')
|
|
)
|
|
assert resp.json['data']['series'][0]['data'] == []
|
|
|
|
# specify start status that is after end status
|
|
resp = get_app(pub).get(
|
|
sign_uri('/api/statistics/resolution-time/?form=test&start_status=4&end_status=2')
|
|
)
|
|
assert resp.json['data']['series'][0]['label'] == 'Time between "End status 2" and "Middle status"'
|
|
assert get_humanized_duration_serie(resp.json) == []
|
|
|
|
# unknown form
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=xxx'), status=400)
|
|
|
|
# form without any final status
|
|
end_status1.add_action('choice')
|
|
end_status2.add_action('choice')
|
|
workflow.store()
|
|
get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test'), status=400)
|
|
|
|
|
|
def test_statistics_resolution_time_status_loop(pub, freezer):
|
|
workflow = Workflow(name='Workflow One')
|
|
new_status = workflow.add_status(name='New status')
|
|
middle_status = workflow.add_status(name='Middle status')
|
|
workflow.add_status(name='End status')
|
|
|
|
# add jump from new to middle, middle to new, and middle to end
|
|
jump = new_status.add_action('jump', id='_jump')
|
|
jump.status = '2'
|
|
jump = middle_status.add_action('jump', id='_jump')
|
|
jump.status = '1'
|
|
jump = middle_status.add_action('jump', id='_jump')
|
|
jump.status = '3'
|
|
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
freezer.move_to(datetime.date(2021, 1, 1))
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
|
|
# one day after creation, jump to middle status
|
|
freezer.move_to(datetime.date(2021, 1, 2))
|
|
formdata.jump_status('2')
|
|
formdata.store()
|
|
|
|
# two days after, jump to start status
|
|
freezer.move_to(datetime.date(2021, 1, 4))
|
|
formdata.jump_status('1')
|
|
formdata.store()
|
|
|
|
# three days after, jump to middle status again
|
|
freezer.move_to(datetime.date(2021, 1, 6))
|
|
formdata.jump_status('2')
|
|
formdata.store()
|
|
|
|
resp = get_app(pub).get(
|
|
sign_uri('/api/statistics/resolution-time/?form=test&start_status=wf-new&end_status=2')
|
|
)
|
|
assert resp.json['data']['series'][0]['label'] == 'Time between "New status" and "Middle status"'
|
|
# only first transition from new to middle is computed, later one is ignored
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
'1 day(s) and 0 hour(s)',
|
|
]
|
|
|
|
|
|
def test_statistics_resolution_time_median(pub, freezer):
|
|
workflow = Workflow(name='Workflow One')
|
|
new_status = workflow.add_status(name='New status')
|
|
workflow.add_status(name='End status')
|
|
jump = new_status.add_action('jump', id='_jump')
|
|
jump.status = '2'
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
for i in range(2, 11):
|
|
formdata = formdef.data_class()()
|
|
freezer.move_to(datetime.date(2021, 1, 1))
|
|
formdata.just_created()
|
|
|
|
if i != 10:
|
|
# add lots of formdata resolved in a few days
|
|
freezer.move_to(datetime.date(2021, 1, i))
|
|
else:
|
|
# one formdata took 3 months
|
|
freezer.move_to(datetime.date(2021, 4, 1))
|
|
|
|
formdata.jump_status('2')
|
|
formdata.store()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test'))
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'1 day(s) and 0 hour(s)', # min
|
|
'90 day(s) and 0 hour(s)', # max
|
|
'14 day(s) and 0 hour(s)', # mean
|
|
'5 day(s) and 0 hour(s)', # median
|
|
]
|
|
|
|
|
|
def test_statistics_resolution_time_start_end_filter(pub, freezer):
|
|
workflow = Workflow(name='Workflow One')
|
|
new_status = workflow.add_status(name='New status')
|
|
workflow.add_status(name='End status')
|
|
jump = new_status.add_action('jump', id='_jump')
|
|
jump.status = '2'
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
# create formdata, the latest being the longest to resolve
|
|
for i in range(1, 10):
|
|
formdata = formdef.data_class()()
|
|
freezer.move_to(datetime.date(2021, 1, i))
|
|
formdata.just_created()
|
|
freezer.move_to(datetime.date(2021, 1, i * 2))
|
|
formdata.jump_status('2')
|
|
formdata.store()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test'))
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'1 day(s) and 0 hour(s)', # min
|
|
'9 day(s) and 0 hour(s)', # max
|
|
'5 day(s) and 0 hour(s)', # mean
|
|
'5 day(s) and 0 hour(s)', # median
|
|
]
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test&start=2021-01-05'))
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'5 day(s) and 0 hour(s)', # min
|
|
'9 day(s) and 0 hour(s)', # max
|
|
'7 day(s) and 0 hour(s)', # mean
|
|
'7 day(s) and 0 hour(s)', # median
|
|
]
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test&end=2021-01-05'))
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'1 day(s) and 0 hour(s)', # min
|
|
'4 day(s) and 0 hour(s)', # max
|
|
'2 day(s) and 12 hour(s)', # mean
|
|
'2 day(s) and 12 hour(s)', # median
|
|
]
|
|
|
|
resp = get_app(pub).get(
|
|
sign_uri('/api/statistics/resolution-time/?form=test&start=2021-01-04&end=2021-01-05')
|
|
)
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'4 day(s) and 0 hour(s)', # min
|
|
'4 day(s) and 0 hour(s)', # max
|
|
'4 day(s) and 0 hour(s)', # mean
|
|
'4 day(s) and 0 hour(s)', # median
|
|
]
|
|
|
|
|
|
def test_statistics_resolution_time_cards(pub, freezer):
|
|
workflow = Workflow(name='Workflow One')
|
|
new_status = workflow.add_status(name='New status')
|
|
workflow.add_status(name='End status')
|
|
jump = new_status.add_action('jump', id='_jump')
|
|
jump.status = '2'
|
|
workflow.store()
|
|
|
|
carddef = CardDef()
|
|
carddef.name = 'test'
|
|
carddef.workflow_id = workflow.id
|
|
carddef.store()
|
|
|
|
for i in range(1, 10):
|
|
carddata = carddef.data_class()()
|
|
freezer.move_to(datetime.date(2021, 1, i))
|
|
carddata.just_created()
|
|
freezer.move_to(datetime.date(2021, 1, i * 2))
|
|
carddata.jump_status('2')
|
|
carddata.store()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time-cards/?form=test'))
|
|
assert get_humanized_duration_serie(resp.json) == [
|
|
'1 day(s) and 0 hour(s)',
|
|
'9 day(s) and 0 hour(s)',
|
|
'5 day(s) and 0 hour(s)',
|
|
'5 day(s) and 0 hour(s)',
|
|
]
|
|
|
|
|
|
def test_statistics_multiple_forms_count(pub, formdef):
|
|
formdef1 = FormDef()
|
|
formdef1.name = 'xxx'
|
|
formdef1.fields = [x for x in formdef.fields if x.varname != 'blockdata']
|
|
formdef1.store()
|
|
|
|
formdef2 = FormDef()
|
|
formdef2.name = 'yyy'
|
|
formdef2.workflow = formdef.workflow
|
|
formdef2.fields = formdef.fields
|
|
formdef2.store()
|
|
|
|
for i in range(20):
|
|
formdata = formdef1.data_class()()
|
|
formdata.data['2'] = 'foo'
|
|
formdata.data['2_display'] = 'Foo'
|
|
formdata.data['3'] = ['foo']
|
|
formdata.data['3_display'] = 'Foo'
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.store()
|
|
|
|
for _i in range(30):
|
|
formdata = formdef2.data_class()()
|
|
formdata.data['2'] = 'baz'
|
|
formdata.data['2_display'] = 'Baz'
|
|
formdata.data['3'] = ['foo', 'bar', 'baz']
|
|
formdata.data['3_display'] = 'Bar, Baz'
|
|
formdata.data['4'] = {'data': [{'1': True}]}
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
|
formdata.jump_status('2')
|
|
formdata.store()
|
|
|
|
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/'))
|
|
assert resp.json['data']['series'] == [{'data': [20, 0, 30], 'label': 'Forms Count'}]
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
|
|
# filter by all forms explicitely
|
|
url = '/api/statistics/forms/count/?form=%s&form=%s' % (formdef1.url_name, formdef2.url_name)
|
|
resp = get_app(pub).get(sign_uri(url))
|
|
assert resp.json['data']['series'] == [{'data': [20, 0, 30], 'label': 'Forms Count'}]
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
|
|
# filter on item fields
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=foo'))
|
|
assert resp.json['data']['series'][0]['data'] == [20]
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=baz'))
|
|
assert resp.json['data']['series'][0]['data'] == [30]
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=bar'))
|
|
assert resp.json['data']['series'][0]['data'] == []
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-items=foo'))
|
|
assert resp.json['data']['series'][0]['data'] == [20, 0, 30]
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-test-items=bar'))
|
|
assert resp.json['data']['series'][0]['data'] == [30]
|
|
|
|
# group by item field
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test-item'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
assert len(resp.json['data']['series']) == 2
|
|
assert {'data': [20, None, None], 'label': 'Foo'} in resp.json['data']['series']
|
|
assert {'data': [None, None, 30], 'label': 'Baz'} in resp.json['data']['series']
|
|
|
|
# filter on status
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=_all'))
|
|
assert resp.json['data']['series'][0]['data'] == [20, 0, 30]
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=just_submitted'))
|
|
assert resp.json['data']['series'][0]['data'] == [20]
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=done'))
|
|
assert resp.json['data']['series'][0]['data'] == [30]
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=pending'))
|
|
assert resp.json['data']['series'][0]['data'] == [20]
|
|
|
|
# filter on status exclusive to one formdef is ignored
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=2'))
|
|
assert resp.json['data']['series'][0]['data'] == [20, 0, 30]
|
|
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-status=rejected'))
|
|
assert resp.json['data']['series'][0]['data'] == [20, 0, 30]
|
|
|
|
# filter on block boolean field exclusive to one formdef yields empty results
|
|
resp = get_app(pub).get(sign_uri(url + '&filter-blockdata_bool=true'))
|
|
assert resp.json['data']['series'][0]['data'] == []
|
|
|
|
|
|
def test_statistics_multiple_forms_count_different_ids(pub):
|
|
data_source = {
|
|
'type': 'jsonvalue',
|
|
'value': json.dumps(
|
|
[{'id': 'foo', 'text': 'Foo'}, {'id': 'bar', 'text': 'Bar'}, {'id': 'baz', 'text': 'Baz'}]
|
|
),
|
|
}
|
|
|
|
formdef1 = FormDef()
|
|
formdef1.name = 'xxx'
|
|
formdef1.fields = [
|
|
fields.ItemField(
|
|
id='1',
|
|
varname='test-item',
|
|
label='Test item',
|
|
data_source=data_source,
|
|
display_locations=['statistics'],
|
|
),
|
|
]
|
|
formdef1.store()
|
|
|
|
formdef2 = FormDef()
|
|
formdef2.name = 'yyy'
|
|
formdef2.fields = [
|
|
fields.ItemField(
|
|
id='2',
|
|
varname='test-item',
|
|
label='Test item',
|
|
data_source=data_source,
|
|
display_locations=['statistics'],
|
|
),
|
|
]
|
|
formdef2.store()
|
|
|
|
formdata = formdef1.data_class()()
|
|
formdata.data['1'] = 'foo'
|
|
formdata.data['1_display'] = 'Foo'
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.store()
|
|
|
|
formdata = formdef2.data_class()()
|
|
formdata.data['2'] = 'baz'
|
|
formdata.data['2_display'] = 'Baz'
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
|
formdata.store()
|
|
|
|
url = '/api/statistics/forms/count/?form=%s&form=%s' % (formdef1.url_name, formdef2.url_name)
|
|
resp = get_app(pub).get(sign_uri(url))
|
|
assert resp.json['data']['series'] == [{'data': [1, 0, 1], 'label': 'Forms Count'}]
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
|
|
# group by item field
|
|
resp = get_app(pub).get(sign_uri(url + '&group-by=test-item'))
|
|
assert resp.json['data']['x_labels'] == ['2021-01', '2021-02', '2021-03']
|
|
assert len(resp.json['data']['series']) == 2
|
|
assert {'data': [1, None, None], 'label': 'Foo'} in resp.json['data']['series']
|
|
assert {'data': [None, None, 1], 'label': 'Baz'} in resp.json['data']['series']
|
|
|
|
|
|
def test_statistics_multiple_forms_count_subfilters(pub, formdef):
|
|
category_a = Category(name='Category A')
|
|
category_a.store()
|
|
|
|
formdef.category_id = category_a.id
|
|
formdef.store()
|
|
|
|
formdef2 = FormDef()
|
|
formdef2.name = 'test 2'
|
|
formdef2.category_id = category_a.id
|
|
formdef2.workflow = formdef.workflow
|
|
formdef2.fields = [x for x in formdef.fields if x.varname not in ('blockdata', 'test-items')]
|
|
formdef2.store()
|
|
|
|
for i in range(20):
|
|
formdata = formdef.data_class()()
|
|
formdata.data['2'] = 'foo'
|
|
formdata.data['2_display'] = 'Foo'
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
|
formdata.jump_status('2')
|
|
formdata.store()
|
|
|
|
for _i in range(30):
|
|
formdata = formdef2.data_class()()
|
|
formdata.data['2'] = 'baz'
|
|
formdata.data['2_display'] = 'Baz'
|
|
formdata.just_created()
|
|
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
|
formdata.store()
|
|
|
|
resp = get_app(pub).get(
|
|
sign_uri('/api/statistics/forms/count/?form=%s&form=%s' % (formdef.url_name, formdef2.url_name))
|
|
)
|
|
|
|
# group-by subfilter shows all common fields
|
|
group_by_filter = [x for x in resp.json['data']['subfilters'] if x['id'] == 'group-by'][0]
|
|
assert group_by_filter['options'] == [
|
|
{'id': 'channel', 'label': 'Channel'},
|
|
{'id': 'form', 'label': 'Form'},
|
|
{'id': 'simple-status', 'label': 'Simplified status'},
|
|
{'id': 'test-item', 'label': 'Test item'},
|
|
{'id': 'checkbox', 'label': 'Checkbox'},
|
|
{'id': 'status', 'label': 'Status'},
|
|
]
|
|
|
|
# item field subfilter shows all possible values
|
|
item_filter = [x for x in resp.json['data']['subfilters'] if x['id'] == 'filter-test-item'][0]
|
|
assert item_filter['options'] == [{'id': 'baz', 'label': 'Baz'}, {'id': 'foo', 'label': 'Foo'}]
|
|
|
|
# boolean field subfilter options are not altered
|
|
boolean_filter = [x for x in resp.json['data']['subfilters'] if x['id'] == 'filter-checkbox'][0]
|
|
assert boolean_filter['options'] == [{'id': 'true', 'label': 'Yes'}, {'id': 'false', 'label': 'No'}]
|
|
|
|
# block boolean and items subfilters are not shown as they are exclusive to one formdef
|
|
assert not any(
|
|
x
|
|
for x in resp.json['data']['subfilters']
|
|
if x['id'] in ('filter-blockdata_bool', 'filter-test-items')
|
|
)
|
|
|
|
category_resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=category:category-a'))
|
|
assert category_resp.json == resp.json
|
|
|
|
# cannot group by form if single form is selected
|
|
form_resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=test'))
|
|
form_group_by_filter = [x for x in form_resp.json['data']['subfilters'] if x['id'] == 'group-by'][0]
|
|
assert [x for x in group_by_filter['options'] if x not in form_group_by_filter['options']] == [
|
|
{'id': 'form', 'label': 'Form'}
|
|
]
|