statistics: use new formdata column to compute counts (#73770)

This commit is contained in:
Valentin Deniaud 2023-01-18 14:24:58 +01:00 committed by Gitea
parent 72f6d8e244
commit cfe0116741
3 changed files with 50 additions and 16 deletions

View File

@ -708,6 +708,7 @@ def test_statistics_forms_count_subfilters_query_same_varname(pub, formdef):
]
formdef.store()
formdatas = []
for i in range(5):
formdata = formdef.data_class()()
formdata.just_created()
@ -718,6 +719,7 @@ def test_statistics_forms_count_subfilters_query_same_varname(pub, formdef):
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'))
@ -725,14 +727,15 @@ def test_statistics_forms_count_subfilters_query_same_varname(pub, formdef):
formdef.fields[0].display_locations = ['statistics']
formdef.store()
for formdata in formdatas:
formdata.store() # refresh statistics_data column
# filter criterias is "f1 == 'foo' and f2 == 'foo'" hence one result, this should be improved
# 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': [1], 'label': 'Forms Count'}]
assert resp.json['data']['series'] == [{'data': [4], 'label': 'Forms Count'}]
# filter criterias is "f1 == 'bar' and f2 == 'bar'" hence no results, this should be improved
resp = get_app(pub).get(sign_uri(url + '&filter-test=bar'))
assert resp.json['data']['series'] == [{'data': [], 'label': 'Forms Count'}]
assert resp.json['data']['series'] == [{'data': [1], 'label': 'Forms Count'}]
def test_statistics_forms_count_subfilters_query_integer_items(pub, formdef):
@ -943,6 +946,7 @@ def test_statistics_forms_count_group_by_same_varname(pub, formdef):
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'))
@ -954,9 +958,8 @@ def test_statistics_forms_count_group_by_same_varname(pub, formdef):
formdata.data['2'] = 'foo'
formdata.store()
# second field is ignored
resp = get_app(pub).get(sign_uri(url + '&group-by=test'))
assert resp.json['data']['series'] == [{'data': [1], 'label': 'foo'}, {'data': [1], 'label': 'None'}]
assert resp.json['data']['series'] == [{'data': [2], 'label': 'foo'}]
def test_statistics_cards_count(pub):

View File

@ -181,7 +181,9 @@ def pickle_loads(value):
class Criteria(qommon.storage.Criteria):
def __init__(self, attribute, value, **kwargs):
self.attribute = attribute.replace('-', '_')
self.attribute = attribute
if '->' not in attribute:
self.attribute = self.attribute.replace('-', '_')
self.value = value
self.field = kwargs.get('field')
@ -4647,7 +4649,11 @@ def get_period_query(
formdef_class = criteria.value
continue
if criteria.__class__.__name__ == 'Equal' and criteria.attribute == 'formdef_id':
if (
formdef_class
and criteria.__class__.__name__ == 'Equal'
and criteria.attribute == 'formdef_id'
):
# if there's a formdef_id specified, switch to using the
# specific table so we have access to all fields
table_name = get_formdef_table_name(formdef_class.get(criteria.value))

View File

@ -30,7 +30,7 @@ from wcs.categories import Category
from wcs.formdata import FormData
from wcs.formdef import FormDef
from wcs.qommon import _, misc, pgettext_lazy
from wcs.qommon.storage import Contains, Equal, GreaterOrEqual, Less, NotNull, Null, Or, StrictNotEqual
from wcs.qommon.storage import Contains, Equal, GreaterOrEqual, Less, Null, Or, StrictNotEqual
class RestrictedView(View):
@ -252,11 +252,7 @@ class FormsCountView(RestrictedView):
except KeyError:
return HttpResponseBadRequest('invalid form')
form_page = self.formpage_class(formdef=formdef, update_breadcrumbs=False)
# formdef_klass is a fake criteria, it will be used in time interval functions
# to switch to appropriate class, it must appear before formdef_id.
totals_kwargs['criterias'].append(Equal('formdef_klass', self.formdef_class))
totals_kwargs['criterias'].append(Equal('formdef_id', formdef.id))
self.set_formdef_parameters(totals_kwargs, formdef)
totals_kwargs['criterias'].extend(self.get_filters_criterias(formdef, form_page))
self.set_group_by_parameters(group_by, formdef, form_page, totals_kwargs, group_labels)
subfilters = self.get_subfilters(form_page, group_by)
@ -314,8 +310,27 @@ class FormsCountView(RestrictedView):
{'data': {'x_labels': x_labels, 'series': series, 'subfilters': subfilters}, 'err': 0}
)
def set_formdef_parameters(self, totals_kwargs, formdef):
# set formdef_klass to None to deactivate switching to formdef specific table
totals_kwargs['criterias'].append(Equal('formdef_klass', None))
totals_kwargs['criterias'].append(Equal('formdef_id', formdef.id))
def transform_criteria(self, criteria):
if not hasattr(criteria, 'field'):
return criteria
attribute = "statistics_data->'%s'" % criteria.field.varname
if isinstance(criteria.value, bool):
value = str(criteria.value).lower()
else:
value = '"%s"' % criteria.value
return sql.ArrayContains(attribute, value)
def get_filters_criterias(self, formdef, form_page):
criterias = form_page.get_criterias_from_query(statistics_fields_only=True)
criterias = [self.transform_criteria(criteria) for criteria in criterias]
selected_status = self.request.GET.get('filter-status')
applied_filters = None
@ -471,10 +486,10 @@ class FormsCountView(RestrictedView):
if group_by_field.type == 'status':
totals_kwargs['group_by'] = 'status'
else:
totals_kwargs['group_by'] = sql.get_field_id(group_by_field)
totals_kwargs['group_by'] = "statistics_data->'%s'" % group_by_field.varname
if self.request.GET.get('hide_none_label') == 'true':
totals_kwargs['criterias'].append(NotNull(totals_kwargs['group_by']))
totals_kwargs['criterias'].append(StrictNotEqual(totals_kwargs['group_by'], '[]'))
group_labels.update(self.get_group_labels(group_by_field, formdef, form_page, group_by))
@ -494,6 +509,8 @@ class FormsCountView(RestrictedView):
groups = total[1]
if not isinstance(groups, list):
groups = [groups]
if not groups:
groups = [None]
for group in groups:
totals_by_group[group] += total[2]
seen_group_values.add(group)
@ -516,6 +533,8 @@ class FormsCountView(RestrictedView):
for groups, total in totals:
if not isinstance(groups, list):
groups = [groups]
if not groups:
groups = [None]
for group in groups:
totals_by_group[group] += total
@ -562,6 +581,12 @@ class CardsCountView(FormsCountView):
has_global_count_support = False
label = _('Cards Count')
def set_formdef_parameters(self, totals_kwargs, formdef):
# formdef_klass is a fake criteria, it will be used in time interval functions
# to switch to appropriate class, it must appear before formdef_id.
totals_kwargs['criterias'].append(Equal('formdef_klass', CardDef))
totals_kwargs['criterias'].append(Equal('formdef_id', formdef.id))
class ResolutionTimeView(RestrictedView):
formdef_class = FormDef