diff --git a/src/dispatch/database/service.py b/src/dispatch/database/service.py index 459473730011..32e34f85b739 100644 --- a/src/dispatch/database/service.py +++ b/src/dispatch/database/service.py @@ -10,7 +10,7 @@ from pydantic import Json from six import string_types from sortedcontainers import SortedSet -from sqlalchemy import Table, and_, desc, func, not_, or_, orm +from sqlalchemy import Table, and_, desc, func, not_, or_, orm, exists from sqlalchemy.exc import InvalidRequestError, ProgrammingError from sqlalchemy.orm import mapperlib, Query as SQLAlchemyQuery from sqlalchemy_filters import apply_pagination, apply_sort @@ -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,11 @@ 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 + 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 9b045561c330..19bdd1fa42e9 100644 --- a/src/dispatch/static/dispatch/src/case/TableFilterDialog.vue +++ b/src/dispatch/static/dispatch/src/case/TableFilterDialog.vue @@ -80,6 +80,18 @@ /> + + + Security Events + Show only cases with a dedicated channel + + + @@ -93,7 +105,7 @@