api: add sms count statistics (#53856)

This commit is contained in:
Valentin Deniaud 2021-05-10 15:40:16 +02:00 committed by Frédéric Péters
parent 6740ded420
commit c4c1312ae2
7 changed files with 153 additions and 4 deletions

View File

@ -16,8 +16,9 @@
from django.conf.urls import url
from .views import JobDetailView
from .views import JobDetailView, StatisticsListView
urlpatterns = [
url(r'jobs/(?P<pk>[\w,-]+)/$', JobDetailView.as_view(), name='api-job'),
url(r'statistics/$', StatisticsListView.as_view(), name='api-statistics-list'),
]

View File

@ -16,10 +16,11 @@
from django.core.exceptions import PermissionDenied
from django.http import Http404, JsonResponse
from django.views.generic import DetailView
from django.views.generic import DetailView, View
from passerelle.base.models import Job
from passerelle.utils import is_authorized
from passerelle.views import get_all_apps
class JobDetailView(DetailView):
@ -45,3 +46,13 @@ class JobDetailView(DetailView):
'done_timestamp': job.done_timestamp,
}
return JsonResponse({'err': 0, 'data': data})
class StatisticsListView(View):
def get(self, request, *args, **kwargs):
sources = []
for app in get_all_apps():
for connector in app.objects.all():
if hasattr(connector, 'get_statistics_entries'):
sources.extend(connector.get_statistics_entries(request))
return JsonResponse({'data': sources})

View File

@ -17,7 +17,7 @@
from django.apps import apps
from django.conf.urls import include, url
from .urls_utils import app_enabled, decorated_includes, manager_required, required
from .urls_utils import app_enabled, decorated_includes, manager_required, required, trust_required
def register_apps_urls(urlpatterns):
@ -54,5 +54,12 @@ def register_apps_urls(urlpatterns):
urls = required(app_enabled(app.label), urls)
urls = required(manager_required, urls)
after_urls.append(url(url_prefix, include(urls), kwargs={'connector': connector_slug}))
if hasattr(obj, 'get_statistics_urls'):
url_prefix = '^api/%s/' % connector_slug
urls = obj.get_statistics_urls()
if urls:
urls = required(app_enabled(app.label), urls)
urls = required(trust_required, urls)
after_urls.append(url(url_prefix, include(urls), kwargs={'connector': connector_slug}))
return before_urls + urlpatterns + after_urls

View File

@ -16,8 +16,10 @@
import logging
import re
from django.conf.urls import url
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.urls import reverse
from django.utils import six
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
@ -99,6 +101,19 @@ class SMSResource(BaseResource):
def get_management_urls(cls):
return import_string('passerelle.sms.urls.management_urlpatterns')
@classmethod
def get_statistics_urls(cls):
from .views import SmsStatisticsView
statistics_urlpatterns = [
url(
r'^(?P<slug>[\w,-]+)/sms-count/$',
SmsStatisticsView.as_view(),
name='api-statistics-sms-%s' % cls.get_connector_slug(),
),
]
return statistics_urlpatterns
def _get_authorized_display(self):
result = []
for key, value in self.AUTHORIZED:
@ -202,6 +217,26 @@ class SMSResource(BaseResource):
self.send_msg(**kwargs)
SMSLog.objects.create(appname=self.get_connector_slug(), slug=self.slug)
def get_statistics_entries(self, request):
return [
{
'name': _('SMS Count (%s)') % self.slug,
'url': request.build_absolute_uri(
reverse('api-statistics-sms-%s' % self.get_connector_slug(), kwargs={'slug': self.slug}),
),
'id': 'sms_count_%s_%s' % (self.get_connector_slug(), self.slug),
'filters': [
{
'id': 'time_interval',
'label': _('Interval'),
'options': [{'id': 'day', 'label': _('Day')}],
'required': True,
'default': 'day',
},
],
}
]
class Meta:
abstract = True

View File

