# 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 - [x] 1. Implement the Atlas metrics aggregation endpoint - [x] 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_ - [x] 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_ - [x] 2. Checkpoint — Verify backend endpoint - Ensure all tests pass, ask the user if questions arise. - [x] 3. Add tab system to the Metric Graphs panel - [x] 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_ - [x] 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_ - [x] 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_ - [x] 4. Add Atlas metrics data fetching - [x] 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_ - [x] 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_ - [x] 5. Implement Atlas donut chart components - [x] 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_ - [x] 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_ - [x] 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_ - [x] 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** - [x] 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 1–5 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** - [x] 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 1–4 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** - [x] 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_ - [x] 6. Wire Atlas donuts into the Atlas Coverage tab - [x] 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_ - [x] 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