Add multi-metric note selection to compliance detail panel
This commit is contained in:
105
.kiro/specs/compliance-multi-metric-notes/tasks.md
Normal file
105
.kiro/specs/compliance-multi-metric-notes/tasks.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user