Fix Alert Triage widget rendering blank on array-shaped alert fields - #48
Conversation
Some ECS alert fields (host.name, user.name, process.name, source.ip, destination.ip) come back as arrays instead of scalars for certain correlated/network-derived alerts, even though SecurityAlert._source types them as scalars. The Alert Triage view's group/sort logic assumed a scalar and threw a TypeError on .localeCompare() when it got an array, and with no error boundary anywhere in the view tree, the exception silently unmounted the whole widget to a blank panel. Normalize array-shaped fields to scalars at the single chokepoint that feeds both the triage-alerts and poll-alerts tool paths (AlertsService.getAlerts), add defense-in-depth guards in useAlertSort's sort/group logic, and add a reusable ErrorBoundary around the view so any future unexpected shape shows a visible fallback instead of a silent blank screen. Fixes #47 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The search input's onChange only updated local text state — it never cleared the actual backend filter (paramsRef.current.query) unless the user pressed Enter, pressed Escape, or clicked the filter chip's "x". Deleting the text by any other means (select-and-delete, backspacing without pressing Enter) left the widget filtered on the old value even though the input box and chip both appeared empty. Fix handleInputChange to clear the filter immediately once the input becomes empty, matching the existing Escape/chip-click behavior. Also add a request-token guard in loadAlertsImpl so a slower, older response can never overwrite a fresher one — a related latent race that could cause the same "stuck filtered" symptom intermittently even via the correct clearing paths. Verified against a live local environment: confirmed the widget now reverts to the unfiltered list immediately when the filter text is cleared directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Follow-up fix in this PR: filter stays applied after clearing the search textFound and fixed a second, unrelated bug in the same Alert Triage view while Repro: apply a filter via the search box (types a host name, applies as a Root cause: the search input's Fix:
Verified against a live local environment — the widget now reverts to the |
Summary
Fixes #47.
Some ECS alert fields (
host.name,user.name,process.name,source.ip,destination.ip) come back as arrays instead of scalars for certaincorrelated/network-derived alerts, even though
SecurityAlert._sourcetypesthem as scalars — Elasticsearch does not enforce that shape at the document
level.
The Alert Triage view's group/sort logic (
useAlertSort.ts) assumed a scalarand threw a
TypeErroron.localeCompare()when it got an array. With noerror boundary anywhere in the view tree, that exception silently unmounted
the whole widget — rendering as a blank/black panel with no error message,
reproduced both against a live customer environment and independently against
a second live environment.
Changes
AlertsService.getAlerts()— the single chokepoint that feeds both thetriage-alertsandpoll-alertstool paths. Added a smalltoScalar()helper in
src/shared/field-utils.tsand reused it to simplify an existingone-off array/scalar coercion in
entityDetailService.ts..localeCompare()calls inuseAlertSort.ts(coerce toString(...)) and tightened the group-skipguard to require actual non-empty strings, in case an unnormalized array
ever reaches this hook via another path.
ErrorBoundarycomponent(
src/shared/components/ErrorBoundary/) and wrapped the Alert Triage view'sroot render in it, so any future unexpected data shape shows a visible
fallback instead of silently blanking the widget.
Test plan
npm run typecheckpassesnpm run lintpassesnpx vitest run— 403/403 passing, including new regression tests:src/shared/field-utils.test.tssrc/elastic/service/alertsService.test.ts(reproduces the exactarray-shaped payload observed in the field)
src/views/alert-triage/hooks/useAlertSort.test.ts(reproduces theexact crash)
npm run buildandnpm run mcpb:packsucceedAlert Triage widget, which previously rendered blank on every load, now
renders correctly both unfiltered and when filtered to the specific host
whose alerts have array-shaped fields