using ScadaLink.Commons.Entities.Audit; using ScadaLink.Commons.Interfaces.Repositories; using ScadaLink.Commons.Types; using ScadaLink.Commons.Types.Audit; using ScadaLink.HealthMonitoring; namespace ScadaLink.CentralUI.Services; /// /// Default implementation โ€” a thin pass-through /// to . Default page size is 100 (the /// AuditResultsGrid default for #23 M7). /// public sealed class AuditLogQueryService : IAuditLogQueryService { // M7 Bundle E (T13): trailing window for the Health dashboard's Audit KPI tiles. // Hard-coded here rather than configurable because the requirement // (Component-AuditLog.md ยง"Health & KPIs") fixes "rows/min over the last hour" // and "% errors over the last hour" as the KPI definition. private static readonly TimeSpan KpiWindow = TimeSpan.FromHours(1); private readonly IAuditLogRepository _repository; private readonly ICentralHealthAggregator _healthAggregator; public AuditLogQueryService( IAuditLogRepository repository, ICentralHealthAggregator healthAggregator) { _repository = repository ?? throw new ArgumentNullException(nameof(repository)); _healthAggregator = healthAggregator ?? throw new ArgumentNullException(nameof(healthAggregator)); } public int DefaultPageSize => 100; public Task> QueryAsync( AuditLogQueryFilter filter, AuditLogPaging? paging = null, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(filter); var effective = paging ?? new AuditLogPaging(DefaultPageSize); return _repository.QueryAsync(filter, effective, ct); } /// public async Task GetKpiSnapshotAsync(CancellationToken ct = default) { // 1. Volume + error counts: aggregate over the trailing 1h window. // BacklogTotal is left at 0 by the repository โ€” we fill it from the // in-memory health aggregator below. var repoSnapshot = await _repository.GetKpiSnapshotAsync(KpiWindow, nowUtc: null, ct); // 2. Backlog: sum PendingCount across every site's latest report. // Sites that have not yet reported or whose reporter is disabled // leave SiteAuditBacklog null โ€” those contribute zero (a Missing // snapshot is "unknown", not "zero", but the tile is best-effort). long backlog = 0; foreach (var state in _healthAggregator.GetAllSiteStates().Values) { var pending = state.LatestReport?.SiteAuditBacklog?.PendingCount; if (pending is > 0) { backlog += pending.Value; } } return repoSnapshot with { BacklogTotal = backlog }; } }