Files
cve-dashboard/.kiro/specs/fp-submission-editing/tasks.md
jramos df30430956 feat: add FP submission editing with lifecycle tracking, clickable workflow badges, and edit modal
- Add migration for lifecycle_status, batch UUID, updated_at columns and submission history table
- Add backend endpoints: GET/PUT/POST/PATCH for viewing, editing, adding findings/attachments, and status changes
- Add pure helpers: validateLifecycleTransition, mergeFindings, buildSubmissionHistoryEntry
- Add FpEditModal with tabbed UI (Details, Findings, Attachments, History)
- Make workflow badges clickable for Reworked/Rejected/Expired states with pencil icon
- Add submissions list section to QueuePanel with lifecycle status badges
- Wire state and data flow in ReportingPage for submissions fetch and edit callbacks
2026-04-13 12:27:56 -06:00

183 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: FP Submission Editing
## Overview
Extends the existing FP workflow system with lifecycle status tracking, edit/resubmit capabilities, and a submission history audit trail. Implementation proceeds bottom-up: database migration → pure helpers → backend endpoints → frontend components → wiring and integration.
## Tasks
- [x] 1. Database migration and schema changes
- [x] 1.1 Create migration script `backend/migrations/add_fp_submission_editing.js`
- Add `lifecycle_status` column to `ivanti_fp_submissions` with CHECK constraint and default `'submitted'`
- Add `ivanti_workflow_batch_uuid` TEXT column to `ivanti_fp_submissions`
- Add `updated_at` DATETIME column to `ivanti_fp_submissions` with default CURRENT_TIMESTAMP
- Create `ivanti_fp_submission_history` table with columns: id, submission_id (FK), user_id, username, change_type (CHECK constraint), change_details_json, created_at
- Create index `idx_fp_history_submission` on submission_id
- Wrap ALTER TABLE statements in try/catch for idempotency; use CREATE TABLE IF NOT EXISTS / CREATE INDEX IF NOT EXISTS
- _Requirements: 8.1, 8.2, 8.3, 8.4, 8.5_
- [x] 2. Implement pure helper functions in `backend/routes/ivantiFpWorkflow.js`
- [x] 2.1 Implement `validateLifecycleTransition(currentStatus, newStatus)`
- Accept two status strings from the set {submitted, approved, rejected, rework, resubmitted}
- Return `{ valid: false, error }` when currentStatus is `'approved'` (finalized, no transitions allowed)
- Return `{ valid: false, error }` when newStatus is not in the allowed set
- Return `{ valid: true }` for all other valid transitions
- Export from module for testing
- _Requirements: 5.4, 5.5_
- [x] 2.2 Implement `mergeFindings(existingJson, newIds)`
- Parse existingJson (JSON array of strings), concatenate with newIds array
- Deduplicate by converting to Set, return JSON.stringify of the merged array
- Handle edge cases: empty existing array, empty newIds, overlapping IDs
- Export from module for testing
- _Requirements: 3.3_
- [x] 2.3 Implement `buildSubmissionHistoryEntry(changeType, details, userId, username)`
- Construct and return an object with: submission_id (to be set by caller), user_id, username, change_type, change_details_json (JSON.stringify of details), created_at (ISO string)
- Export from module for testing
- _Requirements: 6.1, 6.2_
- [ ]* 2.4 Write property test for `mergeFindings` — Property 1: Finding Merge Preserves All IDs and Deduplicates
- **Property 1: Finding Merge Preserves All IDs and Deduplicates**
- **Validates: Requirements 3.3**
- Use fast-check to generate arbitrary arrays of string IDs for existing and new
- Assert: parsed result contains every ID from both inputs, no duplicates, length ≤ sum of input lengths
- Test file: `backend/__tests__/fpSubmissionEditing.property.test.js`
- [ ]* 2.5 Write property test for `validateLifecycleTransition` — Property 2: Lifecycle Transition Validation
- **Property 2: Lifecycle Transition Validation**
- **Validates: Requirements 5.4, 5.5**
- Use fast-check to generate pairs from {submitted, approved, rejected, rework, resubmitted}
- Assert: always invalid when currentStatus is 'approved'; always valid for other currentStatus values with valid newStatus; rejected/rework → resubmitted is always valid
- Test file: `backend/__tests__/fpSubmissionEditing.property.test.js`
- [ ] 3. Checkpoint — Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 4. Implement backend API endpoints in `backend/routes/ivantiFpWorkflow.js`
- [x] 4.1 Implement `GET /api/ivanti/fp-submissions`
- Add route with `requireAuth(db)` — any authenticated user
- Query `ivanti_fp_submissions` filtered by `req.user.id`
- Return JSON array of submission records including lifecycle_status and updated_at
- _Requirements: 7.1, 1.1_
- [x] 4.2 Implement `PUT /api/ivanti/fp-submissions/:id`
- Add route with `requireAuth(db)`, `requireGroup('Admin', 'Standard_User')`
- Verify ownership (user_id match → 403 if not)
- Lifecycle guard: reject if lifecycle_status is 'approved' → 400
- Validate body with existing `validateFpWorkflowForm`
- Proxy to Ivanti update endpoint via `ivantiPost()`
- On success: UPDATE local record fields + updated_at, INSERT history row (change_type: 'fields_updated'), log audit
- If previous status was 'rejected' or 'rework', set lifecycle_status to 'resubmitted'
- _Requirements: 7.2, 2.1, 2.2, 2.3, 2.4, 2.5, 5.4, 5.5, 7.6, 7.7_
- [x] 4.3 Implement `POST /api/ivanti/fp-submissions/:id/findings`
- Add route with `requireAuth(db)`, `requireGroup('Admin', 'Standard_User')`
- Verify ownership, lifecycle guard (reject if approved)
- Validate findingIds and queueItemIds from body; verify queue items belong to user, are FP type, and pending
- Proxy to Ivanti map endpoint via `ivantiFormPost()` using `buildSubjectFilterRequest`
- On success: merge finding IDs with `mergeFindings()`, mark queue items complete, INSERT history + audit
- _Requirements: 7.3, 3.1, 3.2, 3.3, 3.4, 3.5_
- [x] 4.4 Implement `POST /api/ivanti/fp-submissions/:id/attachments`
- Add route with `requireAuth(db)`, `requireGroup('Admin', 'Standard_User')`, Multer middleware
- Verify ownership, lifecycle guard (reject if approved)
- Validate file constraints (10 MB, allowed extensions)
- Loop each file: call `ivantiMultipartPost()` to Ivanti attach endpoint
- Collect per-file success/failure results
- Update attachment_count and attachment_results_json, INSERT history + audit
- _Requirements: 7.4, 4.1, 4.2, 4.3, 4.4, 4.5_
- [x] 4.5 Implement `PATCH /api/ivanti/fp-submissions/:id/status`
- Add route with `requireAuth(db)`, `requireGroup('Admin', 'Standard_User')`
- Verify ownership
- Validate new status is in allowed set
- Use `validateLifecycleTransition()` to check transition validity
- UPDATE lifecycle_status and updated_at, INSERT history row (change_type: 'status_changed'), log audit
- _Requirements: 7.5, 5.1, 5.2, 5.3, 7.6, 7.7_
- [ ]* 4.6 Write unit tests for backend endpoints
- Test ownership verification returns 403 for non-owner
- Test lifecycle guard returns 400 for approved submissions
- Test role guard rejects non-Admin/Standard_User
- Test Ivanti error status mapping (401, 419, 429, 5xx)
- Test history recording produces correct change_type and change_details_json
- Test migration idempotency (can run multiple times without error)
- Test file: `backend/__tests__/fpSubmissionEditing.test.js`
- _Requirements: 7.6, 7.7, 5.5_
- [ ]* 4.7 Write integration tests for backend endpoints
- Test GET returns correct records for authenticated user
- Test PUT proxies to Ivanti and updates local record
- Test POST findings maps to Ivanti and merges finding IDs
- Test POST attachments uploads to Ivanti and updates attachment records
- Test PATCH status updates lifecycle and creates history entry
- Test queue items marked complete after successful finding addition
- Test file: `backend/__tests__/fpSubmissionEditing.integration.test.js`
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5_
- [ ] 5. Checkpoint — Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 6. Register new endpoints in `backend/server.js`
- Wire the updated `ivantiFpWorkflow` router so the new GET/PUT/POST/PATCH routes are accessible under `/api/ivanti/fp-submissions`
- Verify the existing POST `/api/ivanti/fp-workflow` route continues to work
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5_
- [x] 7. Implement frontend components in `frontend/src/components/pages/ReportingPage.js`
- [x] 7.1 Implement `lifecycleStatusBadge(status)` helper function
- Return inline style object with border, background, and text color per status
- Color mapping: submitted/resubmitted (sky blue), approved (emerald), rejected (red), rework (amber)
- _Requirements: 1.3_
- [x] 7.2 Implement `FpEditModal` component
- Props: open, onClose, submission, queueItems, onSuccess
- State: form fields initialized from submission, activeTab, saving, errors, result
- Tab bar with 4 tabs: Details, Findings, Attachments, History
- Details tab: editable form fields (name, reason, description, expirationDate, scopeOverride) with Save button; calls PUT endpoint
- Findings tab: read-only current finding IDs list + mechanism to select and add FP queue items; calls POST findings endpoint
- Attachments tab: existing attachments list + file upload area; calls POST attachments endpoint
- History tab: chronological list fetched from submission history (included in GET response or separate query)
- Approved submissions: all fields read-only with finalized message
- Dark tactical theme matching existing FpWorkflowModal
- _Requirements: 1.2, 2.1, 2.2, 2.3, 2.4, 2.5, 3.1, 3.2, 3.4, 3.5, 4.1, 4.2, 4.3, 4.4, 4.5, 5.5, 6.1_
- [x] 7.3 Modify workflow badge renderer for clickable badges
- In the workflow column renderer (~lines 10441070), for badges with state reworked/rejected/expired:
- Add `cursor: 'pointer'` and `onClick` handler
- Append pencil icon (lucide `Edit3`, 10px) after state text
- On hover: increase border opacity and brighten background
- On click: look up matching FP_Submission by workflow batch ID, open FpEditModal
- For badges with state requested/approved: no changes (remain non-interactive)
- _Requirements: 1.5, 1.6, 1.7, 1.8_
- [x] 7.4 Add submissions list section to QueuePanel
- Fetch submissions via GET /api/ivanti/fp-submissions on panel open
- Display each submission: workflow name, batch ID, lifecycle status badge, finding count, created date
- Clicking a submission row opens FpEditModal with that submission's data
- Viewers see the list but cannot click to edit
- _Requirements: 1.1, 1.2, 1.3, 1.4_
- [x] 8. Wire frontend state and data flow
- [x] 8.1 Add submissions state and fetch logic to ReportingPage
- Add state for submissions array and selected submission
- Fetch submissions on page load and after successful edits (onSuccess callback)
- Pass submissions and queueItems to FpEditModal and QueuePanel
- _Requirements: 1.1, 1.2_
- [x] 8.2 Connect FpEditModal callbacks to refresh data
- On successful edit/findings/attachments/status change, call onSuccess to refresh submissions list, queue items, and reporting table data
- _Requirements: 2.5, 3.4, 4.4, 5.3_
- [ ] 9. 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
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties from the design document
- The project uses plain JavaScript (no TypeScript) — all code should follow existing conventions
- All new endpoints follow the existing factory-pattern router in `ivantiFpWorkflow.js`