Files
cve-dashboard/.kiro/specs/compliance-multi-metric-notes/tasks.md

106 lines
6.7 KiB
Markdown
Raw Normal View History

# Implementation Plan: Multi-Metric Notes for Compliance Detail Panel
## Overview
Extend the compliance notes system so a single note can be associated with multiple metrics in one action. Changes span three layers: a new migration script adding `group_id` to `compliance_notes`, updates to the `POST /notes` endpoint in `backend/routes/compliance.js` to accept `metric_ids` (array) and insert rows transactionally, and frontend changes in `ComplianceDetailPanel.js` to replace the single-select dropdown with a multi-select chip selector and group notes by `group_id` in the display.
## Tasks
- [x] 1. Create database migration for `group_id` column
- [x] 1.1 Create `backend/migrations/add_compliance_notes_group_id.js`
- Add `group_id TEXT` column to `compliance_notes` table via `ALTER TABLE`
- Create index `idx_compliance_notes_group` on `compliance_notes(group_id)`
- Backfill existing rows: `UPDATE compliance_notes SET group_id = 'legacy-' || id WHERE group_id IS NULL`
- Follow the existing migration pattern (sqlite3, serialize, console logging)
- _Requirements: 6.1, 6.2, 6.3_
- [x] 2. Update `POST /notes` endpoint to support multi-metric submissions
- [x] 2.1 Modify the `POST /notes` handler in `backend/routes/compliance.js` to accept `metric_ids` array
- Accept `metric_ids` (array of strings) as an alternative to `metric_id` (single string)
- When both are provided, `metric_ids` takes precedence
- When neither is provided, return 400 with `"metric_id or metric_ids is required"`
- When `metric_ids` is provided but is not an array, return 400 with `"metric_ids must be an array"`
- Normalize single `metric_id` into a one-element array internally so the rest of the logic is uniform
- _Requirements: 5.1, 5.4, 5.5_
- [x] 2.2 Add validation for `metric_ids` array entries
- Validate that `metric_ids` has at least one entry; return 400 with `"At least one metric ID is required"` if empty
- Validate each entry is a non-empty string of 50 characters or fewer; return 400 with `"Invalid metric_id at index N"` on failure
- Reject the entire request if any entry fails validation (atomic rejection, no partial inserts)
- _Requirements: 5.2, 5.6_
- [x] 2.3 Implement transactional multi-row insert with `group_id`
- Generate a `group_id` using `crypto.randomUUID()` for each submission (single or multi)
- Wrap all inserts in `BEGIN TRANSACTION` / `COMMIT` with `ROLLBACK` on error
- Insert one `compliance_notes` row per metric ID, all sharing the same `note`, `group_id`, `created_by`, and `created_at`
- _Requirements: 3.1, 3.2, 5.3, 6.1, 6.2_
- [x] 2.4 Update the response to return all created note rows
- After commit, query all created rows (joined with `users` for `username`) and return as `{ notes: [...] }`
- Each row includes `id`, `hostname`, `metric_id`, `note`, `group_id`, `created_at`, `created_by`
- Return HTTP 201 status
- _Requirements: 5.7_
- [x] 3. Update `GET /items/:hostname` to include `group_id` in notes response
- Add `cn.group_id` to the SELECT in the notes query within the `GET /items/:hostname` handler
- The existing query already fetches notes for the hostname; just add the column
- No other changes to this endpoint
- _Requirements: 6.3, 6.4_
- [x] 4. Checkpoint — Verify backend changes
- Ensure all backend changes are syntactically correct, ask the user if questions arise.
- [x] 5. Replace single-select dropdown with multi-select MetricChipSelector in `ComplianceDetailPanel.js`
- [x] 5.1 Replace `noteMetric` (string) state with `selectedMetrics` (array) state
- Initialize `selectedMetrics` with the first active metric's ID when detail loads (matching current default behavior)
- When there is exactly one active metric, pre-select it as a non-removable selection
- _Requirements: 1.3, 1.4_
- [x] 5.2 Build the multi-select chip-based metric selector UI
- Replace the existing `<select>` dropdown with a row of clickable `MetricChip` components
- Each active metric renders as a chip; selected chips get a highlighted border/background
- Clicking an unselected chip adds it to `selectedMetrics`
- Clicking a selected chip removes it, unless it is the only selected chip (minimum 1 selection)
- Only show the chip selector when there are 2+ active metrics (single metric is auto-selected)
- Style chips using existing `MetricChip` component patterns and category colors
- _Requirements: 1.1, 1.2, 1.5_
- [x] 5.3 Add Select All / Deselect All toggle
- Show a text toggle above or beside the chip row when there are 2+ active metrics
- "Select All" selects all active metrics; label changes to "Deselect All"
- "Deselect All" deselects all except the first metric (minimum selection invariant)
- Toggle label is a pure function of whether all metrics are selected
- Hide the toggle when there is only one active metric
- _Requirements: 2.1, 2.2, 2.3_
- [x] 6. Update note submission logic to send `metric_ids` array
- Modify `handleAddNote` to send `{ hostname, metric_ids: selectedMetrics, note }` instead of `{ hostname, metric_id: noteMetric, note }`
- Disable the submit button when `selectedMetrics` is empty or note text is empty
- On success, clear note text, refresh the detail panel, and retain the current metric selection
- Handle the new response shape (`{ notes: [...] }`) from the updated API
- _Requirements: 3.1, 3.4, 3.5_
- [x] 7. Update note display to group by `group_id`
- [x] 7.1 Add note grouping logic
- Group the `detail.notes` array by `group_id` before rendering
- Notes sharing a `group_id` are displayed as a single card with multiple `MetricChip` badges
- Notes without a `group_id` (pre-migration legacy, should not occur after backfill) render as individual entries
- Maintain reverse chronological order (newest `created_at` first) across groups
- _Requirements: 4.1, 4.2, 4.3, 6.4_
- [x] 7.2 Update the note card rendering
- For grouped notes, display all associated `MetricChip` components in the card header
- For single-metric notes, display one `MetricChip` (matching current behavior)
- Preserve existing note card styling (background, border, padding, typography)
- _Requirements: 4.1, 4.2_
- [x] 8. Final checkpoint — Verify full feature
- Ensure frontend compiles without errors, ask the user if questions arise.
## Notes
- No automated tests — feature is validated manually per user preference
- No new components or route modules required; all changes are scoped to existing files plus one migration
- The `group_id` backfill ensures legacy notes render correctly without null checks
- Each task references specific requirements for traceability