From 808625dab48d9e72be35f5147f832f40a11370f7 Mon Sep 17 00:00:00 2001 From: Jordan Ramos Date: Wed, 13 May 2026 16:57:57 -0600 Subject: [PATCH] Fix requeue: fallback to finding_ids_json when queue items are deleted or absent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The requeue endpoint now handles three scenarios: 1. Original queue items still exist — uses their finding data (ideal case) 2. Queue items deleted (Clear Completed) — looks up findings from ivanti_findings table using finding_ids_json 3. FP created outside dashboard (no queue_item_ids) — same fallback to finding_ids_json and ivanti_findings lookup 4. Last resort — creates queue items with just finding IDs if the findings aren't in ivanti_findings either --- backend/routes/ivantiFpWorkflow.js | 54 ++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/backend/routes/ivantiFpWorkflow.js b/backend/routes/ivantiFpWorkflow.js index a3b32f4..b2180b9 100644 --- a/backend/routes/ivantiFpWorkflow.js +++ b/backend/routes/ivantiFpWorkflow.js @@ -1004,27 +1004,53 @@ function createIvantiFpWorkflowRouter() { let queueItemIds = []; try { queueItemIds = JSON.parse(submission.queue_item_ids_json || '[]'); - } catch (e) { - return res.status(400).json({ error: 'Could not parse queue_item_ids_json.' }); + } catch (e) { /* ignore parse errors */ } + + // Parse finding IDs (always available, even for submissions created outside dashboard) + let findingIds = []; + try { + findingIds = JSON.parse(submission.finding_ids_json || '[]'); + } catch (e) { /* ignore */ } + + if ((!Array.isArray(queueItemIds) || queueItemIds.length === 0) && + (!Array.isArray(findingIds) || findingIds.length === 0)) { + return res.status(400).json({ error: 'No findings associated with this submission.' }); } - if (!Array.isArray(queueItemIds) || queueItemIds.length === 0) { - return res.status(400).json({ error: 'No queue items associated with this submission.' }); + // Fetch original queue items to get finding data (if they still exist) + let findingsToQueue = []; + if (queueItemIds.length > 0) { + const { rows: originalItems } = await pool.query( + `SELECT finding_id, finding_title, cves_json, ip_address, hostname FROM ivanti_todo_queue WHERE id = ANY($1)`, + [queueItemIds] + ); + findingsToQueue = originalItems; } - // Fetch original queue items to get finding data - const { rows: originalItems } = await pool.query( - `SELECT finding_id, finding_title, cves_json, ip_address, hostname FROM ivanti_todo_queue WHERE id = ANY($1)`, - [queueItemIds] - ); + // Fallback: if original queue items were deleted or never existed, + // use finding_ids_json to look up finding data from ivanti_findings + if (findingsToQueue.length === 0 && findingIds.length > 0) { + const { rows: findings } = await pool.query( + `SELECT id AS finding_id, title AS finding_title, cves AS cves_json, ip_address, host_name AS hostname FROM ivanti_findings WHERE id = ANY($1)`, + [findingIds.map(String)] + ); + findingsToQueue = findings; - if (originalItems.length === 0) { - return res.status(400).json({ error: 'No original queue items found for this submission.' }); + // Last resort: create items with just the finding IDs (minimal data) + if (findingsToQueue.length === 0) { + findingsToQueue = findingIds.map(id => ({ + finding_id: String(id), + finding_title: null, + cves_json: null, + ip_address: null, + hostname: null, + })); + } } // INSERT new pending queue items for each finding const newItems = []; - for (const item of originalItems) { + for (const item of findingsToQueue) { const { rows: inserted } = await pool.query( `INSERT INTO ivanti_todo_queue (user_id, finding_id, finding_title, cves_json, ip_address, hostname, vendor, workflow_type) @@ -1042,12 +1068,12 @@ function createIvantiFpWorkflowRouter() { ); // Audit log (fire-and-forget) - const finding_ids = originalItems.map(i => i.finding_id).filter(Boolean); + const auditFindingIds = findingsToQueue.map(i => i.finding_id).filter(Boolean); logAudit({ userId: req.user.id, username: req.user.username, action: 'fp_submission_requeued', entityType: 'ivanti_fp_submissions', entityId: String(submission.id), - details: { target_workflow_type: workflow_type, items_created: newItems.length, finding_ids }, + details: { target_workflow_type: workflow_type, items_created: newItems.length, finding_ids: auditFindingIds }, ipAddress: req.ip });