- Add GET /api/ivanti/fp-workflow/documents/search endpoint for querying the document library - Update POST /api/ivanti/fp-workflow to accept libraryDocIds for attaching library documents on create - Update POST .../submissions/:id/attachments to accept libraryDocIds on edit - Add AttachmentSourcePicker component with local upload and library search modes - Integrate picker into FpWorkflowModal (create) and FpEditModal (edit) - Track attachment source (local/library) in attachment_results_json for traceability
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/searchroute inbackend/routes/ivantiFpWorkflow.js- Add a new GET route handler for
/documents/searchinsidecreateIvantiFpWorkflowRouter - Accept optional
qquery parameter for search term - When
qis provided, query thedocumentstable withLIKEmatching againstname,cve_id, andvendorcolumns (case-insensitive) - When
qis empty or missing, return the most recent documents ordered byuploaded_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
- Add a new GET route handler for
- 1.1 Add
-
2. Modify backend to handle library document attachments on create
- 2.1 Update
POST /api/ivanti/fp-workflowinbackend/routes/ivantiFpWorkflow.jsto acceptlibraryDocIds- Parse
libraryDocIdsfromreq.bodyas a JSON-encoded array (default to[]if absent) - Return 400 if
libraryDocIdsis not valid JSON - Validate each ID is a positive integer; return 400 identifying any invalid ID
- Query the
documentstable 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 singleformFilesarray passed toivantiFormPost - Record attachment results with
source: "local"for uploaded files andsource: "library"plusdocumentIdfor library files - Use the
namefield from thedocumentsrecord as thefilenamein attachment results for library files - Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7
- Parse
- 2.1 Update
-
3. Modify backend to handle library document attachments on edit
- 3.1 Update
POST /api/ivanti/fp-workflow/submissions/:id/attachmentsinbackend/routes/ivantiFpWorkflow.jsto acceptlibraryDocIds- Apply the same
libraryDocIdsparsing, validation, disk-read, and combined upload logic as task 2.1 - Combine local file buffers and library file buffers into a single
formFilesarray for the Ivanti API call - Record attachment results with
sourceanddocumentIdfields matching the create endpoint behavior - Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7
- Apply the same
- 3.1 Update
-
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
AttachmentSourcePickerinline infrontend/src/components/pages/ReportingPage.js- Define the component above
FpWorkflowModalin 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 usingsetTimeout/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
libraryDocsviaonLibraryDocsChange(skip if already selected byid) - 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
disabledprop 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
- Define the component above
- 5.1 Implement
-
6. Integrate AttachmentSourcePicker into FpWorkflowModal (create flow)
- 6.1 Replace the file upload section in
FpWorkflowModalwithAttachmentSourcePicker- Add
libraryDocsstate (useState([])) alongside existingfilesstate - Reset
libraryDocsto[]when modal opens (in the existinguseEffectonopen) - Replace the current drag-and-drop zone and file list section with
<AttachmentSourcePicker>passingfiles,setFiles,libraryDocs,setLibraryDocs, anddisabled={submitting} - Remove the inline
addFiles,removeFile,handleDrop,handleDragOverfunctions andfileInputRef/dropRefrefs (these are now handled inside AttachmentSourcePicker) - On submit, append
libraryDocIdsas 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
sourcefield, default to"local"for backward compatibility) - Requirements: 2.1, 2.2, 2.3, 2.5, 2.6, 2.7
- Add
- 6.1 Replace the file upload section in
-
7. Integrate AttachmentSourcePicker into FpEditModal (edit flow)
- 7.1 Replace the static "upload in Ivanti" message on the attachments tab with
AttachmentSourcePicker- Add
libraryDocsstate (useState([])) alongside existingfilesstate - Reset
libraryDocsto[]when submission changes (in the existinguseEffectonsubmission) - Keep the existing attachment display section (showing attachments from initial submission) above the picker
- Render
<AttachmentSourcePicker>below existing attachments, passingfiles,setFiles,libraryDocs,setLibraryDocs, anddisabled={isApproved} - Update
handleUploadAttachmentsto build FormData with both local files andlibraryDocIdsJSON field - Enable the upload button when either
files.length > 0orlibraryDocs.length > 0 - Disable the picker when
lifecycle_status === 'approved' - Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6
- Add
- 7.1 Replace the static "upload in Ivanti" message on the attachments tab with
-
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
documentstable already exists — no database migrations are needed - The
libraryDocIdsfield is optional in both endpoints, preserving full backward compatibility - Existing
attachment_results_jsonentries without asourcefield are treated as"local"by the frontend