feat: add batch finding disposition — multi-select findings and bulk add to Ivanti queue

This commit is contained in:
jramos
2026-04-09 09:49:40 -06:00
parent 328e48ea8c
commit ccc3576706
6 changed files with 1036 additions and 20 deletions

View File

@@ -0,0 +1,116 @@
# Implementation Plan: Batch Finding Disposition
## Overview
Add multi-select capability to the Vulnerability Triage findings table with a batch-add-to-queue API endpoint. The backend gets a new `POST /api/ivanti/todo-queue/batch` route in `ivantiTodoQueue.js`. The frontend gets selection state, checkbox dual-mode logic, a SelectionToolbar component, shift-click range select, select-all, and Escape-to-clear — all within `ReportingPage.js`.
## Tasks
- [x] 1. Add `POST /api/ivanti/todo-queue/batch` endpoint
- [x] 1.1 Add batch route handler to `backend/routes/ivantiTodoQueue.js`
- Add `POST /batch` route inside `createIvantiTodoQueueRouter`, before the `POST /` route
- Apply `requireAuth(db)` and `requireGroup('Admin', 'Standard_User')` middleware
- Validate request body: `findings` array (1200 items), each with non-empty `finding_id` string
- Validate `workflow_type` is one of `FP`, `Archer`, `CARD`
- Validate `vendor`: required non-empty string ≤200 chars for FP/Archer; ignored for CARD
- If any validation fails, return 400 with descriptive error message and reject entire batch
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.8, 3.10_
- [x] 1.2 Implement transactional batch insert with SQLite
- Use `db.serialize()` with `BEGIN TRANSACTION` / `COMMIT` to insert all findings atomically
- For each finding: insert row into `ivanti_todo_queue` with `user_id`, `finding_id`, `finding_title`, `cves_json`, `ip_address`, `vendor`, `workflow_type`
- On success: fetch all inserted rows, parse `cves_json` back to arrays, return 201 with `{ items: [...] }`
- On any DB error: `ROLLBACK` the transaction and return 500
- _Requirements: 3.7, 3.8, 3.9, 3.11_
- [x] 1.3 Add audit logging for batch additions
- After successful commit, call `logAudit(db, { ... })` with action `'batch_add_to_queue'`, entityType `'ivanti_todo_queue'`, and details including the count and workflow_type
- Import `logAudit` from `../helpers/auditLog`
- _Requirements: 3.7_
- [x] 2. Checkpoint — Verify backend endpoint
- Ensure the batch endpoint is syntactically correct and the route file has no errors. Ask the user if questions arise.
- [x] 3. Add multi-select state and checkbox dual-mode logic to `ReportingPage.js`
- [x] 3.1 Add selection state variables to `VulnerabilityTriagePage`
- Add `selectedIds` (`new Set()`), `lastClickedId` (null), `batchSubmitting` (false), `batchError` (null), `batchWorkflowType` ('FP'), `batchVendor` ('') as new `useState` hooks
- _Requirements: 1.1, 2.1_
- [x] 3.2 Implement checkbox dual-mode click handler
- Replace the existing `<td>` onClick in the checkbox cell with new logic:
- If finding is already queued → no-op (existing behavior)
- If `selectedIds.size === 0` AND not shift-click → open `AddToQueuePopover` (preserves single-select flow)
- If shift-click AND `lastClickedId` exists → range-select all visible non-queued findings between `lastClickedId` and current finding in the `sorted` array
- Otherwise → toggle finding.id in `selectedIds`
- Always update `lastClickedId` when toggling selection
- _Requirements: 1.1, 1.2, 5.1, 5.2, 6.1_
- [x] 3.3 Add visual highlighting for selected rows
- When a finding's ID is in `selectedIds`, apply a highlighted background (e.g. `rgba(14,165,233,0.12)`) to the row
- Override the existing alternating row background and hover for selected rows
- _Requirements: 1.3_
- [x] 3.4 Disable checkbox for already-queued findings
- Keep existing behavior: queued findings show checked + disabled checkbox, preventing re-selection
- Ensure queued findings are excluded from shift-click range select and select-all
- _Requirements: 1.5_
- [x] 4. Implement Select All / Deselect All in column header
- Modify the checkbox column `<th>` to render a clickable "Select All" checkbox when `selectedIds.size > 0` or when the user interacts with it
- Click behavior: if not all visible non-queued findings are selected → select all visible non-queued; if all are selected → deselect all
- _Requirements: 1.6, 1.7_
- [x] 5. Add selection pruning on filter changes
- Add a `useEffect` that watches `filtered` (the filtered findings array) and prunes `selectedIds` to only include IDs still present in the filtered set
- This ensures selection stays consistent when `columnFilters`, `actionFilter`, or `excFilter` change
- _Requirements: 1.4_
- [x] 6. Implement SelectionToolbar component
- [x] 6.1 Create the `SelectionToolbar` inline component in `ReportingPage.js`
- Render between the panel header controls and the `<table>` element, only when `selectedIds.size > 0`
- Use `position: sticky` with appropriate `top` value to stay visible during scroll
- Follow the dark theme design system: monospace fonts, dark gradient background, accent-colored borders
- _Requirements: 2.1, 2.8_
- [x] 6.2 Add toolbar controls: count badge, Clear Selection, workflow toggles, vendor input, submit button
- Display selected count badge (e.g. "12 selected")
- "Clear Selection" button that empties `selectedIds` and hides toolbar
- Workflow type toggle buttons (FP / Archer / CARD) using existing color scheme: FP = amber (`#F59E0B`), Archer = blue (`#0EA5E9`), CARD = green (`#10B981`)
- Vendor text input (hidden when CARD is selected, show "No vendor required" indicator for CARD)
- "Add to Queue" submit button — enabled only when workflow_type is CARD, or vendor is non-empty
- _Requirements: 2.2, 2.3, 2.4, 2.5, 2.6, 2.7_
- [x] 7. Implement batch submission flow
- [x] 7.1 Add `submitBatch` async function to `VulnerabilityTriagePage`
- Build request payload from `selectedIds` (map each ID to its finding object from `sorted`/`filtered` for `finding_id`, `finding_title`, `cves`, `ip_address`), plus `batchWorkflowType` and `batchVendor`
- POST to `${API_BASE}/ivanti/todo-queue/batch` with `credentials: 'include'`
- Set `batchSubmitting = true` before request, `false` after
- _Requirements: 4.1, 4.2_
- [x] 7.2 Handle batch success response
- On 201: merge returned items into `queueItems` state (sorted by vendor then id, matching existing pattern)
- Clear `selectedIds`, reset `batchWorkflowType` to 'FP', reset `batchVendor` to '', clear `batchError`
- The newly queued findings will automatically show as checked+disabled via the existing `isQueued()` helper
- _Requirements: 4.3, 4.4, 4.6_
- [x] 7.3 Handle batch error response
- On 4xx/5xx: parse error message from response JSON, set `batchError` to display in toolbar
- On network failure: set `batchError` to "Network error — please try again"
- Keep selection intact on error so user can retry
- _Requirements: 4.5_
- [x] 8. Add Escape key handler to clear selection
- Add a `useEffect` with a `keydown` listener for Escape that clears `selectedIds` when the SelectionToolbar is visible (i.e. `selectedIds.size > 0`)
- Ensure it doesn't conflict with the existing Escape handler on `AddToQueuePopover`
- _Requirements: 6.3_
- [x] 9. Ensure keyboard Tab accessibility for SelectionToolbar
- Verify all interactive elements in the toolbar (workflow buttons, vendor input, submit button, clear button) are focusable via Tab key
- Use native `<button>` and `<input>` elements (which are inherently tabbable) rather than `<div>` with onClick
- _Requirements: 6.2_
- [x] 10. Final checkpoint — Full integration verification
- Ensure all files have no syntax errors or diagnostic issues
- Verify the checkbox dual-mode logic: no selection → popover, existing selection → toggle
- Verify the SelectionToolbar renders/hides correctly based on selection state
- Verify batch submit wires through to the backend endpoint and updates queue state
- Ensure all tests pass, ask the user if questions arise.
## Notes
- No new database migration needed — batch insert reuses the existing `ivanti_todo_queue` schema
- The batch endpoint must be registered before `POST /` in the router to avoid Express route conflicts
- All testing is done on the dev server after push — no local test tasks included
- Each task references specific acceptance criteria from the requirements document for traceability