fix(dashboard/stats): Active = InProgress only, drop Pending + Stalled

Live data showed cross-tenant 7d breakdown of non-terminal sessions:
InProgress 11, Pending 43, Stalled 6 = 60 total. The Pending bucket
dominates for any tenant with regular WhiteGlove provisioning — those
sessions have completed pre-prov and are waiting for the end user to
power on the device, which can take days or weeks. Counting them under
"Currently enrolling" implies live activity that isn't happening.

Active now matches the literal in-flight status. Pending and Stalled
remain visible via the sessions list filters; if we want a richer
breakdown on the card row later, it can be split into separate cards
without further changing the Active semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-05-11 14:22:37 +02:00
parent 2284100915
commit bf974c4a57
2 changed files with 18 additions and 13 deletions
@@ -41,26 +41,28 @@ public class SessionStatsAggregationTests
}
[Fact]
public void Active_count_includes_InProgress_Pending_Stalled_but_not_terminal()
public void Active_count_is_InProgress_only()
{
// "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.
// "Currently enrolling" must match LIVE activity. Pending (WhiteGlove
// pre-prov complete, awaiting user power-on) and Stalled (>60min no
// progress) are non-terminal but NOT actively enrolling — Pending in
// particular can sit for days/weeks and would dominate the count for
// any tenant with regular WhiteGlove provisioning.
var now = DateTime.UtcNow;
var sessions = new[]
{
MakeSession(SessionStatus.InProgress, now.AddHours(-1)),
MakeSession(SessionStatus.Pending, now.AddHours(-2)),
MakeSession(SessionStatus.Stalled, now.AddHours(-3)),
MakeSession(SessionStatus.Succeeded, now.AddHours(-4), 600),
MakeSession(SessionStatus.Failed, now.AddHours(-5)),
MakeSession(SessionStatus.Unknown, now.AddHours(-6)),
MakeSession(SessionStatus.InProgress, now.AddHours(-2)),
MakeSession(SessionStatus.Pending, now.AddHours(-3)),
MakeSession(SessionStatus.Stalled, now.AddHours(-4)),
MakeSession(SessionStatus.Succeeded, now.AddHours(-5), 600),
MakeSession(SessionStatus.Failed, now.AddHours(-6)),
MakeSession(SessionStatus.Unknown, now.AddHours(-7)),
};
var stats = TableStorageService.AggregateSessionStats(sessions, days: 7);
Assert.Equal(3, stats.ActiveCount);
Assert.Equal(2, stats.ActiveCount);
}
[Fact]
@@ -841,8 +841,11 @@ namespace AutopilotMonitor.Functions.Services
switch (s.Status)
{
case SessionStatus.InProgress:
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
// Card label "Currently enrolling" — literal match to the agent's
// in-flight status. Pending (WhiteGlove pre-prov complete, awaiting
// user) often lingers for days/weeks and isn't actively enrolling;
// Stalled (>60min stale) isn't either. Both belong on dedicated
// breakdowns/filters, not on a card that implies live activity.
active++;
break;
case SessionStatus.Succeeded: