106 lines
6.7 KiB
Markdown
106 lines
6.7 KiB
Markdown
# 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
|