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

6.7 KiB

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

  • 1. Create database migration for group_id column

    • 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
  • 2. Update POST /notes endpoint to support multi-metric submissions

    • 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
    • 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
    • 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
    • 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
  • 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
  • 4. Checkpoint — Verify backend changes

    • Ensure all backend changes are syntactically correct, ask the user if questions arise.
  • 5. Replace single-select dropdown with multi-select MetricChipSelector in ComplianceDetailPanel.js

    • 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
    • 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
    • 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
  • 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
  • 7. Update note display to group by group_id

    • 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
    • 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
  • 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