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 @@