feat(alarm-summary): live-cache-driven updates with poll fallback (plan #10 T5)
Wire the operator Alarm Summary page to the transient per-site live alarm cache (ISiteAlarmLiveCache, T4). Live-cache-first: on site select the page subscribes and rebuilds rows/rollup from near-real-time onChanged deltas; the 15s poll is kept untouched as the authority for NotReporting and as the safety net whenever the cache is not live (pre-seed or degraded stream). Both paths mutate shared state only via the Blazor dispatcher, so they never race, and each rebuild is an idempotent snapshot. - IAlarmSummaryService.BuildFromLiveAlarms: flattens a live AlarmStateChanged snapshot to AlarmSummaryRows with the same deterministic instance-then-name sort as GetSiteAlarmsAsync; NotReporting always empty on the live path. - AlarmSummary.razor: inject ISiteAlarmLiveCache; subscribe on select, re-subscribe on site change, unsubscribe on leave + Dispose. - Tests: service BuildFromLiveAlarms flatten/sort/empty; page subscribe, poll-fallback render, onChanged rebuild, unsubscribe on leave/change/dispose. Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -127,6 +127,26 @@ public sealed class AlarmSummaryService : IAlarmSummaryService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AlarmSummaryResult BuildFromLiveAlarms(IReadOnlyList<AlarmStateChanged> alarms)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(alarms);
|
||||
|
||||
// Flatten one row per alarm (instance from the alarm itself, mirroring
|
||||
// FetchInstanceAsync) and apply the SAME deterministic instance-then-name
|
||||
// sort as GetSiteAlarmsAsync so the live and poll paths render identically.
|
||||
var orderedRows = alarms
|
||||
.Select(alarm => new AlarmSummaryRow(alarm.InstanceUniqueName, alarm))
|
||||
.OrderBy(r => r.InstanceUniqueName, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(r => r.Alarm.AlarmName, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
// The live cache is alarm-only, so it cannot enumerate "not reporting"
|
||||
// instances — left empty here; the periodic poll (GetSiteAlarmsAsync)
|
||||
// remains the authority for that list.
|
||||
return new AlarmSummaryResult(orderedRows, Array.Empty<string>());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AlarmRollup ComputeRollup(IReadOnlyList<AlarmSummaryRow> rows)
|
||||
{
|
||||
|
||||
@@ -42,6 +42,28 @@ public interface IAlarmSummaryService
|
||||
/// </returns>
|
||||
Task<AlarmSummaryResult> GetSiteAlarmsAsync(int siteId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Builds an <see cref="AlarmSummaryResult"/> directly from an in-memory live
|
||||
/// snapshot (plan #10, Task 5) — the current alarms served by
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.Communication.ISiteAlarmLiveCache.GetCurrentAlarms"/>
|
||||
/// — instead of fanning out per-instance snapshot Asks. Each
|
||||
/// <see cref="AlarmStateChanged"/> is flattened to one
|
||||
/// <see cref="AlarmSummaryRow"/> (instance taken from
|
||||
/// <see cref="AlarmStateChanged.InstanceUniqueName"/>) and ordered with the SAME
|
||||
/// deterministic instance-then-alarm-name sort as
|
||||
/// <see cref="GetSiteAlarmsAsync"/>, so the two paths are interchangeable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The live cache carries only alarm state, so it cannot know which Enabled
|
||||
/// instances are silent-but-reporting versus not reporting at all;
|
||||
/// <see cref="AlarmSummaryResult.NotReportingInstances"/> is therefore always
|
||||
/// empty on this path. The page keeps its periodic <see cref="GetSiteAlarmsAsync"/>
|
||||
/// poll as the authority for "not reporting" (and as a live-stream safety net).
|
||||
/// </remarks>
|
||||
/// <param name="alarms">The current live alarm snapshot for a site.</param>
|
||||
/// <returns>An <see cref="AlarmSummaryResult"/> whose <see cref="AlarmSummaryResult.NotReportingInstances"/> is empty.</returns>
|
||||
AlarmSummaryResult BuildFromLiveAlarms(IReadOnlyList<AlarmStateChanged> alarms);
|
||||
|
||||
/// <summary>
|
||||
/// Pure roll-up over a set of <see cref="AlarmSummaryRow"/>s. Exposed so the
|
||||
/// page (and tests) can recompute the headline tiles without re-querying.
|
||||
|
||||
Reference in New Issue
Block a user