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

164 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 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**
- [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 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**
- [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