From a2234ccc1a866707aeb44961117fbb4cb379bde5 Mon Sep 17 00:00:00 2001 From: Jordan Ramos Date: Mon, 15 Jun 2026 09:29:46 -0600 Subject: [PATCH] Write BU history records from drift checker for anomaly banner detail view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The drift checker now inserts into ivanti_finding_bu_history when it classifies archived findings as bu_reassignment. Previously only the inline per-finding BU comparison (for findings still in sync) wrote history records — archived findings that moved BU were counted in the anomaly summary but had no detail records for the banner to display. Also captures title and hostName from the Ivanti API response in the drift checker for richer detail display, and adjusts the banner's time window to 10 minutes before sync_timestamp to catch records written during the drift check phase. --- backend/routes/ivantiFindings.js | 23 ++++++++++++++++++- .../src/components/pages/AnomalyBanner.js | 15 ++++++++---- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/backend/routes/ivantiFindings.js b/backend/routes/ivantiFindings.js index 791b93f..8788c3a 100644 --- a/backend/routes/ivantiFindings.js +++ b/backend/routes/ivantiFindings.js @@ -734,7 +734,9 @@ async function runBUDriftChecker(newlyArchivedIds, apiKey, clientId, skipTls) { const bu = f.assetCustomAttributes?.['1550_host_1']?.[0] || 'UNKNOWN'; const severity = typeof f.severity === 'number' ? f.severity : parseFloat(f.severity) || 0; const state = f.status || f.generic_state || ''; - foundMap.set(String(f.id), { bu, severity, state }); + const title = f.title || ''; + const hostName = f.host?.hostName || f.hostName || ''; + foundMap.set(String(f.id), { bu, severity, state, title, hostName }); } page++; @@ -791,6 +793,25 @@ async function runBUDriftChecker(newlyArchivedIds, apiKey, clientId, skipTls) { } catch (err) { console.error(`[BU Drift Checker] Error updating transition reason for finding ${id}:`, err.message); } + + // Record BU reassignment in ivanti_finding_bu_history for detail view + if (classification === 'bu_reassignment' && found) { + try { + // Determine previous BU — look up from the cached finding record + const { rows: prevRows } = await pool.query( + `SELECT bu_ownership FROM ivanti_findings WHERE id = $1`, + [id] + ); + const previousBu = prevRows[0]?.bu_ownership || 'UNKNOWN'; + await pool.query( + `INSERT INTO ivanti_finding_bu_history (finding_id, finding_title, host_name, previous_bu, new_bu, detected_at) + VALUES ($1, $2, $3, $4, $5, NOW())`, + [id, found.title || '', found.hostName || '', previousBu, found.bu] + ); + } catch (err) { + console.error(`[BU Drift Checker] Error recording BU change for finding ${id}:`, err.message); + } + } } console.log(`[BU Drift Checker] Classification complete:`, summary); diff --git a/frontend/src/components/pages/AnomalyBanner.js b/frontend/src/components/pages/AnomalyBanner.js index 3b3a181..d2a118d 100644 --- a/frontend/src/components/pages/AnomalyBanner.js +++ b/frontend/src/components/pages/AnomalyBanner.js @@ -247,11 +247,18 @@ export default function AnomalyBanner() { setBuLoading(true); setBuExpanded(true); try { - // Scope to changes detected since this anomaly's sync + // Scope to changes detected around this anomaly's sync. + // The drift checker writes BU history records slightly before the anomaly + // summary is recorded, so use a 10-minute window before sync_timestamp. const since = anomaly?.sync_timestamp || ''; - const url = since - ? `${API_BASE}/ivanti/findings/bu-changes?since=${encodeURIComponent(since)}` - : `${API_BASE}/ivanti/findings/bu-changes?limit=50`; + let url; + if (since) { + const sinceDate = new Date(since); + sinceDate.setMinutes(sinceDate.getMinutes() - 10); + url = `${API_BASE}/ivanti/findings/bu-changes?since=${encodeURIComponent(sinceDate.toISOString())}`; + } else { + url = `${API_BASE}/ivanti/findings/bu-changes?limit=50`; + } const res = await fetch(url, { credentials: 'include' }); if (res.ok) { const data = await res.json();