feat(overview): show historian session-pool depths on the HistorianGateway card
Asked whether the gateway's new session row also covered HistorianGateway. It did not, and could not: HistorianGateway publishes its pool depths on a different TIER with a different KEY SET. mxaccessgw active tier total / ready / faulted one check historiangw READY tier warmSessions / pool / … TWO checks The session row is located by the total/ready/faulted triple, so it correctly ignored these rather than half-matching them. This adds a sibling row instead of unifying the two behind a shared shape, because there is no such shape - inventing one would mean rendering a contract neither app publishes. No poller change was needed: the ready tier's body is already retained, so this is entirely a rendering change. Labelled "Sessions pooled", deliberately not "connections". warmSessions is pool DEPTH - warm pre-authenticated sessions held ready - and sessions are leased on demand, so the event pool legitimately sits at 0 until something reads events. Calling that a connection count would make a normal idle pool read as an outage, so 0 is shown plainly and the row goes red only on consecutiveFactoryFailures, which is an actual fault. Pools render read-then-event rather than in dictionary order, so the line is stable across polls and the pool an operator cares about comes first. 188 tests (183 + 5): ordering, that an idle pool is not painted red, that a failing factory is, a positive control on the omission assertion, and one pinning that neither app's row can render the other's data.
This commit is contained in:
@@ -306,6 +306,75 @@ public class InstanceCardTests : TestContext
|
||||
Assert.Empty(Render(Card()).FindAll("[data-testid=mxaccess-sessions]"));
|
||||
}
|
||||
|
||||
// ---- HistorianGateway session pools (ready tier) -------------------------------------------
|
||||
// A different app, a different tier and a different key set, so this is a sibling row rather
|
||||
// than a shared abstraction neither app actually publishes.
|
||||
|
||||
private static ZbHealthReport PoolReport(params (string Pool, int Warm, int Failures)[] pools) =>
|
||||
System.Text.Json.JsonSerializer.Deserialize<ZbHealthReport>(
|
||||
HealthBody.WithData(
|
||||
"Healthy",
|
||||
pools.Select(p => (
|
||||
$"historian-{p.Pool}-session-pool",
|
||||
"Healthy",
|
||||
(Dictionary<string, object>?)new Dictionary<string, object>(StringComparer.Ordinal)
|
||||
{
|
||||
["pool"] = p.Pool,
|
||||
["warmSessions"] = p.Warm,
|
||||
["consecutiveFactoryFailures"] = p.Failures,
|
||||
})).ToArray()))!;
|
||||
|
||||
[Fact]
|
||||
public void Card_ShowsHistorianPoolDepths_FromTheReadyTier()
|
||||
{
|
||||
var card = Render(Card(report: PoolReport(("read", 1, 0), ("event", 0, 0))));
|
||||
|
||||
var row = card.Find("[data-testid=historian-pools]");
|
||||
// read before event: the order an operator cares about, not dictionary order.
|
||||
Assert.Contains("read 1 · event 0", row.TextContent, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Card_DoesNotPaintAnIdlePoolAsBroken()
|
||||
{
|
||||
// 0 warm sessions is the normal idle state - sessions are leased on demand. Colouring it
|
||||
// red would train operators to ignore the one colour that should mean something.
|
||||
var card = Render(Card(report: PoolReport(("event", 0, 0))));
|
||||
|
||||
Assert.DoesNotContain("bad", card.Find("[data-testid=historian-pools] .v").ClassList, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Card_FlagsAPoolWhoseSessionFactoryIsFailing()
|
||||
{
|
||||
var card = Render(Card(report: PoolReport(("read", 0, 3))));
|
||||
|
||||
var value = card.Find("[data-testid=historian-pools] .v");
|
||||
Assert.Contains("3 fail", value.TextContent, StringComparison.Ordinal);
|
||||
Assert.Contains("bad", value.ClassList, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Card_OmitsThePoolRow_ForAppsWithNoSessionPools()
|
||||
{
|
||||
// Positive control first, or the omission below proves nothing.
|
||||
Assert.NotEmpty(Render(Card(report: PoolReport(("read", 1, 0)))).FindAll("[data-testid=historian-pools]"));
|
||||
|
||||
Assert.Empty(Render(Card(report: Report(("db", "Healthy", null)))).FindAll("[data-testid=historian-pools]"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Card_KeepsTheTwoSessionRowsIndependent()
|
||||
{
|
||||
// The gateway's active-tier session count and the historian's ready-tier pool depths use
|
||||
// different keys on different tiers; neither may render the other's row.
|
||||
var gateway = Render(Card(activeReport: SessionReport(total: 2, ready: 2)));
|
||||
Assert.Empty(gateway.FindAll("[data-testid=historian-pools]"));
|
||||
|
||||
var historian = Render(Card(report: PoolReport(("read", 1, 0))));
|
||||
Assert.Empty(historian.FindAll("[data-testid=mxaccess-sessions]"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Card_IgnoresActiveTierChecksThatAreNotSessionCounts()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user