Files
cve-dashboard/.kiro/specs/atlas-metrics-report/tasks.md

11 KiB
Raw Blame History

Implementation Plan: Atlas Metrics Report

Overview

Add a tab system to the Metric Graphs panel on the ReportingPage, with an "Ivanti Findings" tab (existing donuts) and an "Atlas Coverage" tab (three new donut charts). A new GET /api/atlas/metrics endpoint aggregates cached Atlas action plan data into chart-ready metrics. All backend changes stay within backend/routes/atlas.js. Frontend changes are in frontend/src/components/pages/ReportingPage.js.

Tasks

  • 1. Implement the Atlas metrics aggregation endpoint

    • 1.1 Add GET /metrics route inside the existing createAtlasRouter factory function in backend/routes/atlas.js

      • Query all rows from atlas_action_plans_cache using the existing dbAll helper: SELECT has_action_plan, plans_json FROM atlas_action_plans_cache
      • Extract the aggregation logic into a pure function aggregateAtlasMetrics(rows) that takes an array of { has_action_plan, plans_json } objects and returns { totalHosts, hostsWithPlans, hostsWithoutPlans, plansByType, plansByStatus, totalPlans }
      • For each row: count hosts with/without plans based on has_action_plan; parse plans_json and count plans by plan_type and status; skip plan details for rows with invalid JSON
      • Return 503 if Atlas is not configured; return 500 on DB errors; require authentication via requireAuth(db)
      • Return all-zero metrics with empty objects when the cache table is empty
      • Do NOT modify server.js — the route is added inside the existing router factory
      • Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6
    • 1.2 Write property test for metrics aggregation (Property 1)

      • Property 1: Metrics aggregation correctness
      • Extract aggregateAtlasMetrics as a pure exported function for testability
      • Use fast-check to generate arrays of objects with has_action_plan (0 or 1) and plans_json (valid JSON arrays of { plan_type, status } objects or invalid strings)
      • Verify: totalHosts === rows.length, hostsWithPlans + hostsWithoutPlans === totalHosts, hostsWithPlans equals count of rows where has_action_plan === 1, totalPlans equals sum of valid plan array lengths, plansByType and plansByStatus counts match individual plan fields, rows with invalid JSON are counted in host totals but excluded from plan counts
      • Validates: Requirements 1.3, 1.4, 1.5
    • * 1.3 Write unit tests for the metrics endpoint

      • Test empty cache returns all-zero metrics
      • Test correct host counting with seeded data
      • Test correct plansByType and plansByStatus aggregation
      • Test rows with invalid plans_json are handled gracefully
      • Test 503 response when Atlas is not configured
      • Test 401 response for unauthenticated requests
      • Requirements: 1.1, 1.2, 1.3, 1.4, 1.5
  • 2. Checkpoint — Verify backend endpoint

    • Ensure all tests pass, ask the user if questions arise.
  • 3. Add tab system to the Metric Graphs panel

    • 3.1 Add tab state and tab bar UI to the Metric Graphs panel header in ReportingPage.js

      • Add metricsTab state initialized to 'ivanti'
      • Render a horizontal tab bar to the right of the "Metric Graphs" title with two tabs: "Ivanti Findings" and "Atlas Coverage"
      • Active tab styling: color: #F59E0B, borderBottom: 2px solid #F59E0B
      • Inactive tab styling: color: #64748B, no bottom border
      • Hover on inactive: background: rgba(245, 158, 11, 0.06)
      • Font: 'JetBrains Mono', monospace, 0.7rem, uppercase, letterSpacing: 0.08em
      • Add role="tab", aria-selected attributes on tabs; role="tabpanel" on content area
      • Tabs navigable via Tab and Enter keys
      • Requirements: 2.1, 2.2, 2.3, 2.6, 2.7, 2.8, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6
    • 3.2 Conditionally render Ivanti donuts vs Atlas content based on active tab

      • When metricsTab === 'ivanti': show existing four donut charts in their current layout (unchanged)
      • When metricsTab === 'atlas': show placeholder for Atlas donut charts (to be implemented in task 5)
      • Requirements: 2.4, 2.5
    • 3.3 Conditionally render IvantiCountsChart based on active tab

      • Show IvantiCountsChart only when metricsTab === 'ivanti'
      • Hide it when metricsTab === 'atlas'
      • Requirements: 7.1, 7.2, 7.3
  • 4. Add Atlas metrics data fetching

    • 4.1 Add Atlas metrics state and fetch function to ReportingPage.js

      • Add state: atlasMetrics (null), atlasMetricsLoading (false), atlasMetricsError (null)
      • Add fetchAtlasMetrics callback that calls GET /api/atlas/metrics with credentials: 'include'
      • On success: store data in atlasMetrics; on error: store message in atlasMetricsError
      • Set loading state during fetch
      • Requirements: 6.1, 6.3, 6.4
    • 4.2 Call fetchAtlasMetrics on mount and after successful Atlas sync

      • Add fetchAtlasMetrics() call in the existing mount useEffect
      • After a successful Atlas sync (existing sync handler), call fetchAtlasMetrics() to refresh
      • Do NOT re-fetch on tab switch
      • Requirements: 6.1, 6.2, 6.5
  • 5. Implement Atlas donut chart components

    • 5.1 Implement AtlasCoverageDonut component in ReportingPage.js

      • Props: { hostsWithPlans, hostsWithoutPlans, totalHosts }
      • Segments: emerald (#10B981) for with plans, amber (#F59E0B) for without plans
      • Center text: totalHosts count, "HOSTS" label
      • Legend: count and percentage for each segment
      • Empty state: "No data — run Atlas Sync" when totalHosts === 0
      • Reuse existing polarToCartesian and donutArcPath helpers; same dimensions (180px, 72px outer, 48px inner)
      • Follow the same SVG and styling pattern as StatusDonut
      • Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6
    • 5.2 Implement AtlasPlanTypeDonut component in ReportingPage.js

      • Props: { plansByType, totalPlans }
      • Color map: decommission: #EF4444, remediation: #0EA5E9, false_positive: #A855F7, risk_acceptance: #F59E0B, scan_exclusion: #64748B
      • Center text: totalPlans count, "PLANS" label
      • Legend: only show types with count > 0, with label, count, and percentage
      • Empty state: "No plans — run Atlas Sync" when totalPlans === 0
      • Same SVG dimensions and styling pattern
      • Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6
    • 5.3 Implement AtlasPlanStatusDonut component in ReportingPage.js

      • Props: { plansByStatus, totalPlans }
      • Color map: active: #10B981, expired: #EF4444, completed: #0EA5E9, fallback: #64748B
      • Extract a getStatusColor(status) helper function for color assignment
      • Center text: totalPlans count, "STATUS" label
      • Legend: only show statuses with count > 0, with label, count, and percentage
      • Empty state: "No plans — run Atlas Sync" when totalPlans === 0
      • Same SVG dimensions and styling pattern
      • Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6
    • 5.4 Write property test for Coverage donut data correctness (Property 2)

      • Property 2: Coverage donut data correctness
      • Use fast-check to generate random (hostsWithPlans, hostsWithoutPlans) pairs of non-negative integers where at least one > 0
      • Render AtlasCoverageDonut, verify center text equals totalHosts, legend percentages equal (count / totalHosts) * 100
      • Validates: Requirements 3.3, 3.4
    • 5.5 Write property test for Plan type donut data correctness (Property 3)

      • Property 3: Plan type donut data correctness
      • Use fast-check to generate random plansByType objects with 15 plan type keys mapped to positive integers
      • Render AtlasPlanTypeDonut, verify center text equals sum of counts, legend entries match input, percentages are correct
      • Validates: Requirements 4.3, 4.4
    • 5.6 Write property test for Plan status donut data correctness (Property 4)

      • Property 4: Plan status donut data correctness
      • Use fast-check to generate random plansByStatus objects with 14 status keys mapped to positive integers
      • Render AtlasPlanStatusDonut, verify center text equals sum of counts, legend entries match input, percentages are correct
      • Validates: Requirements 5.3, 5.4
    • 5.7 Write property test for Plan status color assignment (Property 5)

      • Property 5: Plan status color assignment
      • Use fast-check to generate random strings (mix of known statuses and arbitrary strings)
      • Verify getStatusColor returns #10B981 for "active", #EF4444 for "expired", #0EA5E9 for "completed", #64748B for any other string
      • Validates: Requirements 5.2
    • [~]* 5.8 Write unit tests for Atlas donut components

      • Test Coverage donut empty state message when totalHosts is 0
      • Test Plan type donut empty state message when totalPlans is 0
      • Test Plan status donut empty state message when totalPlans is 0
      • Test SVG dimensions are 180px with correct outer/inner radius
      • Test color assignments for each plan type and status
      • Requirements: 3.5, 4.5, 5.5, 3.6, 4.6, 5.6
  • 6. Wire Atlas donuts into the Atlas Coverage tab

    • 6.1 Render Atlas donut charts in the Atlas Coverage tab content area

      • When metricsTab === 'atlas': render AtlasCoverageDonut, AtlasPlanTypeDonut, AtlasPlanStatusDonut in a horizontal flex row with dividers (same layout pattern as Ivanti donuts)
      • Pass data from atlasMetrics state to each donut component
      • Show loading indicator while atlasMetricsLoading is true
      • Show error message when atlasMetricsError is set
      • Add chart labels above each donut: "Host Coverage", "Plan Types", "Plan Status" — matching existing label style
      • Requirements: 2.5, 3.1, 4.1, 5.1, 6.3, 6.4
    • * 6.2 Write integration tests for the full metrics flow

      • Test: fetch metrics on mount populates Atlas donut charts
      • Test: tab switch does not trigger re-fetch
      • Test: loading state shown during fetch
      • Test: error state shown on fetch failure
      • Requirements: 6.1, 6.2, 6.3, 6.4, 6.5
  • 7. Final checkpoint — Ensure 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
  • All backend changes are confined to backend/routes/atlas.js — server.js is NOT modified
  • The aggregateAtlasMetrics function is extracted as a pure function for testability and property-based testing
  • Property tests use fast-check with { numRuns: 100 } minimum
  • Checkpoints ensure incremental validation after backend and full integration
  • The existing donut chart pattern (StatusDonut, ActionCoverageDonut, FPWorkflowDonut) serves as the template for all three Atlas donut components