Files
cve-dashboard/.kiro/specs/fp-attachment-library/tasks.md

7.9 KiB

Implementation Plan: FP Attachment Library

Overview

This plan implements the FP Attachment Library feature, which allows users to attach existing CVE document library files to FP workflow submissions alongside traditional local file uploads. The implementation adds a new Document Search API endpoint, modifies two existing backend endpoints to handle library document references, and creates a shared AttachmentSourcePicker component used in both the create and edit modals.

Tasks

  • 1. Add Document Search API endpoint

    • 1.1 Add GET /api/documents/search route in backend/routes/ivantiFpWorkflow.js
      • Add a new GET route handler for /documents/search inside createIvantiFpWorkflowRouter
      • Accept optional q query parameter for search term
      • When q is provided, query the documents table with LIKE matching against name, cve_id, and vendor columns (case-insensitive)
      • When q is empty or missing, return the most recent documents ordered by uploaded_at DESC
      • Limit results to 50 records maximum
      • Return each record with fields: id, cve_id, vendor, name, type, file_size, mime_type, uploaded_at
      • Protect with requireAuth(db) middleware
      • Return 500 with { error: 'Database error.' } on DB failure
      • Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6
  • 2. Modify backend to handle library document attachments on create

    • 2.1 Update POST /api/ivanti/fp-workflow in backend/routes/ivantiFpWorkflow.js to accept libraryDocIds
      • Parse libraryDocIds from req.body as a JSON-encoded array (default to [] if absent)
      • Return 400 if libraryDocIds is not valid JSON
      • Validate each ID is a positive integer; return 400 identifying any invalid ID
      • Query the documents table for all referenced IDs; return 400 if any ID is not found
      • Read each library file from disk using fs.readFileSync(file_path); if a file is missing on disk, log a warning and include { success: false, error: 'File not found on disk', source: 'library', documentId: id } in attachment results, skip that file
      • Combine local file buffers (req.files) and library file buffers into a single formFiles array passed to ivantiFormPost
      • Record attachment results with source: "local" for uploaded files and source: "library" plus documentId for library files
      • Use the name field from the documents record as the filename in attachment results for library files
      • Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7
  • 3. Modify backend to handle library document attachments on edit

    • 3.1 Update POST /api/ivanti/fp-workflow/submissions/:id/attachments in backend/routes/ivantiFpWorkflow.js to accept libraryDocIds
      • Apply the same libraryDocIds parsing, validation, disk-read, and combined upload logic as task 2.1
      • Combine local file buffers and library file buffers into a single formFiles array for the Ivanti API call
      • Record attachment results with source and documentId fields matching the create endpoint behavior
      • Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7
  • 4. Checkpoint — Verify backend changes

    • Ensure all backend changes are syntactically correct and consistent with existing patterns. Ask the user if questions arise.
  • 5. Create AttachmentSourcePicker component

    • 5.1 Implement AttachmentSourcePicker inline in frontend/src/components/pages/ReportingPage.js
      • Define the component above FpWorkflowModal in the file
      • Accept props: files, onFilesChange, libraryDocs, onLibraryDocsChange, disabled
      • Implement a mode toggle with two tab-style buttons: "Local Upload" and "Library" (default to "Local Upload")
      • In Local Upload mode, render the existing drag-and-drop zone with file input, file validation (extension + size), and file list
      • In Library mode, render a search input that queries GET /api/documents/search?q=... with 300ms debounce using setTimeout/clearTimeout
      • Display search results in a scrollable list showing document name, CVE ID, vendor, and file size
      • Show already-selected library documents as disabled/checked in search results to prevent duplicates
      • When a search result is clicked, add it to libraryDocs via onLibraryDocsChange (skip if already selected by id)
      • When a library doc is removed from the attachment list, re-enable it in search results
      • Render a unified attachment list below the mode-specific UI showing all attachments (local + library)
      • Each attachment row displays: source badge ("Local" or "Library"), filename, file size, and a remove button (Trash2 icon)
      • Library attachment rows additionally display CVE ID and vendor name
      • Disable all interactions when disabled prop is true
      • Style consistently with existing modal components using inline style objects, monospace font, dark theme colors from DESIGN_SYSTEM.md
      • Handle network errors on search by showing an inline error message in the results area
      • Show "No documents found" when search returns empty results
      • Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 5.1, 5.2, 5.3, 6.1, 6.2, 6.3, 6.4
  • 6. Integrate AttachmentSourcePicker into FpWorkflowModal (create flow)

    • 6.1 Replace the file upload section in FpWorkflowModal with AttachmentSourcePicker
      • Add libraryDocs state (useState([])) alongside existing files state
      • Reset libraryDocs to [] when modal opens (in the existing useEffect on open)
      • Replace the current drag-and-drop zone and file list section with <AttachmentSourcePicker> passing files, setFiles, libraryDocs, setLibraryDocs, and disabled={submitting}
      • Remove the inline addFiles, removeFile, handleDrop, handleDragOver functions and fileInputRef/dropRef refs (these are now handled inside AttachmentSourcePicker)
      • On submit, append libraryDocIds as a JSON string to the FormData: formData.append('libraryDocIds', JSON.stringify(libraryDocs.map(d => d.id)))
      • Update the progress message to reflect combined attachment count
      • Update the result view to show source badges on attachment results (use source field, default to "local" for backward compatibility)
      • Requirements: 2.1, 2.2, 2.3, 2.5, 2.6, 2.7
  • 7. Integrate AttachmentSourcePicker into FpEditModal (edit flow)

    • 7.1 Replace the static "upload in Ivanti" message on the attachments tab with AttachmentSourcePicker
      • Add libraryDocs state (useState([])) alongside existing files state
      • Reset libraryDocs to [] when submission changes (in the existing useEffect on submission)
      • Keep the existing attachment display section (showing attachments from initial submission) above the picker
      • Render <AttachmentSourcePicker> below existing attachments, passing files, setFiles, libraryDocs, setLibraryDocs, and disabled={isApproved}
      • Update handleUploadAttachments to build FormData with both local files and libraryDocIds JSON field
      • Enable the upload button when either files.length > 0 or libraryDocs.length > 0
      • Disable the picker when lifecycle_status === 'approved'
      • Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6
  • 8. Final checkpoint — Verify all changes

    • Ensure all changes are complete and consistent across backend and frontend. Ensure no hanging or orphaned code. Ask the user if questions arise.

Notes

  • No testing tasks included per user request — testing will be done on the dev server
  • The project uses plain JavaScript (no TypeScript) throughout
  • All frontend styling uses inline style objects consistent with the existing dark theme design system
  • The documents table already exists — no database migrations are needed
  • The libraryDocIds field is optional in both endpoints, preserving full backward compatibility
  • Existing attachment_results_json entries without a source field are treated as "local" by the frontend