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

11 KiB
Raw Blame History

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_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
  • 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_submissions filtered by req.user.id
      • Return JSON array of submission records including lifecycle_status and updated_at
      • Requirements: 7.1, 1.1
    • 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
    • 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
    • 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
    • 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.
  • 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
  • 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 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
    • 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
    • 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