Fix requeue: fallback to finding_ids_json when queue items are deleted or absent

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
This commit is contained in:
Jordan Ramos
2026-05-13 16:57:57 -06:00
parent 0fefd2a707
commit 808625dab4

View File

@@ -1004,27 +1004,53 @@ function createIvantiFpWorkflowRouter() {
let queueItemIds = []; let queueItemIds = [];
try { try {
queueItemIds = JSON.parse(submission.queue_item_ids_json || '[]'); queueItemIds = JSON.parse(submission.queue_item_ids_json || '[]');
} catch (e) { } catch (e) { /* ignore parse errors */ }
return res.status(400).json({ error: 'Could not parse queue_item_ids_json.' });
// 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) { // Fetch original queue items to get finding data (if they still exist)
return res.status(400).json({ error: 'No queue items associated with this submission.' }); 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 // Fallback: if original queue items were deleted or never existed,
const { rows: originalItems } = await pool.query( // use finding_ids_json to look up finding data from ivanti_findings
`SELECT finding_id, finding_title, cves_json, ip_address, hostname FROM ivanti_todo_queue WHERE id = ANY($1)`, if (findingsToQueue.length === 0 && findingIds.length > 0) {
[queueItemIds] 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) { // Last resort: create items with just the finding IDs (minimal data)
return res.status(400).json({ error: 'No original queue items found for this submission.' }); 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 // INSERT new pending queue items for each finding
const newItems = []; const newItems = [];
for (const item of originalItems) { for (const item of findingsToQueue) {
const { rows: inserted } = await pool.query( const { rows: inserted } = await pool.query(
`INSERT INTO ivanti_todo_queue `INSERT INTO ivanti_todo_queue
(user_id, finding_id, finding_title, cves_json, ip_address, hostname, vendor, workflow_type) (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) // 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({ logAudit({
userId: req.user.id, username: req.user.username, userId: req.user.id, username: req.user.username,
action: 'fp_submission_requeued', entityType: 'ivanti_fp_submissions', action: 'fp_submission_requeued', entityType: 'ivanti_fp_submissions',
entityId: String(submission.id), 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 ipAddress: req.ip
}); });