- 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
183 lines
11 KiB
Markdown
183 lines
11 KiB
Markdown
# 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 1044–1070), 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`
|