diff --git a/passbook/admin/api/overview.py b/passbook/admin/api/overview.py index 7dc08a326..801ad8426 100644 --- a/passbook/admin/api/overview.py +++ b/passbook/admin/api/overview.py @@ -1,6 +1,5 @@ """passbook administration overview""" from django.core.cache import cache -from django.http import response from drf_yasg2.utils import swagger_auto_schema from rest_framework.fields import SerializerMethodField from rest_framework.permissions import IsAdminUser @@ -61,7 +60,7 @@ class AdministrationOverviewSerializer(Serializer): """Get cached flow count""" return len(cache.keys("flow_*")) - def create(self, request: Request) -> response: + def create(self, request: Request) -> Response: raise NotImplementedError def update(self, request: Request) -> Response: diff --git a/passbook/admin/views/overview.py b/passbook/admin/views/overview.py index e4d1ed525..c8874b3b2 100644 --- a/passbook/admin/views/overview.py +++ b/passbook/admin/views/overview.py @@ -4,8 +4,6 @@ from typing import Union from django.conf import settings from django.contrib.messages.views import SuccessMessageMixin from django.core.cache import cache -from django.db.models import Count -from django.db.models.fields.json import KeyTextTransform from django.http.request import HttpRequest from django.http.response import HttpResponse from django.urls import reverse_lazy @@ -18,7 +16,6 @@ from passbook import __version__ from passbook.admin.forms.overview import FlowCacheClearForm, PolicyCacheClearForm from passbook.admin.mixins import AdminRequiredMixin from passbook.admin.tasks import VERSION_CACHE_KEY, update_latest_version -from passbook.audit.models import Event, EventAction from passbook.core.models import Provider, User from passbook.policies.models import Policy @@ -39,27 +36,12 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView): return parse(__version__) return parse(version_in_cache) - def get_most_used_applications(self): - """Get Most used applications, total login counts and unique users that have used them.""" - return ( - Event.objects.filter(action=EventAction.AUTHORIZE_APPLICATION) - .exclude(context__authorized_application=None) - .annotate(application=KeyTextTransform("authorized_application", "context")) - .annotate(user_pk=KeyTextTransform("pk", "user")) - .values("application") - .annotate(total_logins=Count("application")) - .annotate(unique_users=Count("user_pk", distinct=True)) - .values("unique_users", "application", "total_logins") - .order_by("-total_logins")[:15] - ) - def get_context_data(self, **kwargs): kwargs["policy_count"] = len(Policy.objects.all()) kwargs["user_count"] = len(User.objects.all()) - 1 # Remove anonymous user kwargs["provider_count"] = len(Provider.objects.all()) kwargs["version"] = parse(__version__) kwargs["version_latest"] = self.get_latest_version() - kwargs["most_used_applications"] = self.get_most_used_applications() kwargs["providers_without_application"] = Provider.objects.filter( application=None ) diff --git a/passbook/audit/api.py b/passbook/audit/api.py index 0208935b5..e19b5ecf1 100644 --- a/passbook/audit/api.py +++ b/passbook/audit/api.py @@ -1,8 +1,15 @@ """Audit API Views""" -from rest_framework.serializers import ModelSerializer +from django.db.models.aggregates import Count +from django.db.models.fields.json import KeyTextTransform +from drf_yasg2.utils import swagger_auto_schema +from rest_framework.decorators import action +from rest_framework.fields import DictField, IntegerField +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.serializers import ModelSerializer, Serializer from rest_framework.viewsets import ReadOnlyModelViewSet -from passbook.audit.models import Event +from passbook.audit.models import Event, EventAction class EventSerializer(ModelSerializer): @@ -22,8 +29,42 @@ class EventSerializer(ModelSerializer): ] +class EventTopPerUserSerialier(Serializer): + """Response object of Event's top_per_user""" + + application = DictField() + counted_events = IntegerField() + unique_users = IntegerField() + + def create(self, request: Request) -> Response: + raise NotImplementedError + + def update(self, request: Request) -> Response: + raise NotImplementedError + + class EventViewSet(ReadOnlyModelViewSet): """Event Read-Only Viewset""" queryset = Event.objects.all() serializer_class = EventSerializer + + @swagger_auto_schema( + method="GET", responses={200: EventTopPerUserSerialier(many=True)} + ) + @action(detail=False, methods=["GET"]) + def top_per_user(self, request: Request): + """Get the top_n events grouped by user count""" + filtered_action = request.query_params.get("filter_action", EventAction.LOGIN) + top_n = request.query_params.get("top_n", 15) + return Response( + Event.objects.filter(action=filtered_action) + .exclude(context__authorized_application=None) + .annotate(application=KeyTextTransform("authorized_application", "context")) + .annotate(user_pk=KeyTextTransform("pk", "user")) + .values("application") + .annotate(counted_events=Count("application")) + .annotate(unique_users=Count("user_pk", distinct=True)) + .values("unique_users", "application", "counted_events") + .order_by("-counted_events")[:top_n] + )