perf(adminui): /hosts rebuilds driver rows on the tick, not per health publish (R04 P4)

OnSnapshotChanged used to re-project every cluster group and re-render the
whole page for each driver publish, per open tab. It now only marks a
CoalescingDirtyFlag; the 5 s tick rebuilds when the flag is set or the config
load changed (or evicted) anything.

Refs #745
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 17:28:23 -04:00
parent a04196d725
commit 348ffaec1c
4 changed files with 100 additions and 10 deletions
@@ -0,0 +1,53 @@
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Hosts;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Hosts;
/// <summary>
/// R04 P4: a driver health publish only marks <c>/hosts</c> dirty; the page's 5 s tick rebuilds and
/// renders at most once, however many publishes arrived.
/// </summary>
[Trait("Category", "Unit")]
public sealed class HostsRenderCoalescingTests
{
[Fact]
public void Many_marks_coalesce_into_one_consume()
{
var flag = new CoalescingDirtyFlag();
for (var i = 0; i < 200; i++) flag.Mark();
flag.TryConsume().ShouldBeTrue();
flag.TryConsume().ShouldBeFalse();
}
[Fact]
public void A_fresh_flag_is_clean_and_a_mark_after_consume_is_seen()
{
var flag = new CoalescingDirtyFlag();
flag.TryConsume().ShouldBeFalse();
// Positive control: the flag is not stuck clean.
flag.Mark();
flag.TryConsume().ShouldBeTrue();
}
[Fact]
public void Concurrent_marks_are_never_lost()
{
var flag = new CoalescingDirtyFlag();
Parallel.For(0, 1000, _ => flag.Mark());
flag.TryConsume().ShouldBeTrue();
}
/// <summary>Source pin (no bUnit): the snapshot handler must not render or rebuild.</summary>
[Fact]
public void Snapshot_handler_only_marks_and_the_tick_consumes()
{
var src = AdminUiSource.Read("Components", "Pages", "Hosts.razor");
src.ShouldContain("private void OnSnapshotChanged(DriverHealthChanged _) => _driversDirty.Mark();");
src.ShouldContain("if (_driversDirty.TryConsume() | configChanged) RebuildDriverGroups();");
}
}
@@ -128,7 +128,7 @@ public sealed class HostsEvictionWiringTests
{
var src = AdminUiSource.Read("Components", "Pages", "Hosts.razor");
var load = src.IndexOf("private async Task LoadConfigAsync()", StringComparison.Ordinal);
var load = src.IndexOf("private async Task<bool> LoadConfigAsync()", StringComparison.Ordinal);
load.ShouldBeGreaterThan(0);
var firstCatch = src.IndexOf("catch (Exception ex) when (ex is OperationCanceledException", load, StringComparison.Ordinal);
firstCatch.ShouldBeGreaterThan(load);