feat(web): add incident dashboard workspace - #218
Open
ThatCodeBabe wants to merge 1 commit into
Open
Conversation
Adds `apps/web/app/incidents/`, a workspace for reviewing active security investigations: an incident listing, status and priority filters, and priority indicators. The `Incident` type mirrors the string columns on the existing `Incident` Prisma model rather than inventing a parallel shape, so the dashboard cannot drift from what the database can store. Statuses, priorities and severities are exhaustive unions over those documented values. Behaviour notes: - Filter counts are computed from the unfiltered incident set, so each control shows how many incidents it would reveal rather than how many are currently on screen. A filter matching nothing is disabled rather than hidden, keeping the control set stable as data changes. - Status and priority filters compose; the clear control appears only once a filter is applied. - Priority is never conveyed by colour alone. The indicator dot is `aria-hidden` and the literal priority text is always rendered, so the most important signal on the page survives for anyone who cannot separate the hues. - The summary list carries `role="group"`. A bare `<dl>` exposes no ARIA role, which would cause its `aria-label` to be dropped by assistive technology. - Incidents are passed as a prop defaulting to the mock set, matching how the other workspaces in `apps/web/app` source data, so wiring this to the real API later needs no change to the view. 15 tests cover the listing, both filters and their combination, the pressed state, the result count, clearing, the disabled-filter rule, the empty state and the summary counts. Full dashboard suite: 56 passing across 6 suites. Lint and prettier clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #121
Summary
Adds
apps/web/app/incidents/— a dedicated workspace for reviewing active security investigations, with an incident listing, status and priority filters, and priority indicators.IncidentListIncidentFilters, one control per lifecycle stateIncidentDashboardinpage.tsxIt follows the conventions already set by
apps/web/app/threat-hunting/: named plus default export,types.tscarrying types and mock data, co-located plain CSS, and apage.spec.tsxalongside.The types mirror the database, not an invented shape
prisma/schema.prismaalready has anIncidentmodel, added by20260719120000_add_incidents. Rather than define a parallel shape, the unions here are taken from its documented column values:new | open | acknowledged | investigating | contained | resolved | closed | reopenedlow | medium | high | criticalp1 | p2 | p3 | p4So the dashboard cannot drift from what the database can actually store, and wiring it to the real API later is a data-source change rather than a type rewrite.
incidentsis already a prop defaulting to the mock set for exactly that reason.Details worth reviewing
Filter counts come from the unfiltered set. Each control shows how many incidents it would reveal, not how many are currently on screen — otherwise every count reads 0 the moment you filter. A filter matching nothing is disabled rather than hidden, so the control set stays stable as data changes instead of shifting under the cursor.
Priority is never conveyed by colour alone. The indicator dot is
aria-hiddenand the literal text ("P1 — Critical") always renders. Colour alone would leave the most important signal on the page unreadable to anyone who cannot separate the hues.The summary list carries
role="group". A bare<dl>exposes no ARIA role, so itsaria-labelis dropped by assistive technology. My own test caught this — the group was unreachable by accessible name, which is exactly what a screen reader user would have hit.Terminal incidents are de-emphasised rather than hidden, so resolved and closed work stays auditable without competing for attention.
Tests
15 tests; 56 passing across the full dashboard suite, no regressions. Lint and prettier clean.
Covered: the listing · a title per incident · a status indicator per incident · the readable priority label · filtering by status · by priority · both combined · the pressed state · the result count · clearing · the clear control appearing only once a filter is applied · the disabled-filter rule · the empty state · and the unassigned summary count.
One note on the diff:
getRows()selects direct children of the list rather than usinggetAllByRole('listitem'). Each row contains a nested tag list, so the role query counts tag chips as rows — 25 instead of 8. The nested list is valid markup, so the query is the thing that needed to be precise.Follow-up
The view reads from
MOCK_INCIDENTS, matching how the other workspaces inapps/web/appcurrently source their data. Connecting it to theIncidenttable through the API is a natural next issue, and the prop seam is already there for it — happy to pick that up if useful.