perf: Add user report api

pull/15630/merge^2
feng 2025-07-02 08:05:45 +08:00
parent f685ea457a
commit ae881022dc
4 changed files with 130 additions and 10 deletions

View File

@ -167,10 +167,7 @@ class UserLoginLogViewSet(UserLoginCommonMixin, OrgReadonlyModelViewSet):
def get_queryset(self):
queryset = super().get_queryset()
if current_org.is_root() or not settings.XPACK_ENABLED:
return queryset
users = self.get_org_member_usernames()
queryset = queryset.filter(username__in=users)
queryset = queryset.model.filter_login_queryset_by_org(queryset)
return queryset

View File

@ -255,6 +255,15 @@ class UserLoginLog(models.Model):
reason = old_reason_choices.get(self.reason, self.reason)
return reason
@staticmethod
def filter_login_queryset_by_org(queryset):
from audits.utils import construct_userlogin_usernames
if current_org.is_root() or not settings.XPACK_ENABLED:
return queryset
user_queryset = current_org.get_members()
users = construct_userlogin_usernames(user_queryset)
return queryset.filter(username__in=users)
class Meta:
ordering = ["-datetime", "username"]
verbose_name = _("User login log")

View File

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from django.db.models import Count
from django.http.response import JsonResponse
from django.utils import timezone
from rest_framework.views import APIView
from audits.const import LoginStatusChoices
from audits.models import UserLoginLog
from common.permissions import IsValidLicense
from common.utils import lazyproperty
from common.utils.timezone import local_zero_hour, local_now
from rbac.permissions import RBACPermission
__all__ = ['UserReportApi']
class UserReportApi(APIView):
http_method_names = ['get']
# TODO: Define the required RBAC permissions for this API
rbac_perms = {
}
permission_classes = [RBACPermission, IsValidLicense]
@lazyproperty
def days(self):
count = self.request.query_params.get('days', 1)
return int(count)
@property
def days_to_datetime(self):
if self.days == 1:
return local_zero_hour()
return local_now() - timezone.timedelta(days=self.days)
@lazyproperty
def date_range_list(self):
return [
(local_now() - timezone.timedelta(days=i)).date()
for i in range(self.days - 1, -1, -1)
]
def filter_by_date_range(self, queryset, field_name):
date_range_bounds = self.days_to_datetime.date(), (local_now() + timezone.timedelta(days=1)).date()
return queryset.filter(**{f'{field_name}__range': date_range_bounds})
def get_user_login_metrics(self):
filtered_queryset = self.filter_by_date_range(self.user_login_log_queryset, 'datetime')
data = defaultdict(set)
for t, username in filtered_queryset.values_list('datetime', 'username'):
date_str = str(t.date())
data[date_str].add(username)
metrics = [len(v) for __, v in data]
return metrics
def get_user_login_method_metrics(self):
filtered_queryset = self.filter_by_date_range(self.user_login_log_queryset, 'datetime')
backends = set()
data = defaultdict(lambda: defaultdict(set))
for t, username, backend in filtered_queryset.values_list('datetime', 'username', 'backend'):
backends.add(backend)
date_str = str(t.date())
data[date_str][backend].add(username)
metrics = defaultdict(list)
for backend in backends:
for date_str, usernames in data.items():
metrics[backend].append(len(usernames.get(backend, set())))
return metrics
def get_user_login_region_distribution(self):
filtered_queryset = self.filter_by_date_range(self.user_login_log_queryset, 'datetime')
data = filtered_queryset.values('city').annotate(
user_count=Count('username', distinct=True)
).order_by('-user_count')
return list(data)
@lazyproperty
def user_login_log_queryset(self):
queryset = UserLoginLog.objects.filter(status=LoginStatusChoices.success)
return UserLoginLog.filter_login_queryset_by_org(queryset)
def get(self, request, *args, **kwargs):
query_params = self.request.query_params
data = {}
_all = query_params.get('all')
if _all or query_params.get('user_login_log_metrics'):
metrics = self.get_user_login_metrics()
data.update({
'dates_metrics_date': [date.strftime('%m-%d') for date in self.date_range_list] or ['0'],
'dates_metrics_total': metrics,
})
if _all or query_params.get('user_login_method_metrics'):
metrics = self.get_user_login_method_metrics()
data.update({
'dates_metrics_date': [date.strftime('%m-%d') for date in self.date_range_list] or ['0'],
'dates_metrics_total': metrics,
})
if _all or query_params.get('user_login_region_distribution'):
data.update({
'user_login_region_distribution': self.get_user_login_region_distribution(),
})
return JsonResponse(data, status=200)

View File

@ -1,19 +1,19 @@
import base64
import io
import urllib.parse
from io import BytesIO
from urllib.parse import urlparse
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.http import FileResponse, HttpResponseBadRequest
from django.http import JsonResponse
from django.utils import timezone
from django.conf import settings
from django.http import FileResponse, HttpResponseBadRequest
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from playwright.sync_api import sync_playwright
from django.core.mail import EmailMultiAlternatives
from pdf2image import convert_from_bytes
import base64
from io import BytesIO
from playwright.sync_api import sync_playwright
charts_map = {
"UserActivity": {