Skip to content

feat(web): add incident dashboard workspace - #218

Open
ThatCodeBabe wants to merge 1 commit into
MD-Creative-Production:mainfrom
ThatCodeBabe:feat/incident-dashboard-121
Open

feat(web): add incident dashboard workspace#218
ThatCodeBabe wants to merge 1 commit into
MD-Creative-Production:mainfrom
ThatCodeBabe:feat/incident-dashboard-121

Conversation

@ThatCodeBabe

Copy link
Copy Markdown
Contributor

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.

Requirement Where
Incident listing IncidentList
Status filters IncidentFilters, one control per lifecycle state
Priority indicators colour-coded dot plus literal priority text on every row
Dashboard implemented IncidentDashboard in page.tsx
Incident filters working status and priority, combinable, with a clear control
Status indicators visible status and severity badges per row

It follows the conventions already set by apps/web/app/threat-hunting/: named plus default export, types.ts carrying types and mock data, co-located plain CSS, and a page.spec.tsx alongside.

The types mirror the database, not an invented shape

prisma/schema.prisma already has an Incident model, added by 20260719120000_add_incidents. Rather than define a parallel shape, the unions here are taken from its documented column values:

  • statusnew | open | acknowledged | investigating | contained | resolved | closed | reopened
  • severitylow | medium | high | critical
  • priorityp1 | p2 | p3 | p4

So 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. incidents is 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-hidden and 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 its aria-label is 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 using getAllByRole('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 in apps/web/app currently source their data. Connecting it to the Incident table through the API is a natural next issue, and the prop seam is already there for it — happy to pick that up if useful.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build Incident Dashboard

1 participant