@ -1,12 +1,19 @@
import datetime
from django.apps import apps
from django.contrib import messages
from django.db.models import Count
from django.db.models.functions import TruncDay
from django.http import JsonResponse
from django.utils.timezone import make_aware
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView
from django.views.generic import FormView, View
from passerelle.utils.jsonresponse import APIError
from passerelle.views import GenericConnectorMixin
from .forms import SmsTestSendForm
from .models import SMSLog
class SmsTestSendView(GenericConnectorMixin, FormView):
@ -36,3 +43,39 @@ class SmsTestSendView(GenericConnectorMixin, FormView):
else:
messages.success(self.request, _('An SMS was just sent'))
return super(SmsTestSendView, self).form_valid(form)
class SmsStatisticsView(View):
def get(self, request, *args, **kwargs):
if 'time_interval' in request.GET and request.GET['time_interval'] != 'day':
return JsonResponse({'err': 1, 'err_desc': 'unsupported time interval'})
logs = SMSLog.objects.filter(appname=kwargs['connector'], slug=kwargs['slug'])
if 'start' in request.GET:
start = make_aware(datetime.datetime.strptime(request.GET['start'], '%Y-%m-%d'))
logs = logs.filter(timestamp__gte=start)
if 'end' in request.GET:
end = make_aware(datetime.datetime.strptime(request.GET['end'], '%Y-%m-%d'))
logs = logs.filter(timestamp__lte=end)
logs = logs.annotate(day=TruncDay('timestamp'))
logs = logs.values('day').annotate(total=Count('id')).order_by('day')
x_labels, data = [], []
for log in logs:
x_labels.append(log['day'].strftime('%Y-%m-%d'))
data.append(log['total'])
return JsonResponse(
{
'data': {
'x_labels': x_labels,
'series': [
{
'label': _('SMS Count'),
'data': data,
}
],
}
}
)

View File

@ -15,6 +15,8 @@ except ImportError:
from django.http import Http404
from django.views.debug import technical_404_response
from passerelle.utils import is_trusted
class DecoratedURLPattern(URLPattern):
def resolve(self, *args, **kwargs):
@ -148,3 +150,13 @@ def manager_required(function=None, login_url=None):
if function:
return actual_decorator(function)
return actual_decorator
def trust_required(func):
@wraps(func)
def f(request, *args, **kwargs):
if not (request.user.is_superuser or is_trusted(request)):
raise PermissionDenied()
return func(request, *args, **kwargs)
return f

View File

@ -566,3 +566,43 @@ def test_manager(admin_user, app, connector):
ResourceLog.objects.filter(levelno=30)[0].extra['exception']
== 'no phone number was authorized: 0033188888888'
)
@pytest.mark.parametrize('connector', [OVHSMSGateway], indirect=True)
def test_api_statistics(app, freezer, connector, admin_user):
resp = app.get('/api/statistics/')
url = [x for x in resp.json['data'] if x['id'] == 'sms_count_ovh_ovhsmsgateway'][0]['url']
assert app.get(url, status=403)
login(app)
resp = app.get(url)
assert len(resp.json['data']['series'][0]['data']) == 0
freezer.move_to('2021-01-01 12:00')
for _ in range(5):
SMSLog.objects.create(appname='ovh', slug='ovhsmsgateway')
freezer.move_to('2021-02-03 13:00')
for _ in range(3):
SMSLog.objects.create(appname='ovh', slug='ovhsmsgateway')
freezer.move_to('2021-02-06 13:00')
SMSLog.objects.create(appname='ovh', slug='ovhsmsgateway')
SMSLog.objects.create(appname='ovh', slug='other')
resp = app.get(url + '?time_interval=day')
assert resp.json['data'] == {
'x_labels': ['2021-01-01', '2021-02-03', '2021-02-06'],
'series': [{'label': 'SMS Count', 'data': [5, 3, 1]}],
}
resp = app.get(url + '?start=2021-02-04&end=2021-02-07')
assert resp.json['data'] == {
'x_labels': ['2021-02-06'],
'series': [{'label': 'SMS Count', 'data': [1]}],
}
# invalid time_interval
resp = app.get(url + '?time_interval=month')
assert resp.json['err'] == 1