diff --git a/backend/routes/ivantiFindings.js b/backend/routes/ivantiFindings.js index 342b1d2..07fab42 100644 --- a/backend/routes/ivantiFindings.js +++ b/backend/routes/ivantiFindings.js @@ -130,33 +130,31 @@ function extractFinding(f) { // CVE list: vulnerabilities.vulnInfoList[].cve const cves = (f.vulnerabilities?.vulnInfoList || []).map(v => v.cve).filter(Boolean); - // Workflow: flatten all distribution buckets, prioritise FP# over SYS# + // Workflow: only capture FP# (False Positive) tickets — SYS# are auto-generated + // system workflows and not actionable for our purposes. const wfDist = f.workflowDistribution || {}; - const allWfEntries = [ - ...(wfDist.actionableWorkflows || []), - ...(wfDist.requestedWorkflows || []), - ...(wfDist.approvedWorkflows || []), - ...(wfDist.reworkedWorkflows || []), - ...(wfDist.rejectedWorkflows || []), - ...(wfDist.expiredWorkflows || []), - ...(wfDist.latestSystemWorkflows || []), - ]; - // FP# (False Positive tickets) take priority over SYS# (system workflows) - const fpEntry = allWfEntries.find(w => (w.generatedId || '').startsWith('FP#')); - const sysEntry = allWfEntries.find(w => (w.generatedId || '').startsWith('SYS#')); - const wfEntry = fpEntry || sysEntry || allWfEntries[0] || null; + const fpBuckets = [ + ...(wfDist.actionableWorkflows || []), + ...(wfDist.requestedWorkflows || []), + ...(wfDist.reworkedWorkflows || []), + ...(wfDist.rejectedWorkflows || []), + ...(wfDist.expiredWorkflows || []), + ...(wfDist.approvedWorkflows || []), + ].filter(w => (w.generatedId || '').startsWith('FP#')); - // If the distribution didn't surface an FP#, also check workflowGeneratedNames directly. - // (Some FP# tickets only appear in the names list without full state info.) + // Priority: actionable > requested > reworked > rejected > expired > approved + const fpEntry = fpBuckets[0] || null; + + // Fallback: if no FP# in distribution, check workflowGeneratedNames directly const generatedNames = f.workflowGeneratedNames || []; const fpFromNames = !fpEntry ? generatedNames.find(n => n.startsWith('FP#')) || null : null; - const workflow = wfEntry ? { - id: wfEntry.generatedId || '', - state: wfEntry.state || '', - type: wfEntry.type || wfEntry.acronym || '', + const workflow = fpEntry ? { + id: fpEntry.generatedId || '', + state: fpEntry.state || '', + type: 'FP', } : fpFromNames ? { id: fpFromNames, state: '', diff --git a/frontend/src/components/pages/ReportingPage.js b/frontend/src/components/pages/ReportingPage.js index b0345f7..eb67126 100644 --- a/frontend/src/components/pages/ReportingPage.js +++ b/frontend/src/components/pages/ReportingPage.js @@ -126,14 +126,14 @@ function dueDateColor(dueDate) { } function workflowStyle(state) { + // Colors reflect action urgency — all findings here are Open, so Approved won't appear. switch ((state || '').toLowerCase()) { - case 'approved': return { bg: 'rgba(16,185,129,0.12)', border: 'rgba(16,185,129,0.35)', text: '#10B981' }; - case 'requested': return { bg: 'rgba(14,165,233,0.12)', border: 'rgba(14,165,233,0.35)', text: '#0EA5E9' }; - case 'actionable': return { bg: 'rgba(245,158,11,0.12)', border: 'rgba(245,158,11,0.35)', text: '#F59E0B' }; - case 'reworked': return { bg: 'rgba(245,158,11,0.12)', border: 'rgba(245,158,11,0.35)', text: '#F59E0B' }; - case 'rejected': return { bg: 'rgba(239,68,68,0.12)', border: 'rgba(239,68,68,0.35)', text: '#EF4444' }; - case 'expired': return { bg: 'rgba(100,116,139,0.12)', border: 'rgba(100,116,139,0.3)', text: '#64748B' }; - default: return { bg: 'rgba(100,116,139,0.08)', border: 'rgba(100,116,139,0.2)', text: '#64748B' }; + case 'expired': return { bg: 'rgba(239,68,68,0.12)', border: 'rgba(239,68,68,0.4)', text: '#EF4444' }; // overdue — renew FP + case 'rejected': return { bg: 'rgba(239,68,68,0.12)', border: 'rgba(239,68,68,0.4)', text: '#EF4444' }; // denied — must remediate + case 'reworked': return { bg: 'rgba(245,158,11,0.12)', border: 'rgba(245,158,11,0.4)', text: '#F59E0B' }; // challenged — resubmit FP + case 'actionable': return { bg: 'rgba(245,158,11,0.12)', border: 'rgba(245,158,11,0.4)', text: '#F59E0B' }; // needs action + case 'requested': return { bg: 'rgba(14,165,233,0.12)', border: 'rgba(14,165,233,0.35)', text: '#0EA5E9' }; // in flight — awaiting approval + default: return { bg: 'rgba(100,116,139,0.08)', border: 'rgba(100,116,139,0.2)', text: '#64748B' }; // unknown state } }