From 7f9403a0bb809a6eda83a37abd667d774d12944b Mon Sep 17 00:00:00 2001 From: David Whittaker Date: Wed, 6 Aug 2025 16:01:54 -0700 Subject: [PATCH 1/4] feat(case): allows filtering for security events only --- src/dispatch/database/service.py | 15 +++++++- .../dispatch/src/case/TableFilterDialog.vue | 37 ++++++++++++++++++- .../static/dispatch/src/case/store.js | 1 + .../static/dispatch/src/router/utils.js | 13 +++++++ .../static/dispatch/src/search/utils.js | 18 ++++++++- 5 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/dispatch/database/service.py b/src/dispatch/database/service.py index 459473730011..5a8329be2acb 100644 --- a/src/dispatch/database/service.py +++ b/src/dispatch/database/service.py @@ -591,6 +591,7 @@ def common_parameters( sort_by: list[str] = Query([], alias="sortBy[]"), descending: list[bool] = Query([], alias="descending[]"), role: UserRoles = Depends(get_current_role), + security_event_only: bool = Query(None, alias="security_event_only"), ): return { "db_session": db_session, @@ -602,11 +603,15 @@ def common_parameters( "descending": descending, "current_user": current_user, "role": role, + "security_event_only": security_event_only, } CommonParameters = Annotated[ - dict[str, int | CurrentUser | DbSession | QueryStr | Json | list[str] | list[bool] | UserRoles], + dict[ + str, + int | CurrentUser | DbSession | QueryStr | Json | list[str] | list[bool] | UserRoles | bool, + ], Depends(common_parameters), ] @@ -676,6 +681,7 @@ def search_filter_sort_paginate( descending: list[bool] = None, current_user: DispatchUser = None, role: UserRoles = UserRoles.member, + security_event_only: bool = None, ): """Common functionality for searching, filtering, sorting, and pagination.""" model_cls = get_class_by_tablename(model) @@ -712,6 +718,13 @@ def search_filter_sort_paginate( else: query = apply_filters(query, filter_spec, model_cls) + # Handle security_event_only filter for Case model + if model == "Case" and security_event_only: + # Use NOT EXISTS to find cases that do NOT have signal instances + from sqlalchemy import exists + + query = query.filter(~exists().where(SignalInstance.case_id == Case.id)) + # Apply tag_all filters using intersect only when necessary for filter in tag_all_filters: query = query.intersect(filter) diff --git a/src/dispatch/static/dispatch/src/case/TableFilterDialog.vue b/src/dispatch/static/dispatch/src/case/TableFilterDialog.vue index 4dac0dc74590..eb74550c0bca 100644 --- a/src/dispatch/static/dispatch/src/case/TableFilterDialog.vue +++ b/src/dispatch/static/dispatch/src/case/TableFilterDialog.vue @@ -79,6 +79,18 @@ /> + + + Security Events + Filter cases based on signal instances + + + @@ -92,7 +104,7 @@