From 068321baaa03dc0c9718e45fb4a8fab163d4455e Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Mon, 11 May 2026 14:00:02 +0200 Subject: [PATCH] fix(dashboard/stats): reset cards on scope change, pin "in flight" semantics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to b76a6cca. Two issues surfaced after deploy: 1. Toggling GA mode off left the cross-tenant numbers stuck on the cards. Hook now setStats(null) synchronously before refetching on every scope change, and the card render falls back to "..." whenever stats is null (covers initial load, post-toggle reset, and fetch errors — previously an error would silently show 0). 2. "Active Sessions" intent: the user wants in-flight sessions in the broader sense, which matches the original code. AggregateSessionStats counts InProgress + Pending (WhiteGlove waiting on user) + Stalled (>60min no progress, non-terminal, can heal back to InProgress). Test renamed + comment pins the rationale so a future "tighten to InProgress only" refactor knows what it's changing. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../SessionStatsAggregationTests.cs | 4 ++++ .../Services/TableStorageService.Sessions.cs | 4 ++-- .../app/dashboard/hooks/useDashboardStats.ts | 4 ++++ .../app/dashboard/page.tsx | 17 ++++++++++------- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Backend/AutopilotMonitor.Functions.Tests/SessionStatsAggregationTests.cs b/src/Backend/AutopilotMonitor.Functions.Tests/SessionStatsAggregationTests.cs index 8245a756..bc6c95ac 100644 --- a/src/Backend/AutopilotMonitor.Functions.Tests/SessionStatsAggregationTests.cs +++ b/src/Backend/AutopilotMonitor.Functions.Tests/SessionStatsAggregationTests.cs @@ -43,6 +43,10 @@ public class SessionStatsAggregationTests [Fact] public void Active_count_includes_InProgress_Pending_Stalled_but_not_terminal() { + // "Currently enrolling" covers everything still in flight: InProgress + // (mid-enrollment), Pending (WhiteGlove waiting for user), Stalled + // (>60min stale — non-terminal, can heal back to InProgress). Terminal + // states (Succeeded / Failed) and Unknown are NOT in flight. var now = DateTime.UtcNow; var sessions = new[] { diff --git a/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Sessions.cs b/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Sessions.cs index 9332555e..3869a5ac 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Sessions.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Sessions.cs @@ -841,8 +841,8 @@ namespace AutopilotMonitor.Functions.Services switch (s.Status) { case SessionStatus.InProgress: - case SessionStatus.Pending: - case SessionStatus.Stalled: + case SessionStatus.Pending: // WhiteGlove pre-prov complete, waiting for user — still in flight + case SessionStatus.Stalled: // >60min no progress, non-terminal — can heal back to InProgress active++; break; case SessionStatus.Succeeded: diff --git a/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardStats.ts b/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardStats.ts index 4dedcf4d..d39ac850 100644 --- a/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardStats.ts +++ b/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardStats.ts @@ -175,9 +175,13 @@ export function useDashboardStats({ }, [fetchStats]); // Refetch on scope change (tenant switch, GA toggle, filter Submit, days change). + // Reset stats synchronously so any previous-scope numbers (e.g. cross-tenant + // sums when toggling GA off) don't linger on the cards if the refetch errors + // out or returns nothing — the user sees "..." instead of stale data. useEffect(() => { if (disabled) return; if (!globalAdminMode && !tenantId) return; + setStats(null); fetchStats(); // fetchStats is stable; intentionally not in deps to avoid re-running on identity flips. // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/Web/autopilot-monitor-web/app/dashboard/page.tsx b/src/Web/autopilot-monitor-web/app/dashboard/page.tsx index 96c2dab1..4e550889 100644 --- a/src/Web/autopilot-monitor-web/app/dashboard/page.tsx +++ b/src/Web/autopilot-monitor-web/app/dashboard/page.tsx @@ -134,7 +134,7 @@ function HomeContent() { // the client has paginated into view. Refreshes on SignalR newSession/newevents // (debounced) and on SignalR reconnect to recover from any missed messages. const isRegularUser = !!user && !user.isTenantAdmin && !user.isGlobalAdmin && user.role !== "Operator"; - const { stats: dashboardStats, loading: statsLoading } = useDashboardStats({ + const { stats: dashboardStats } = useDashboardStats({ tenantId, globalAdminMode, submittedTenantIdFilter, @@ -348,35 +348,38 @@ function HomeContent() { )} - {/* Stats cards — server-aggregated (see useDashboardStats) */} + {/* Stats cards — server-aggregated (see useDashboardStats). + `dashboardStats === null` covers both initial load and post-scope-change + reset; show a non-zero placeholder so a fetch error doesn't masquerade + as legitimate zeros. */}