api: add global login and registration counts to statistics (#63368)
gitea/authentic/pipeline/head This commit looks good Details

This commit is contained in:
Valentin Deniaud 2023-02-23 15:12:04 +01:00
parent 30251c80b1
commit e3ba72b7bd
3 changed files with 56 additions and 33 deletions

View File

@ -1600,7 +1600,7 @@ class StatisticsSerializer(serializers.Serializer):
]
time_interval = serializers.ChoiceField(choices=TIME_INTERVAL_CHOICES, default='month')
group_by = serializers.ChoiceField(choices=GROUP_BY_CHOICES, default='authentication_type')
group_by = serializers.ChoiceField(choices=GROUP_BY_CHOICES, default='global')
service = ServiceOUField(child=serializers.SlugField(max_length=256), required=False)
services_ou = serializers.SlugField(required=False, allow_blank=False, max_length=256)
users_ou = serializers.SlugField(required=False, allow_blank=False, max_length=256)
@ -1611,11 +1611,13 @@ class StatisticsSerializer(serializers.Serializer):
def stat(**kwargs):
'''Extend action decorator to allow passing statistics related info.'''
filters = kwargs.pop('filters', [])
name = kwargs['name']
kwargs['detail'] = False
decorator = action(**kwargs)
def wraps(func):
func.filters = filters
func.name = name
return decorator(func)
return wraps
@ -1644,8 +1646,6 @@ class StatisticsAPI(ViewSet):
'label': _('Group by'),
'options': [{'id': key, 'label': label} for key, label in group_by_field.choices.items()],
'has_subfilters': True,
'required': True,
'default': group_by_field.default,
}
for action in self.get_extra_actions():
url = self.reverse_action(action.url_name)
@ -1729,6 +1729,7 @@ class StatisticsAPI(ViewSet):
subfilters = []
if not method:
method = {
'global': 'get_global_statistics',
'authentication_type': 'get_method_statistics',
'service': 'get_service_statistics',
'service_ou': 'get_service_ou_statistics',
@ -1737,6 +1738,8 @@ class StatisticsAPI(ViewSet):
if data['group_by'] == 'authentication_type':
allowed_filters = ('services_ou', 'users_ou', 'service')
subfilters = self.get_additional_filters(allowed_filters)
elif data['group_by'] == 'global':
kwargs['y_label'] = getattr(self, self.action).name
else:
allowed_filters = getattr(self, self.action).filters
service = data.get('service')

View File

@ -38,6 +38,7 @@ from django.utils.translation import gettext_lazy as _
from authentic2.utils.cache import GlobalCache
from . import sql
from .utils import Statistics
if django.VERSION < (3, 1):
from django.contrib.postgres.fields.jsonb import JSONField # noqa pylint: disable=ungrouped-imports
@ -163,6 +164,16 @@ class EventTypeDefinition(metaclass=EventTypeDefinitionMeta):
qs = qs.annotate(count=Count('id'))
return qs.order_by(group_by_time)
@classmethod
def get_global_statistics(cls, group_by_time, y_label, start=None, end=None):
qs = cls.get_statistics(group_by_time=group_by_time, start=start, end=end)
stats = Statistics(qs, time_interval=group_by_time)
for stat in qs:
stats.add(x_label=stat[group_by_time], y_label=y_label, value=stat['count'])
return stats.to_json()
def __repr__(self):
return '<EventTypeDefinition %r %s>' % (self.name, self.label)

View File

@ -2709,8 +2709,6 @@ def test_api_statistics_list_new(app, admin):
{'id': 'service_ou', 'label': 'Organizational unit'},
],
'has_subfilters': True,
'required': True,
'default': 'authentication_type',
},
],
'deprecated': False,
@ -2728,6 +2726,9 @@ def test_api_statistics_subfilters(app, admin, endpoint):
headers = basic_authorization_header(admin)
resp = app.get('/api/statistics/%s/' % endpoint, headers=headers)
assert len(resp.json['data']['subfilters']) == 0
resp = app.get('/api/statistics/%s/?group_by=authentication_type' % endpoint, headers=headers)
assert len(resp.json['data']['subfilters']) == 1
assert resp.json['data']['subfilters'][0] == {
'id': 'service',
@ -2740,13 +2741,13 @@ def test_api_statistics_subfilters(app, admin, endpoint):
# adding second ou doesn't change anything
ou = OU.objects.create(name='Second OU', slug='second')
new_resp = app.get('/api/statistics/%s/' % endpoint, headers=headers)
new_resp = app.get('/api/statistics/%s/?group_by=authentication_type' % endpoint, headers=headers)
assert new_resp.json == resp.json
# if there are services in two differents OUs, filter is shown
service.ou = ou
service.save()
resp = app.get('/api/statistics/%s/' % endpoint, headers=headers)
resp = app.get('/api/statistics/%s/?group_by=authentication_type' % endpoint, headers=headers)
assert len(resp.json['data']['subfilters']) == 2
assert resp.json['data']['subfilters'][1] == {
'id': 'services_ou',
@ -2759,7 +2760,7 @@ def test_api_statistics_subfilters(app, admin, endpoint):
# same goes with users
User.objects.create(username='john.doe', email='john.doe@example.com', ou=ou)
resp = app.get('/api/statistics/%s/' % endpoint, headers=headers)
resp = app.get('/api/statistics/%s/?group_by=authentication_type' % endpoint, headers=headers)
assert len(resp.json['data']['subfilters']) == 3
assert resp.json['data']['subfilters'][2] == {
'id': 'users_ou',
@ -2776,6 +2777,9 @@ def test_api_statistics_subfilters(app, admin, endpoint):
resp = app.get('/api/statistics/%s/?group_by=service_ou' % endpoint, headers=headers)
assert len(resp.json['data']['subfilters']) == 0
resp = app.get('/api/statistics/%s/' % endpoint, headers=headers)
assert len(resp.json['data']['subfilters']) == 0
@pytest.mark.parametrize(
'event_type_name,event_name',
@ -2817,7 +2821,12 @@ def test_api_statistics(app, admin, freezer, event_type_name, event_name):
)
Event.objects.create(type=event_type, references=[portal], data=dict(method2, service_name=str(portal)))
resp = app.get('/api/statistics/%s/?time_interval=month' % event_name, headers=headers)
params = {}
if 'new' in event_name:
params['group_by'] = 'authentication_type'
url = '/api/statistics/%s/' % event_name
resp = app.get(url, headers=headers, params=params)
assert resp.json['data']['x_labels'] == ['2020-02', '2020-03']
assert resp.json['data']['series'] == [
{'label': 'FranceConnect', 'data': [None, 1]},
@ -2826,53 +2835,44 @@ def test_api_statistics(app, admin, freezer, event_type_name, event_name):
# default time interval is 'month'
month_data = resp.json['data']
resp = app.get('/api/statistics/%s/' % event_name, headers=headers)
resp = app.get(url, headers=headers, params=params)
assert month_data == resp.json['data']
resp = app.get(
'/api/statistics/%s/?time_interval=month&services_ou=default' % event_name, headers=headers
)
resp = app.get(url, headers=headers, params={'services_ou': 'default', **params})
assert resp.json['data']['x_labels'] == ['2020-02', '2020-03']
assert resp.json['data']['series'] == [{'label': 'password', 'data': [1, 1]}]
resp = app.get(
'/api/statistics/%s/?time_interval=month&users_ou=default&service=agendas default' % event_name,
headers=headers,
url, headers=headers, params={'service': 'agendas default', 'users_ou': 'default', **params}
)
assert resp.json['data']['x_labels'] == ['2020-02']
assert resp.json['data']['series'] == [{'label': 'password', 'data': [1]}]
resp = app.get('/api/statistics/%s/?time_interval=month&users_ou=default' % event_name, headers=headers)
resp = app.get(url, headers=headers, params={'users_ou': 'default', **params})
assert resp.json['data']['x_labels'] == ['2020-02']
assert resp.json['data']['series'] == [{'label': 'password', 'data': [1]}]
resp = app.get(
'/api/statistics/%s/?time_interval=month&service=agendas default' % event_name, headers=headers
)
resp = app.get(url, headers=headers, params={'service': 'agendas default', **params})
assert resp.json['data']['x_labels'] == ['2020-02', '2020-03']
assert resp.json['data']['series'] == [{'label': 'password', 'data': [1, 1]}]
resp = app.get(
'/api/statistics/%s/?time_interval=month&start=2020-03-01T01:01' % event_name, headers=headers
)
resp = app.get(url, headers=headers, params={'start': '2020-03-01T01:01', **params})
assert resp.json['data']['x_labels'] == ['2020-03']
assert resp.json['data']['series'] == [
{'label': 'FranceConnect', 'data': [1]},
{'label': 'password', 'data': [1]},
]
resp = app.get(
'/api/statistics/%s/?time_interval=month&end=2020-03-01T01:01' % event_name, headers=headers
)
resp = app.get(url, headers=headers, params={'end': '2020-03-01T01:01', **params})
assert resp.json['data']['x_labels'] == ['2020-02']
assert resp.json['data']['series'] == [{'label': 'password', 'data': [2]}]
resp = app.get('/api/statistics/%s/?time_interval=month&end=2020-03-01' % event_name, headers=headers)
resp = app.get(url, headers=headers, params={'end': '2020-03-01', **params})
assert resp.json['data']['x_labels'] == ['2020-02']
assert resp.json['data']['series'] == [{'label': 'password', 'data': [2]}]
resp = app.get(
'/api/statistics/%s/?time_interval=year&service=portal second' % event_name, headers=headers
url, headers=headers, params={'time_interval': 'year', 'service': 'portal second', **params}
)
assert resp.json['data']['x_labels'] == ['2020']
assert resp.json['data']['series'] == [
@ -2881,10 +2881,10 @@ def test_api_statistics(app, admin, freezer, event_type_name, event_name):
]
if 'new' in event_name:
url = '/api/statistics/%s/?time_interval=month&group_by=service' % event_name
params['group_by'] = 'service'
else:
url = '/api/statistics/service_%s/?time_interval=month' % event_name
resp = app.get(url, headers=headers)
url = '/api/statistics/service_%s/' % event_name
resp = app.get(url, headers=headers, params=params)
assert resp.json['data']['x_labels'] == ['2020-02', '2020-03']
assert resp.json['data']['series'] == [
{'data': [1, 1], 'label': 'agendas'},
@ -2892,16 +2892,25 @@ def test_api_statistics(app, admin, freezer, event_type_name, event_name):
]
if 'new' in event_name:
url = '/api/statistics/%s/?time_interval=month&group_by=service_ou' % event_name
params['group_by'] = 'service_ou'
else:
url = '/api/statistics/service_ou_%s/?time_interval=month' % event_name
resp = app.get(url, headers=headers)
url = '/api/statistics/service_ou_%s/' % event_name
resp = app.get(url, headers=headers, params=params)
assert resp.json['data']['x_labels'] == ['2020-02', '2020-03']
assert resp.json['data']['series'] == [
{'data': [1, 1], 'label': 'Default organizational unit'},
{'data': [1, 1], 'label': 'Second OU'},
]
if 'new' in event_name:
del params['group_by']
resp = app.get(url, headers=headers, params=params)
assert resp.json['data']['x_labels'] == ['2020-02', '2020-03']
assert len(resp.json['data']['series']) == 1
assert resp.json['data']['series'][0]['data'] == [2, 2]
assert 'count' in resp.json['data']['series'][0]['label']
def test_api_statistics_no_crash_older_drf(app, admin):
headers = basic_authorization_header(admin)