Files
cve-dashboard/.kiro/specs/compliance-chart-replacements/tasks.md

9.5 KiB
Raw Blame History

Implementation Plan: Compliance Chart Replacements

Overview

Replace Chart 4 (MTTR by Team) and Chart 5 (Most Persistent Findings) in the compliance dashboard with two new visualizations: Aging Findings Distribution (stacked bar by age bucket) and Net Change Waterfall (per-cycle net movement). The implementation reuses existing route paths, requires no database migrations, and extracts pure functions for testability.

Tasks

  • 1. Extract pure backend functions and implement aging endpoint

    • 1.1 Create bucketAgingItems(items) pure function in cve-dashboard/backend/routes/compliance.js

      • Export a pure function that accepts an array of { seen_count, team } objects
      • Bucket each item into one of four age groups: 1 cycle (seen_count = 1), 23 cycles (23), 46 cycles (46), 7+ cycles (≥ 7)
      • Pivot into one object per bucket with per-team counts and a total, sorted by ascending age
      • Return the array in the response shape defined in the design: [{ bucket, total, STEAM, ACCESS-ENG, ACCESS-OPS, INTELDEV }]
      • Requirements: 1.1, 1.2, 1.3
    • 1.2 Replace the /mttr route handler with the aging endpoint

      • Remove the old MTTR SQL query and handler logic
      • Add a new handler at GET /mttr that queries active compliance items grouped by bucket CASE expression and team
      • Use the SQL from the design: SELECT CASE WHEN seen_count = 1 THEN '1 cycle' ... END AS bucket, team, COUNT(*) AS count FROM compliance_items WHERE status = 'active' GROUP BY bucket, team ORDER BY ...
      • Call bucketAgingItems() to pivot the flat rows, then return { aging: [...] }
      • Return { aging: [] } with HTTP 200 when no active findings exist
      • Return HTTP 500 with { error: "Database error" } on SQLite errors
      • Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 6.3
    • * 1.3 Write property test for aging bucketing (Property 1)

      • Property 1: Aging bucketing correctness and conservation
      • Create cve-dashboard/backend/__tests__/compliance-aging-bucketing.property.test.js
      • Generator: array of { seen_count: fc.integer({ min: 1, max: 200 }), team: fc.constantFrom('STEAM', 'ACCESS-ENG', 'ACCESS-OPS', 'INTELDEV') }
      • Assert: every item maps to exactly one bucket, per-team counts sum to bucket total, all bucket totals sum to input length
      • Validates: Requirements 1.1, 1.2
    • * 1.4 Write unit tests for aging endpoint

      • Create cve-dashboard/backend/__tests__/compliance-aging-endpoint.test.js
      • Test correct JSON shape with known seed data (Req 1.3)
      • Test empty array returned when no active items exist (Req 1.4)
      • Test HTTP 500 returned on DB error (Req 1.5)
      • Requirements: 1.3, 1.4, 1.5
  • 2. Implement waterfall endpoint with pure function

    • 2.1 Create computeWaterfall(uploads) pure function in cve-dashboard/backend/routes/compliance.js

      • Export a pure function that accepts an ordered array of { report_date, new_count, recurring_count, resolved_count }
      • Iterate with a running accumulator: first row starts at 0, each subsequent row's start = previous row's end
      • Compute end = start + new_count + recurring_count - resolved_count for each row
      • Return array of { date, start, new_count, recurring_count, resolved_count, end }
      • Requirements: 3.1, 3.2, 3.3
    • 2.2 Replace the /top-recurring route handler with the waterfall endpoint

      • Remove the old persistent-findings SQL query and handler logic
      • Add a new handler at GET /top-recurring that queries compliance_uploads ordered by report_date ASC
      • Use COALESCE for null safety on count columns
      • Call computeWaterfall() to build the chain, then return { waterfall: [...] }
      • Return { waterfall: [] } with HTTP 200 when no uploads exist
      • Return HTTP 500 with { error: "Database error" } on SQLite errors
      • Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 6.4
    • * 2.3 Write property test for waterfall computation (Property 2)

      • Property 2: Waterfall chain linkage and arithmetic invariant
      • Create cve-dashboard/backend/__tests__/compliance-waterfall-chain.property.test.js
      • Generator: array of { new_count: fc.nat({ max: 50 }), recurring_count: fc.nat({ max: 50 }), resolved_count: fc.nat({ max: 50 }), report_date: fc.date().map(d => d.toISOString().slice(0, 10)) }
      • Assert: first row start = 0, each row N start = row N-1 end, each row end = start + new_count + recurring_count - resolved_count
      • Validates: Requirements 3.1, 3.2, 3.3
    • * 2.4 Write unit tests for waterfall endpoint

      • Create cve-dashboard/backend/__tests__/compliance-waterfall-endpoint.test.js
      • Test empty array returned when no uploads exist (Req 3.5)
      • Test HTTP 500 returned on DB error (Req 3.6)
      • Test correct chain computation with known seed data (Req 3.1, 3.2, 3.3)
      • Requirements: 3.1, 3.2, 3.3, 3.5, 3.6
  • 3. Checkpoint — Backend complete

    • Ensure all tests pass, ask the user if questions arise.
  • 4. Implement frontend AgingChart component

    • 4.1 Replace MttrChart with AgingChart in cve-dashboard/frontend/src/components/pages/ComplianceChartsPanel.js

      • Remove the MttrChart function component entirely
      • Add AgingChart({ data }) that renders a vertical stacked BarChart using Recharts
      • X-axis: bucket labels (1 cycle, 23 cycles, 46 cycles, 7+ cycles)
      • Y-axis: finding count with monospace axis styling
      • One <Bar> per team key from TEAM_COLORS using stackId="aging"
      • Use DarkTooltip for hover showing bucket label, per-team counts, and total
      • Render <NoData /> when data array is empty
      • Wrap in <ChartCard title="Aging Findings Distribution" subtitle="Active findings by age bucket — stacked by team" />
      • Place in the Chart 4 grid slot previously held by MTTR
      • Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7
    • * 4.2 Write unit tests for AgingChart

      • Test <NoData /> renders for empty data (Req 2.5)
      • Test ChartCard renders with correct title (Req 2.3)
      • Requirements: 2.3, 2.5
  • 5. Implement frontend WaterfallChart component

    • 5.1 Replace RecurringChart with WaterfallChart in cve-dashboard/frontend/src/components/pages/ComplianceChartsPanel.js

      • Remove the RecurringChart function component entirely
      • Add WaterfallChart({ data }) that renders a vertical BarChart using Recharts
      • Stacked bars: transparent base <Bar dataKey="start" stackId="w" fill="transparent" />, red new <Bar dataKey="new_count" fill="#EF4444" />, amber recurring <Bar dataKey="recurring_count" fill="#F59E0B" />
      • Separate bar for resolved: <Bar dataKey="resolved_count" fill="#10B981" /> (not stacked, matching existing DeltaChart pattern)
      • X-axis: cycle dates formatted via fmtDate, Y-axis: finding count with monospace styling
      • Custom WaterfallTooltip showing date, start, new, recurring, resolved, and end values
      • Render <NoData /> when data array is empty
      • Wrap in <ChartCard title="Net Change Waterfall" subtitle="Per-cycle net movement: start → +new → +recurring → resolved → end" />
      • Place in the Chart 5 grid slot previously held by Most Persistent Findings
      • Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8
    • * 5.2 Write unit tests for WaterfallChart

      • Test <NoData /> renders for empty data (Req 4.5)
      • Test ChartCard renders with correct title (Req 4.6)
      • Requirements: 4.5, 4.6
  • 6. Update state and fetch logic in ComplianceChartsPanel

    • 6.1 Replace state variables and fetch calls

      • Replace const [mttr, setMttr] = useState([]) with const [aging, setAging] = useState([])
      • Replace const [recurring, setRecurring] = useState([]) with const [waterfall, setWaterfall] = useState([])
      • In the Promise.all fetch block, update the /compliance/mttr response handler: setAging(d.aging || [])
      • Update the /compliance/top-recurring response handler: setWaterfall(d.waterfall || [])
      • Keep /compliance/trends and /archer-tickets/status-trend fetches unchanged
      • Pass aging to <AgingChart data={aging} /> and waterfall to <WaterfallChart data={waterfall} />
      • Ensure individual fetch failures render <NoData /> without affecting other charts
      • Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 6.1, 6.2
    • * 6.2 Write integration tests for dashboard fetch logic

      • Test that dashboard fetches /compliance/mttr and /compliance/top-recurring on mount (Req 5.1, 5.2)
      • Test that /compliance/trends and /archer-tickets/status-trend are still fetched (Req 5.3)
      • Test that all four fetches run in parallel via Promise.all (Req 5.4)
      • Test that a single fetch failure does not break other charts (Req 5.5)
      • Requirements: 5.1, 5.2, 5.3, 5.4, 5.5
  • 7. Final checkpoint — All tests pass

    • Ensure all tests pass, ask the user if questions arise.

Notes

  • Tasks marked with * are optional and can be skipped for faster MVP
  • Each task references specific requirements for traceability
  • Checkpoints ensure incremental validation after backend and full integration
  • Property tests validate universal correctness properties from the design (Properties 1 and 2)
  • Unit tests validate specific examples and edge cases
  • Pure functions bucketAgingItems() and computeWaterfall() are extracted first to enable property-based testing
  • No new route paths are introduced — /mttr and /top-recurring are reused in-place
  • No database migrations are needed — all data comes from existing columns