using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; using ZB.MOM.WW.ScadaBridge.HealthMonitoring; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services; /// /// Default implementation. /// /// /// Pure projection — no new health plumbing. The /// already holds the latest /// SiteHealthReport per site (fed by the existing site→central health /// transport); this service reads those reports and resolves their connection /// NAME keys back to DataConnection.Id using the same site repository the /// design page loads from. Health reports key connections by Name, the /// page renders nodes by id, so the join is per-site Name → Id; doing it per-site /// avoids cross-site name collisions (two sites can each own a "MainPlc"). /// public sealed class ConnectionHealthQueryService : IConnectionHealthQueryService { private readonly ISiteRepository _siteRepository; private readonly ICentralHealthAggregator _healthAggregator; /// /// Initializes the service with the site repository (connection name→id source) /// and the central health aggregator (latest-report source). /// /// Source of sites and their connections. /// Source of the latest per-site health report. public ConnectionHealthQueryService( ISiteRepository siteRepository, ICentralHealthAggregator healthAggregator) { _siteRepository = siteRepository ?? throw new ArgumentNullException(nameof(siteRepository)); _healthAggregator = healthAggregator ?? throw new ArgumentNullException(nameof(healthAggregator)); } /// public async Task> GetConnectionHealthAsync( CancellationToken ct = default) { var sites = await _siteRepository.GetAllSitesAsync(ct); var connections = await _siteRepository.GetAllDataConnectionsAsync(ct); // Per-site connection name → id. Ordinal: connection names are case-sensitive // identifiers and the health report keys are produced from the same names. var idByName = connections .GroupBy(c => c.SiteId) .ToDictionary( g => g.Key, g => g .GroupBy(c => c.Name, StringComparer.Ordinal) .ToDictionary(n => n.Key, n => n.First().Id, StringComparer.Ordinal)); var result = new Dictionary(); foreach (var site in sites) { // Health reports are keyed by the site's machine identifier. var report = _healthAggregator.GetSiteState(site.SiteIdentifier)?.LatestReport; if (report is null) { // No report yet (just-started central / heartbeat-only): contribute // nothing — the page renders these connections as "unknown". continue; } if (!idByName.TryGetValue(site.Id, out var siteIds)) { continue; } foreach (var (connName, health) in report.DataConnectionStatuses) { // Unknown name (e.g. deleted centrally but still live at the site): // skip — there is no node to badge. if (siteIds.TryGetValue(connName, out var connId)) { result[connId] = health; } } } return result; } }