11 KiB
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
-
1. Database migration and schema changes
- 1.1 Create migration script
backend/migrations/add_fp_submission_editing.js- Add
lifecycle_statuscolumn toivanti_fp_submissionswith CHECK constraint and default'submitted' - Add
ivanti_workflow_batch_uuidTEXT column toivanti_fp_submissions - Add
updated_atDATETIME column toivanti_fp_submissionswith default CURRENT_TIMESTAMP - Create
ivanti_fp_submission_historytable with columns: id, submission_id (FK), user_id, username, change_type (CHECK constraint), change_details_json, created_at - Create index
idx_fp_history_submissionon 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
- Add
- 1.1 Create migration script
-
2. Implement pure helper functions in
backend/routes/ivantiFpWorkflow.js-
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
-
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
-
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.
-
4. Implement backend API endpoints in
backend/routes/ivantiFpWorkflow.js-
4.1 Implement
GET /api/ivanti/fp-submissions- Add route with
requireAuth(db)— any authenticated user - Query
ivanti_fp_submissionsfiltered byreq.user.id - Return JSON array of submission records including lifecycle_status and updated_at
- Requirements: 7.1, 1.1
- Add route with
-
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
- Add route with
-
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()usingbuildSubjectFilterRequest - 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
- Add route with
-
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
- Add route with
-
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
- Add route with
-
* 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.
-
6. Register new endpoints in
backend/server.js- Wire the updated
ivantiFpWorkflowrouter so the new GET/PUT/POST/PATCH routes are accessible under/api/ivanti/fp-submissions - Verify the existing POST
/api/ivanti/fp-workflowroute continues to work - Requirements: 7.1, 7.2, 7.3, 7.4, 7.5
- Wire the updated
-
7. Implement frontend components in
frontend/src/components/pages/ReportingPage.js-
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
-
7.2 Implement
FpEditModalcomponent- 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
-
7.3 Modify workflow badge renderer for clickable badges
- In the workflow column renderer (~lines 1044–1070), for badges with state reworked/rejected/expired:
- Add
cursor: 'pointer'andonClickhandler - 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
- Add
- For badges with state requested/approved: no changes (remain non-interactive)
- Requirements: 1.5, 1.6, 1.7, 1.8
- In the workflow column renderer (~lines 1044–1070), for badges with state reworked/rejected/expired:
-
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
-
-
8. Wire frontend state and data flow
-
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
-
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