feat: per-BU trend lines in counts history chart

- Create ivanti_counts_history_by_bu table (bu_ownership, state, count per sync)
- Sync writes per-BU snapshot alongside global history on each sync
- Seed table with current counts for immediate first data point
- GET /counts/history accepts ?teams param — queries per-BU table when filtered
- IvantiCountsChart accepts teamsParam prop, re-fetches on scope change
- ReportingPage passes getActiveTeamsParam() to the chart
- Historical per-BU data accumulates from this point forward
- Global history (no filter) still uses the original aggregate table
This commit is contained in:
Jordan Ramos
2026-05-06 13:38:38 -06:00
parent 77f113e9ae
commit 573903a885
5 changed files with 87 additions and 5 deletions

View File

@@ -188,7 +188,7 @@ function extractDate(ts) {
// ---------------------------------------------------------------------------
// Main component
// ---------------------------------------------------------------------------
export default function IvantiCountsChart() {
export default function IvantiCountsChart({ teamsParam }) {
const [collapsed, setCollapsed] = useState(false);
const [loading, setLoading] = useState(true);
const [history, setHistory] = useState([]);
@@ -199,8 +199,11 @@ export default function IvantiCountsChart() {
const load = async () => {
setLoading(true);
try {
const historyUrl = teamsParam
? `${API_BASE}/ivanti/findings/counts/history?teams=${encodeURIComponent(teamsParam)}`
: `${API_BASE}/ivanti/findings/counts/history`;
const [countsRes, anomalyRes] = await Promise.all([
fetch(`${API_BASE}/ivanti/findings/counts/history`, { credentials: 'include' }),
fetch(historyUrl, { credentials: 'include' }),
fetch(`${API_BASE}/ivanti/findings/anomaly/history`, { credentials: 'include' }),
]);
if (!cancelled) {
@@ -218,7 +221,7 @@ export default function IvantiCountsChart() {
};
load();
return () => { cancelled = true; };
}, []);
}, [teamsParam]);
const chartData = useMemo(
() => history.map(r => ({ ...r, date: fmtDate(r.date) })),