feat: add row visibility controls to Reporting page — hide/bulk-hide rows, localStorage persistence, visibility manager popover, chart/export integration
This commit is contained in:
127
.kiro/specs/reporting-row-visibility/tasks.md
Normal file
127
.kiro/specs/reporting-row-visibility/tasks.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Implementation Plan: Reporting Row Visibility
|
||||
|
||||
## Overview
|
||||
|
||||
This plan implements row-level visibility controls for the Reporting page's findings table. All changes are contained within `frontend/src/components/pages/ReportingPage.js` — no new files, no backend changes. The implementation adds hidden row state management (localStorage-persisted), a visibility filtering step in the data pipeline, selection checkboxes with bulk hide, a Row Visibility Manager popover, chart/export integration, and per-row hide buttons. Each task builds incrementally on the previous one, wiring everything together by the final step.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] 1. Add hidden row state management and localStorage helpers
|
||||
- Add the `HIDDEN_ROWS_KEY` constant (`'steam_findings_hidden_rows'`)
|
||||
- Implement `loadHiddenRows()` function that reads from localStorage, parses JSON, returns a `Set<string>` (empty set on parse failure or missing key)
|
||||
- Implement `saveHiddenRows(hiddenSet)` function that serializes the set to a JSON array and writes to localStorage (silent catch on failure)
|
||||
- Add `hiddenRowIds` state initialized via `useState(loadHiddenRows)`
|
||||
- Implement `hideRow(findingId)` callback that adds a string ID to the set and persists
|
||||
- Implement `restoreRow(findingId)` callback that removes a string ID from the set and persists
|
||||
- Implement `restoreAllRows()` callback that clears the set and persists an empty set
|
||||
- _Requirements: 1.1, 2.1, 2.2, 2.3, 2.4_
|
||||
|
||||
- [ ]* 1.1 Write property test: localStorage round-trip preserves hidden row state
|
||||
- **Property 2: localStorage round-trip preserves hidden row state**
|
||||
- Generate arbitrary sets of valid Finding_ID strings, call `saveHiddenRows` then `loadHiddenRows`, assert the returned set contains exactly the same elements
|
||||
- **Validates: Requirements 2.1, 2.2**
|
||||
|
||||
- [ ]* 1.2 Write property test: corrupted localStorage produces empty set
|
||||
- **Property 3: Corrupted localStorage produces empty set**
|
||||
- Generate arbitrary strings that are not valid JSON arrays of strings, set them in localStorage under the hidden rows key, call `loadHiddenRows`, assert the result is an empty set and no error is thrown
|
||||
- **Validates: Requirements 2.4**
|
||||
|
||||
- [x] 2. Insert visibility filtering into the data pipeline
|
||||
- Add `visibleFindings` useMemo that filters `findings` by excluding any finding whose `String(f.id)` is in `hiddenRowIds` (short-circuit when set is empty)
|
||||
- Modify the existing `filtered` useMemo to start from `visibleFindings` instead of `findings`
|
||||
- Ensure column filter dropdowns, action filter, and EXC filter all operate on the post-hide dataset
|
||||
- _Requirements: 1.2, 5.1, 5.2_
|
||||
|
||||
- [ ]* 2.1 Write property test: hidden row filtering invariant
|
||||
- **Property 1: Hidden row filtering invariant**
|
||||
- Generate arbitrary arrays of finding objects and arbitrary sets of hidden Finding_IDs, compute `visibleFindings`, assert no finding in the output has an ID present in the hidden set
|
||||
- **Validates: Requirements 1.1, 1.2, 4.1, 4.3, 5.1, 5.2, 6.1, 6.2**
|
||||
|
||||
- [x] 3. Integrate hidden rows with chart and export
|
||||
- Pass `visibleFindings` (instead of `findings`) to the `ActionCoverageDonut` component's `findings` prop
|
||||
- Modify the CSV export function to use the sorted/filtered visible rows (already derived from `visibleFindings` via the pipeline)
|
||||
- Modify the XLSX export function to use the sorted/filtered visible rows
|
||||
- Verify that chart segment click filtering operates on the visible set
|
||||
- _Requirements: 4.1, 4.2, 4.3, 6.1, 6.2_
|
||||
|
||||
- [x] 4. Checkpoint — Verify core hide/restore pipeline
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 5. Add selection state and bulk hide logic
|
||||
- Add `selectedRowIds` state as `useState(new Set())`
|
||||
- Implement `toggleRowSelection(findingId)` callback that adds/removes a string ID from the selection set
|
||||
- Implement `toggleSelectAll()` callback that selects all visible sorted row IDs when not all are selected, or clears selection when all are selected
|
||||
- Implement `hideSelectedRows()` callback that unions `selectedRowIds` into `hiddenRowIds`, persists, and clears the selection set
|
||||
- Add a `useEffect` that prunes `selectedRowIds` to only include IDs present in the current `sorted` array whenever `sorted` changes
|
||||
- _Requirements: 8.1, 8.2, 8.3, 8.5, 8.6, 8.8_
|
||||
|
||||
- [ ]* 5.1 Write property test: bulk hide produces union of hidden and selected sets
|
||||
- **Property 5: Bulk hide produces the union of hidden and selected sets**
|
||||
- Generate two arbitrary sets of Finding_ID strings (hidden and selected), simulate `hideSelectedRows`, assert the resulting hidden set equals the union and the selection set is empty
|
||||
- **Validates: Requirements 8.5, 8.6**
|
||||
|
||||
- [ ]* 5.2 Write property test: selection is always a subset of visible rows
|
||||
- **Property 6: Selection is always a subset of visible rows**
|
||||
- Generate arbitrary selection and visible row sets, simulate the pruning effect, assert the resulting selection is a subset of visible row IDs
|
||||
- **Validates: Requirements 8.8**
|
||||
|
||||
- [ ]* 5.3 Write property test: select all produces exactly the visible row ID set
|
||||
- **Property 7: Select all produces exactly the visible row ID set**
|
||||
- Generate an arbitrary array of finding objects representing sorted visible rows, simulate `toggleSelectAll` from empty selection, assert the selection equals the full visible ID set; toggle again, assert empty
|
||||
- **Validates: Requirements 8.2, 8.3**
|
||||
|
||||
- [ ]* 5.4 Write property test: restore removes exactly the specified ID
|
||||
- **Property 4: Restore removes exactly the specified ID**
|
||||
- Generate a non-empty set of hidden Finding_IDs, pick a random element, simulate `restoreRow`, assert the result equals the original set minus that single ID
|
||||
- **Validates: Requirements 3.3**
|
||||
|
||||
- [x] 6. Add selection checkbox column and select-all checkbox to the table
|
||||
- Import `Square`, `CheckSquare`, and `MinusSquare` icons from lucide-react
|
||||
- Add a fixed 36px selection checkbox column as the first column in the table header and body
|
||||
- Render `Select_All_Checkbox` in the header: `CheckSquare` when all selected, `MinusSquare` when partially selected, `Square` when none selected; onClick calls `toggleSelectAll`
|
||||
- Render `Selection_Checkbox` on each row: `CheckSquare` when selected, `Square` when not; onClick calls `toggleRowSelection(finding.id)`
|
||||
- Style checkboxes with muted slate default color, accent blue when checked/active, matching existing icon sizing
|
||||
- _Requirements: 8.1, 8.2, 8.3, 8.9, 8.11_
|
||||
|
||||
- [x] 7. Add per-row hide button column
|
||||
- Add a fixed 36px hide button column as the second column (after selection checkbox) in the table header and body
|
||||
- Render an `EyeOff` icon button on each row; onClick calls `hideRow(finding.id)`
|
||||
- Style the button with 13px icon size, muted slate default color, accent blue on hover, matching existing toolbar icon patterns
|
||||
- The column header cell is empty (no label)
|
||||
- _Requirements: 1.1, 1.3, 1.4, 7.1_
|
||||
|
||||
- [x] 8. Implement BulkHideToolbar component
|
||||
- Create inline `BulkHideToolbar` component accepting `count`, `onHide`, and `onClear` props
|
||||
- Render "{count} rows selected" label, "Hide Selected" button with `EyeOff` icon, and "Clear" button
|
||||
- Style with dark gradient background, accent border, monospace font, matching existing toolbar patterns
|
||||
- Render the toolbar above the table inside the scroll container, only when `selectedRowIds.size > 0`
|
||||
- Wire `onHide` to `hideSelectedRows` and `onClear` to clearing the selection set
|
||||
- _Requirements: 8.4, 8.5, 8.6, 8.7, 8.10, 8.11_
|
||||
|
||||
- [x] 9. Checkpoint — Verify selection and bulk hide UI
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [x] 10. Implement RowVisibilityManager popover component
|
||||
- Create inline `RowVisibilityManager` component accepting `hiddenRowIds`, `findings`, `onRestore`, and `onRestoreAll` props
|
||||
- Add `open` state for popover visibility, with outside-click-to-close behavior (same pattern as existing `ColumnManager`)
|
||||
- Render a toolbar button with `EyeOff` icon and "Hidden (N)" count text, styled to match the existing ColumnManager and Queue toolbar buttons (same padding, border radius, font size, uppercase text)
|
||||
- When open, render a popover panel listing hidden findings by Finding_ID and title (looked up from the full `findings` array)
|
||||
- Each entry has an `Eye` icon restore button that calls `onRestore(findingId)`
|
||||
- Include a "Restore All" button at the bottom that calls `onRestoreAll`
|
||||
- When `hiddenRowIds.size === 0`, show "No rows hidden" message in the popover
|
||||
- Use dark gradient background, accent border, and box shadow matching the ColumnManager popover
|
||||
- Place the button in the toolbar div adjacent to the ColumnManager button
|
||||
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 7.2, 7.3_
|
||||
|
||||
- [x] 11. Final checkpoint — Verify complete feature integration
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
## Notes
|
||||
|
||||
- Tasks marked with `*` are optional and can be skipped for faster MVP
|
||||
- All changes are contained within `frontend/src/components/pages/ReportingPage.js` — no new files needed
|
||||
- The design uses JavaScript throughout; fast-check is the PBT library
|
||||
- Each task references specific requirements for traceability
|
||||
- Checkpoints ensure incremental validation
|
||||
- Property tests validate the 7 correctness properties defined in the design document
|
||||
- Unit tests validate specific UI rendering scenarios and edge cases
|
||||
Reference in New Issue
Block a user