Optimize default case list metrics

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
funnywolf
2026-07-10 21:38:30 +08:00
co-authored by Copilot
parent 4fc5f1047a
commit aa75eed5e3
2 changed files with 82 additions and 13 deletions
+47 -7
View File
@@ -1,21 +1,20 @@
from django.db.models import Count, Min
from django.db.models import Count, DateTimeField, IntegerField, Min, OuterRef, Subquery, Value
from django.db.models.functions import Coalesce
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets, permissions
from rest_framework.filters import OrderingFilter, SearchFilter
from apps.accounts.permissions import IsBusinessWriterOrReadOnly
from apps.alerts.models import Alert
from apps.audit.mixins import AuditActorMixin
from apps.common.advanced_filters import AdvancedFilterBackend
from apps.playbooks.models import Playbook
from .models import Case
from .serializers import CaseListSerializer, CaseSerializer
class CaseViewSet(AuditActorMixin, viewsets.ModelViewSet):
queryset = Case.objects.select_related("assignee").annotate(
alert_count=Count("alerts", distinct=True),
playbook_count=Count("playbooks", distinct=True),
first_alert_seen_time=Min("alerts__first_seen_time"),
).order_by("-created_at")
queryset = Case.objects.select_related("assignee").order_by("-created_at")
serializer_class = CaseSerializer
permission_classes = [permissions.IsAuthenticated, IsBusinessWriterOrReadOnly]
lookup_field = "id"
@@ -71,8 +70,49 @@ class CaseViewSet(AuditActorMixin, viewsets.ModelViewSet):
"correlation_uid": "text",
}
def is_ordering_by_relation_count(self):
raw_ordering = self.request.query_params.get("ordering", "")
ordering_fields = {field.strip().lstrip("-") for field in raw_ordering.split(",")}
return bool(ordering_fields & {"alert_count", "playbook_count"})
def annotate_list_metrics(self, queryset):
if self.is_ordering_by_relation_count():
return queryset.annotate(
alert_count=Count("alerts", distinct=True),
playbook_count=Count("playbooks", distinct=True),
first_alert_seen_time=Min("alerts__first_seen_time"),
)
alert_count = (
Alert.objects
.filter(case_id=OuterRef("pk"))
.order_by()
.values("case_id")
.annotate(count=Count("id"))
.values("count")[:1]
)
playbook_count = (
Playbook.objects
.filter(case_id=OuterRef("pk"))
.order_by()
.values("case_id")
.annotate(count=Count("id"))
.values("count")[:1]
)
first_alert_seen_time = (
Alert.objects
.filter(case_id=OuterRef("pk"), first_seen_time__isnull=False)
.order_by("first_seen_time")
.values("first_seen_time")[:1]
)
return queryset.annotate(
alert_count=Coalesce(Subquery(alert_count, output_field=IntegerField()), Value(0)),
playbook_count=Coalesce(Subquery(playbook_count, output_field=IntegerField()), Value(0)),
first_alert_seen_time=Subquery(first_alert_seen_time, output_field=DateTimeField()),
)
def get_queryset(self):
queryset = super().get_queryset()
queryset = self.annotate_list_metrics(super().get_queryset())
if self.action == "list":
return queryset.defer("investigation_report_ai_json")
return queryset
@@ -8,7 +8,7 @@ from types import SimpleNamespace
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.db import connection, reset_queries
from django.db.models import CharField, Count, IntegerField, Min, OuterRef, Q, Subquery, Value
from django.db.models import CharField, Count, DateTimeField, IntegerField, Min, OuterRef, Q, Subquery, Value
from django.db.models.functions import Cast, Coalesce, Concat
from django.utils import timezone
@@ -18,6 +18,7 @@ from apps.audit.models import AuditLog
from apps.cases.models import Case
from apps.common.cursor_pagination import paginate_created_at_cursor
from apps.dashboard.views import build_dashboard_overview
from apps.playbooks.models import Playbook
HOT_SEARCH_TOKEN = "perf-hot-auth"
@@ -158,11 +159,39 @@ class Command(BaseCommand):
}
def scenarios(self, *, page_size, deep_offset):
def case_queryset():
def case_queryset(*, with_count_order=False):
if with_count_order:
return Case.objects.select_related("assignee").annotate(
alert_count=Count("alerts", distinct=True),
playbook_count=Count("playbooks", distinct=True),
first_alert_seen_time=Min("alerts__first_seen_time"),
).order_by("-created_at")
alert_count = (
Alert.objects
.filter(case_id=OuterRef("pk"))
.order_by()
.values("case_id")
.annotate(count=Count("id"))
.values("count")[:1]
)
playbook_count = (
Playbook.objects
.filter(case_id=OuterRef("pk"))
.order_by()
.values("case_id")
.annotate(count=Count("id"))
.values("count")[:1]
)
first_alert_seen_time = (
Alert.objects
.filter(case_id=OuterRef("pk"), first_seen_time__isnull=False)
.order_by("first_seen_time")
.values("first_seen_time")[:1]
)
return Case.objects.select_related("assignee").annotate(
alert_count=Count("alerts", distinct=True),
playbook_count=Count("playbooks", distinct=True),
first_alert_seen_time=Min("alerts__first_seen_time"),
alert_count=Coalesce(Subquery(alert_count, output_field=IntegerField()), Value(0)),
playbook_count=Coalesce(Subquery(playbook_count, output_field=IntegerField()), Value(0)),
first_alert_seen_time=Subquery(first_alert_seen_time, output_field=DateTimeField()),
).order_by("-created_at")
def alert_queryset(*, with_artifact_count=False):
@@ -210,7 +239,7 @@ class Command(BaseCommand):
("cases.default_page", lambda: list_count(case_queryset())),
("cases.deep_page", lambda: list_count(case_queryset()[deep_offset:deep_offset + page_size])),
("cases.filter_status_severity", lambda: list_count(case_queryset().filter(status__in=["New", "In Progress"], severity__in=["High", "Critical"]))),
("cases.order_alert_count", lambda: list_count(case_queryset().order_by("-alert_count", "-created_at"))),
("cases.order_alert_count", lambda: list_count(case_queryset(with_count_order=True).order_by("-alert_count", "-created_at"))),
("cases.search_hot", lambda: list_count(case_queryset().filter(Q(case_id__icontains=HOT_SEARCH_TOKEN) | Q(title__icontains=HOT_SEARCH_TOKEN) | Q(description__icontains=HOT_SEARCH_TOKEN) | Q(summary__icontains=HOT_SEARCH_TOKEN) | Q(correlation_uid__icontains=HOT_SEARCH_TOKEN)))),
("cases.search_rare", lambda: list_count(case_queryset().filter(Q(title__icontains=RARE_SEARCH_TOKEN) | Q(description__icontains=RARE_SEARCH_TOKEN)))),
("alerts.default_page", lambda: list_count(alert_queryset())),