Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/dispatch/database/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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),
]

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
37 changes: 36 additions & 1 deletion src/dispatch/static/dispatch/src/case/TableFilterDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@
/>
</v-card>
</v-list-item>
<v-list-item>
<v-card class="mx-auto">
<v-card-title>Security Events</v-card-title>
<v-card-subtitle>Show only cases with a dedicated channel</v-card-subtitle>
<v-checkbox
class="ml-10 mr-5"
v-model="local_security_event_only"
label="Show only Security Event cases"
hint="Show only cases that have a dedicated channel"
/>
</v-card>
</v-list-item>
</v-list>
<v-card-actions>
<v-spacer />
Expand All @@ -93,7 +105,7 @@
</template>

<script setup>
import { ref, computed } from "vue"
import { ref, computed, watch } from "vue"
import { useStore } from "vuex"
import { sum } from "lodash"

Expand Down Expand Up @@ -133,6 +145,7 @@ const local_tag = ref([])
const local_tag_type = ref([])
const local_participant = ref(null)
const local_participant_is_assignee = ref(false)
const local_security_event_only = ref(false)

const case_priority = computed(
() => store.state.case_management.table.options.filters.case_priority
Expand All @@ -144,6 +157,26 @@ const project = computed(() => store.state.case_management.table.options.filters
const status = computed(() => store.state.case_management.table.options.filters.status)
const tag = computed(() => store.state.case_management.table.options.filters.tag)
const tag_type = computed(() => store.state.case_management.table.options.filters.tag_type)
const security_event_only = computed(
() => store.state.case_management.table.options.filters.security_event_only
)
const case_type = computed(() => store.state.case_management.table.options.filters.case_type)

// Initialize local variables when dialog opens
watch(display, (newValue) => {
if (newValue) {
local_case_priority.value = case_priority.value || []
local_case_severity.value = case_severity.value || []
local_selected_case_types.value = case_type.value || []
local_project.value = project.value || []
local_status.value = status.value || []
local_tag.value = tag.value || []
local_tag_type.value = tag_type.value || []
local_participant.value = null
local_participant_is_assignee.value = false
local_security_event_only.value = security_event_only.value || false
}
})

const numFilters = computed(() => {
return sum([
Expand All @@ -155,6 +188,7 @@ const numFilters = computed(() => {
tag.value?.length || 0,
tag_type.value?.length || 0,
local_participant.value == null ? 0 : 1,
local_security_event_only.value ? 1 : 0,
])
})

Expand Down Expand Up @@ -182,6 +216,7 @@ const applyFilters = () => {
tag_type: local_tag_type.value,
participant: filtered_participant,
assignee: filtered_assignee,
security_event_only: local_security_event_only.value,
}

// Commit the mutation to update the filters in the Vuex store
Expand Down
1 change: 1 addition & 0 deletions src/dispatch/static/dispatch/src/case/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ const state = {
end: null,
},
participant: null,
security_event_only: false,
},
q: "",
page: 1,
Expand Down
13 changes: 13 additions & 0 deletions src/dispatch/static/dispatch/src/router/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export default {
flatFilters = { ...flatFilters, ...{ [startKey]: value.start, [endKey]: value.end } }
return
}

// handle boolean values (like security_event_only)
if (typeof value === "boolean") {
flatFilters[key] = value
return
}

each(value, function (item) {
if (["commander", "participant", "assignee"].includes(key)) {
if (has(flatFilters, key)) {
Expand Down Expand Up @@ -71,6 +78,12 @@ export default {
}
return
}

// handle boolean values (like security_event_only)
if (typeof value === "boolean" || value === "true" || value === "false") {
filters[key] = value === "true" || value === true
return
}
if (["status"].includes(key)) {
if (typeof value === "string" || value instanceof String) {
if (has(filters, key)) {
Expand Down
18 changes: 17 additions & 1 deletion src/dispatch/static/dispatch/src/search/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ export default {
createParametersFromTableOptions(options, model, rawFilters) {
let [sortBy, descending] = this.createSortExpression(options.sortBy, options.descending)
let expression = this.createFilterExpression(options.filters, model)

// Extract boolean filters (like security_event_only) and pass them as separate parameters
let booleanFilters = {}
forEach(options.filters, function (value, key) {
if (typeof value === "boolean") {
booleanFilters[key] = value
}
})

delete options.filters
delete options.sortBy

Expand All @@ -37,12 +46,13 @@ export default {
expression = { and: [...rawFilters] }
return {
...options,
...booleanFilters,
sortBy: sortBy,
descending: descending,
filter: JSON.stringify(expression),
}
} else {
return { ...options, sortBy: sortBy, descending: descending }
return { ...options, ...booleanFilters, sortBy: sortBy, descending: descending }
}
}

Expand All @@ -54,6 +64,7 @@ export default {

return {
...options,
...booleanFilters,
sortBy: sortBy,
descending: descending,
filter: JSON.stringify(expression),
Expand Down Expand Up @@ -131,6 +142,11 @@ export default {
],
})
}
} else if (typeof value === "boolean") {
// Handle boolean values (like security_event_only)
// For boolean filters, we'll pass them as raw filters to be handled by the backend
// This is a special case that bypasses the normal filter expression creation
return
} else {
each(value, function (value) {
// filter null/undefined values but allow false
Expand Down
Loading