mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
fix(dashboard/stats): reset cards on scope change, pin "in flight" semantics
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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[]
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 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. */}
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-5 mb-2">
|
||||
<StatsCard
|
||||
title="Active Sessions"
|
||||
value={statsLoading && !dashboardStats ? "..." : (dashboardStats?.activeCount ?? 0).toString()}
|
||||
value={dashboardStats ? dashboardStats.activeCount.toString() : "..."}
|
||||
description="Currently enrolling"
|
||||
color="blue"
|
||||
/>
|
||||
<StatsCard
|
||||
title="Success Rate"
|
||||
value={statsLoading && !dashboardStats ? "..." : `${dashboardStats?.successRatePct ?? 0}%`}
|
||||
value={dashboardStats ? `${dashboardStats.successRatePct}%` : "..."}
|
||||
description="Last 7 days"
|
||||
color="green"
|
||||
/>
|
||||
<StatsCard
|
||||
title="Avg. Duration"
|
||||
value={statsLoading && !dashboardStats ? "..." : `${dashboardStats?.avgDurationMinutes ?? 0} min`}
|
||||
value={dashboardStats ? `${dashboardStats.avgDurationMinutes} min` : "..."}
|
||||
description="Last 7 days"
|
||||
color="purple"
|
||||
/>
|
||||
<StatsCard
|
||||
title="Total Today"
|
||||
value={statsLoading && !dashboardStats ? "..." : (dashboardStats?.totalToday ?? 0).toString()}
|
||||
value={dashboardStats ? dashboardStats.totalToday.toString() : "..."}
|
||||
description="Started today"
|
||||
color="indigo"
|
||||
/>
|
||||
<StatsCard
|
||||
title="Failed Today"
|
||||
value={statsLoading && !dashboardStats ? "..." : (dashboardStats?.failedToday ?? 0).toString()}
|
||||
value={dashboardStats ? dashboardStats.failedToday.toString() : "..."}
|
||||
description="Needs attention"
|
||||
color="red"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user