Sync .kiro/ from master — v2.2.0 release batch
New specs: archer-template-library, ccp-metrics-view-restructure, compliance-list-stale-after-sidebar-edit, compliance-metric-estimated-resolution-date, compliance-remediation-display-fix, flexible-jira-ticket-creation, forecast-burndown-chart, granite-loader-export, ivanti-queue-clear-completed-fix, multi-item-jira-ticket, queue-collapsible-sections, vendor-issue-type-dropdown New steering: archer-template-gen.md Updated: migration-registration-check hook, remediation-plan-history spec, gitlab-workflow, tech, versioning steering files
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{"specId": "7b867406-4d3d-4728-9b3c-94ea7c6f51ef", "workflowType": "requirements-first", "specType": "bugfix"}
|
||||
@@ -0,0 +1,44 @@
|
||||
# Bugfix Requirements Document
|
||||
|
||||
## Introduction
|
||||
|
||||
GitLab issue [#23](http://steam-gitlab.charterlab.com/steam/cve-dashboard/-/issues/23) — "[Bug] Update Res Date/Remed Plan in list after updating in sidebar" (reported by nkapur on the AEO Compliance page).
|
||||
|
||||
After a user sets a resolution date and/or remediation plan in the host detail sidebar (`ComplianceDetailPanel`) and closes it, the new value does not appear in the host list on `CompliancePage` until the list is refreshed by some other action (changing filters, switching teams, switching tabs, completing an upload, adding a note, or clicking the manual refresh button).
|
||||
|
||||
This is purely a frontend state-refresh issue. The predecessor spec `compliance-remediation-display-fix` already fixed the list endpoint (`GET /api/compliance/items?team=X&status=Y`) to include `resolution_date` and `remediation_plan` in the SELECT and propagate them through `groupByHostname()`, so the backend list endpoint already returns the correct data. The defect is that `CompliancePage` never re-fetches the device list after a sidebar metadata save, so the in-memory `devices` state (populated by `fetchDevices`) keeps the stale value until one of the existing refresh triggers fires. The backend is out of scope for this fix.
|
||||
|
||||
Relevant code:
|
||||
|
||||
- `frontend/src/components/pages/CompliancePage.js` renders `<ComplianceDetailPanel>` (around lines 696–702) with `onClose={() => setSelectedHost(null)}` (clears selection, no list refresh) and `onNoteAdded={refresh}` (where `refresh` runs `fetchSummary` + `fetchDevices`, but `onNoteAdded` is only invoked when a note is added or deleted).
|
||||
- `frontend/src/components/pages/ComplianceDetailPanel.js` has `handleSaveMetadata()` which `PATCH`es `/api/compliance/items/:hostname/metadata` then calls its own `fetchDetail()` to refresh the panel, but never invokes any parent callback after a metadata save. As a result the parent list held in `CompliancePage` state is never re-fetched after a resolution date / remediation plan edit.
|
||||
|
||||
## Bug Analysis
|
||||
|
||||
### Current Behavior (Defect)
|
||||
|
||||
1.1 WHEN the user saves a resolution date and/or remediation plan in the sidebar via `handleSaveMetadata()` (`PATCH /api/compliance/items/:hostname/metadata` succeeds) THEN the system refreshes only the panel's own detail (`fetchDetail()`) and does not notify the parent `CompliancePage`, so `fetchDevices` is never re-issued.
|
||||
|
||||
1.2 WHEN the user closes the sidebar after a successful metadata save via `onClose` THEN the system only clears `selectedHost` (`setSelectedHost(null)`) and does not trigger any list refresh, so the parent `devices` state retains the pre-edit value.
|
||||
|
||||
1.3 WHEN the host list row re-renders after a sidebar metadata save without an intervening refresh THEN the system displays the stale Resolution Date / Remediation Plan value (or "—") because `devices` in `CompliancePage` state still holds the value from the last `fetchDevices` call.
|
||||
|
||||
### Expected Behavior (Correct)
|
||||
|
||||
2.1 WHEN the user saves a resolution date and/or remediation plan in the sidebar via `handleSaveMetadata()` and the `PATCH` succeeds THEN the system SHALL invoke a parent refresh callback (mirroring the existing `onNoteAdded` pattern) that re-issues `fetchDevices` so the parent list reflects the updated values.
|
||||
|
||||
2.2 WHEN the parent list has been re-fetched following a successful sidebar metadata save THEN the system SHALL display the updated Resolution Date / Remediation Plan value in the corresponding host list row without requiring a manual filter change, team change, tab change, or manual refresh.
|
||||
|
||||
### Unchanged Behavior (Regression Prevention)
|
||||
|
||||
3.1 WHEN a user adds or deletes a note in the sidebar THEN the system SHALL CONTINUE TO invoke the existing `onNoteAdded` refresh callback exactly as before.
|
||||
|
||||
3.2 WHEN a user changes team, changes the active/resolved tab, completes an upload, or clicks the manual refresh button THEN the system SHALL CONTINUE TO refresh the list via the existing `fetchSummary` + `fetchDevices` triggers.
|
||||
|
||||
3.3 WHEN a metadata save fails (the `PATCH` returns a non-OK response and `handleSaveMetadata()` sets `metaError`) THEN the system SHALL CONTINUE TO surface the error in the panel and SHALL NOT require the list to display changed values for a save that did not persist.
|
||||
|
||||
3.4 WHEN the user closes the sidebar without having saved any metadata change THEN the system SHALL CONTINUE TO clear the selection as before, and no unnecessary behavioral regression SHALL be introduced for the no-change case.
|
||||
|
||||
3.5 WHEN the host list renders hostname, IP address, device type, failing metrics, and seen count THEN the system SHALL CONTINUE TO display these fields correctly with no change in behavior.
|
||||
|
||||
3.6 WHEN the sidebar saves metadata THEN the system SHALL CONTINUE TO call its own `fetchDetail()` so the panel's in-place view and history remain correct.
|
||||
116
.kiro/specs/compliance-list-stale-after-sidebar-edit/tasks.md
Normal file
116
.kiro/specs/compliance-list-stale-after-sidebar-edit/tasks.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Implementation Plan: Compliance List Stale After Sidebar Edit
|
||||
|
||||
## Overview
|
||||
|
||||
Fix the frontend state-refresh defect described in GitLab issue [#23](http://steam-gitlab.charterlab.com/steam/cve-dashboard/-/issues/23). After a successful metadata save in `ComplianceDetailPanel` (`PATCH /api/compliance/items/:hostname/metadata`), the host list on `CompliancePage` keeps showing the pre-edit Resolution Date / Remediation Plan until an unrelated refresh trigger fires. The backend list endpoint already returns the correct data (fixed in the predecessor spec `compliance-remediation-display-fix`); the defect is purely that the sidebar never notifies the parent after a metadata save, so `fetchDevices` is never re-issued.
|
||||
|
||||
The fix mirrors the existing `onNoteAdded` → `refresh` pattern: `handleSaveMetadata()` invokes a new parent callback (`onMetadataSaved`) after a successful `PATCH`, and `CompliancePage` wires that callback to `refresh` (`fetchSummary` + `fetchDevices`). The backend is out of scope.
|
||||
|
||||
All tasks below are coding/test tasks. Tests live in the frontend and run via `react-scripts test` (`CI=true npm test`), using `@testing-library/react`, `@testing-library/jest-dom`, and `fast-check` (already permitted by `jest.transformIgnorePatterns` in `frontend/package.json`).
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] 1. Write bug condition exploration property test
|
||||
- **Property 1: Bug Condition** - List Row Stays Stale After Sidebar Metadata Save
|
||||
- **CRITICAL**: This test MUST FAIL on the current unfixed code — failure confirms the bug exists
|
||||
- **DO NOT attempt to fix the test or the code when it fails** — the failure is the expected, desired outcome at this step
|
||||
- **NOTE**: This test encodes the expected behavior (Property 1 below) — it will validate the fix when it passes after implementation
|
||||
- **GOAL**: Surface counterexamples demonstrating that after a successful sidebar metadata save the parent `CompliancePage` list does NOT reflect the saved value without a manual refresh
|
||||
- **Test file**: `frontend/src/components/pages/__tests__/compliance-list-stale-after-sidebar-edit.exploration.property.test.js`
|
||||
- **Header comment**: reference spec `.kiro/specs/compliance-list-stale-after-sidebar-edit/` and issue #23, and state "EXPECTED TO FAIL on unfixed code — failure confirms the bug" (mirrors `backend/__tests__/compliance-remediation-display-fix.exploration.property.test.js` tagging convention)
|
||||
- **Bug Condition** (from bugfix.md Current Behavior): `isBugCondition(input) = a successful PATCH /api/compliance/items/:hostname/metadata occurred from the sidebar` — under this condition the parent list is never re-fetched (1.1), `onClose` only clears `selectedHost` (1.2), and the row shows the stale value (1.3)
|
||||
- **Scoped PBT Approach**: use `fast-check` to generate varied saved-metadata values across runs and assert the universal property holds for each:
|
||||
- `fc.assert(fc.asyncProperty(arbResolutionDate, arbRemediationPlan, async (resolutionDate, remediationPlan) => { ... }), { numRuns: ... })`
|
||||
- `arbResolutionDate`: build `YYYY-MM-DD` strings from integer tuples (do NOT call `toISOString` on shrunk values — follow the date-generator pattern in the predecessor exploration test)
|
||||
- `arbRemediationPlan`: non-empty trimmed strings, length-bounded (e.g. 1–200)
|
||||
- **Property under test**: _for any_ saved metadata value, after a successful sidebar save the parent list row for that hostname displays the saved value without a manual filter/team/tab change or manual refresh click
|
||||
- **Test structure**:
|
||||
- Mock `useAuth` from `../../contexts/AuthContext` (e.g. `jest.mock`) to return a stub: `canWrite: () => true`, `isAdmin: () => false`, `getAvailableTeams: () => ['STEAM']`, `adminScope: null`
|
||||
- Mock `global.fetch` with a URL-routing implementation responding to:
|
||||
- `GET /compliance/summary?team=STEAM` → minimal summary `{ entries: [], overall_scores: {}, upload: { report_date: '2025-01-01' } }`
|
||||
- `GET /compliance/items?team=STEAM&status=active` → `{ devices: [oneDevice] }` where the device starts with a stale value (e.g. `resolution_date: null`, `remediation_plan: null`) on the FIRST call, and returns the generated saved value on SUBSEQUENT calls (so a re-fetch is observable)
|
||||
- `GET /compliance/items/:hostname` → detail object with one active metric so the panel renders the Resolution Date / Remediation Plan inputs and Save button
|
||||
- `PATCH /compliance/items/:hostname/metadata` → `{ ok: true }` success response
|
||||
- Render `<CompliancePage onNavigate={() => {}} />`, wait for the device row to appear, click the row to open `ComplianceDetailPanel`
|
||||
- Set the Resolution Date input and/or Remediation Plan textarea to the generated value(s) and click **Save** (use `@testing-library/user-event` or `fireEvent`, wrapped in `act`/`waitFor`)
|
||||
- **Primary assertion (encodes Property 1)**: `await waitFor(() => expect(within(row).getByText(<generated value, sliced to 10 for the date>)).toBeInTheDocument())` — the list row reflects the saved value
|
||||
- **Supporting assertion**: the list endpoint `GET /compliance/items?...` is called more than once (initial load + post-save refresh); on unfixed code it is called exactly once
|
||||
- Run on UNFIXED code: `cd frontend && CI=true npm test -- --testPathPattern compliance-list-stale-after-sidebar-edit.exploration`
|
||||
- **EXPECTED OUTCOME**: Test FAILS — `handleSaveMetadata()` never invokes a parent callback, so `fetchDevices` is not re-issued and the row keeps the stale value (the list GET is called only once)
|
||||
- Document the counterexample found (e.g. "after saving resolution_date='2026-03-04', the list row still shows '—'; GET /compliance/items called 1 time, expected ≥ 2")
|
||||
- Mark this task complete when the test is written, run, and the failure is documented
|
||||
- _Requirements: 1.1, 1.2, 1.3, 2.1, 2.2_
|
||||
|
||||
- [x] 2. Fix the stale list after sidebar metadata save
|
||||
|
||||
- [x] 2.1 Invoke a parent refresh callback from `handleSaveMetadata()` after a successful PATCH
|
||||
- In `frontend/src/components/pages/ComplianceDetailPanel.js`, add `onMetadataSaved` to the component props: `export default function ComplianceDetailPanel({ hostname, onClose, onNoteAdded, onMetadataSaved, onNavigate })`
|
||||
- In `handleSaveMetadata()`, after the `PATCH` succeeds and `await fetchDetail()` completes (inside the `try`, after the existing success handling), add: `if (onMetadataSaved) onMetadataSaved();`
|
||||
- Place the call so it only runs on success (not in the `catch`), mirroring how `handleAddNote()` calls `if (onNoteAdded) onNoteAdded();` only after a successful note save
|
||||
- Keep the existing `await fetchDetail()` call so the panel's own view/history still refreshes (do not replace it)
|
||||
- _Bug_Condition: isBugCondition(input) = successful PATCH /api/compliance/items/:hostname/metadata from the sidebar_
|
||||
- _Expected_Behavior: a parent refresh callback is invoked on save success so the parent list re-fetches (bugfix.md 2.1)_
|
||||
- _Preservation: only the success path is touched; the `catch` path that sets `metaError` is unchanged (3.3); `fetchDetail()` still runs (3.6)_
|
||||
- _Requirements: 2.1, 3.3, 3.6_
|
||||
|
||||
- [x] 2.2 Wire `onMetadataSaved` to `refresh` in `CompliancePage`
|
||||
- In `frontend/src/components/pages/CompliancePage.js`, at the `<ComplianceDetailPanel>` render site (around lines 696–702), add the prop `onMetadataSaved={refresh}` alongside the existing `onNoteAdded={refresh}`
|
||||
- Reuse the existing `refresh` function (`fetchSummary(activeTeam)` + `fetchDevices(activeTeam, activeTab)`) — do not introduce a new refresh path
|
||||
- Leave `onClose={() => setSelectedHost(null)}` unchanged (close still just clears selection)
|
||||
- Do not leave any unused variables; if a value is intentionally unused prefix it with `_` per the ESLint `no-unused-vars` warning budget
|
||||
- _Bug_Condition: isBugCondition(input) = successful sidebar metadata save while the panel is open from CompliancePage_
|
||||
- _Expected_Behavior: the parent list re-fetches via fetchDevices so the updated Resolution Date / Remediation Plan appears in the row without a manual refresh (bugfix.md 2.1, 2.2)_
|
||||
- _Preservation: onNoteAdded still maps to refresh (3.1); team/tab/upload/manual-refresh triggers unchanged (3.2); onClose unchanged (3.4); other row fields unchanged (3.5)_
|
||||
- _Requirements: 2.1, 2.2_
|
||||
|
||||
- [x] 2.3 Verify the bug condition exploration test now passes
|
||||
- **Property 1: Expected Behavior** - List Row Reflects Saved Value Without Manual Refresh
|
||||
- **IMPORTANT**: Re-run the SAME test from Task 1 — do NOT write a new test
|
||||
- The Task 1 test encodes the expected behavior; when it passes it confirms the fix satisfies Property 1
|
||||
- Run: `cd frontend && CI=true npm test -- --testPathPattern compliance-list-stale-after-sidebar-edit.exploration`
|
||||
- **EXPECTED OUTCOME**: Test PASSES — after a successful save the list re-fetches (`GET /compliance/items?...` called ≥ 2 times) and the row displays the saved value
|
||||
- _Requirements: 2.1, 2.2_
|
||||
|
||||
- [x] 3. Write preservation / regression property test
|
||||
- **Property 2: Preservation** - Unchanged Behaviors Hold After the Fix
|
||||
- **IMPORTANT**: Follow observation-first methodology — the unchanged behaviors below are observed to hold on UNFIXED code and must continue to hold after the fix; only the new "list updates after save" guard is expected to flip from failing to passing
|
||||
- **Test file**: `frontend/src/components/pages/__tests__/compliance-list-stale-after-sidebar-edit.preservation.property.test.js`
|
||||
- **Header comment**: reference spec `.kiro/specs/compliance-list-stale-after-sidebar-edit/` and issue #23 (mirrors the predecessor preservation test tagging convention)
|
||||
- **Test structure**: reuse the same `useAuth` mock and URL-routing `global.fetch` mock approach as Task 1; use `fast-check` to generate varied note text and metadata values across runs
|
||||
- **Regression guard (new behavior, post-fix)**: _for any_ saved metadata value, after a successful save the parent list row displays the saved value (same assertion family as Property 1, kept as a standing regression guard)
|
||||
- **Preservation properties (must hold before and after the fix)**:
|
||||
- **Note-add refresh still works**: when a note is added in the sidebar (`POST /compliance/notes` succeeds), the existing `onNoteAdded` callback still triggers a list re-fetch — assert `GET /compliance/items?...` is re-issued after a note add (bugfix.md 3.1)
|
||||
- **Failed save shows error and does NOT falsely update the list**: when `PATCH /compliance/items/:hostname/metadata` returns a non-OK response, assert the panel surfaces `metaError` AND the list row value is unchanged / no spurious list re-fetch displays a value that was never persisted (bugfix.md 3.3)
|
||||
- **Close-without-change just clears selection**: clicking the close (`X`) / backdrop without saving removes the detail panel (selection cleared) and does not error; no metadata save occurs (bugfix.md 3.4)
|
||||
- **Other row fields render correctly**: _for any_ generated device, the row still displays hostname, IP address, device type, failing metrics, and seen count unchanged (bugfix.md 3.5)
|
||||
- Run before the fix to confirm the preservation properties pass on unfixed code; run after the fix to confirm they still pass and the regression guard now passes
|
||||
- Command: `cd frontend && CI=true npm test -- --testPathPattern compliance-list-stale-after-sidebar-edit.preservation`
|
||||
- **EXPECTED OUTCOME (post-fix)**: All preservation properties PASS (no regressions) and the regression guard PASSES
|
||||
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6_
|
||||
|
||||
- [x] 4. Checkpoint - Run tests and confirm the production build
|
||||
- Run both new tests together: `cd frontend && CI=true npm test -- --testPathPattern compliance-list-stale-after-sidebar-edit`
|
||||
- **EXPECTED OUTCOME**: exploration test PASSES (bug fixed), preservation test PASSES (no regressions)
|
||||
- Run the production build to confirm it compiles and stays within the ESLint 25-warning budget: `cd frontend && npm run build`
|
||||
- **EXPECTED OUTCOME**: build succeeds and the warning count remains at or below 25 (the CI `lint`/`build` stage fails above 25) — prefix any intentionally-unused variables with `_` if needed
|
||||
- Ensure all tests pass; ask the user if questions arise.
|
||||
|
||||
## Task Dependency Graph
|
||||
|
||||
```json
|
||||
{
|
||||
"waves": [
|
||||
{ "id": 0, "tasks": ["1"] },
|
||||
{ "id": 1, "tasks": ["2.1", "2.2"] },
|
||||
{ "id": 2, "tasks": ["2.3", "3"] },
|
||||
{ "id": 3, "tasks": ["4"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- This is a frontend-only fix. The backend list endpoint (`GET /api/compliance/items`) already returns `resolution_date` and `remediation_plan` (predecessor spec `compliance-remediation-display-fix`), and `DeviceRow` in `CompliancePage.js` (lines 883–890) already renders them correctly when present.
|
||||
- The fix is intentionally minimal and mirrors the existing `onNoteAdded` → `refresh` pattern rather than introducing a new refresh mechanism, satisfying the Regression Prevention clauses (3.1–3.6).
|
||||
- Both tests render `CompliancePage` with a mocked `useAuth` and a URL-routing `global.fetch` mock; the list GET mock returns the stale value on first call and the saved value on later calls so a post-save re-fetch is observable in the rendered row.
|
||||
- `fast-check` is already allowed through `jest.transformIgnorePatterns` in `frontend/package.json`; generate dates as `YYYY-MM-DD` strings from integer tuples (avoid `toISOString` on shrunk values) as in the predecessor exploration test.
|
||||
Reference in New Issue
Block a user