943c2ced39
Adds three KPI tiles to the central Health dashboard for the Audit channel: volume (rows in the last hour), error rate (Failed/Parked/Discarded over total), and backlog (sum of SiteAuditBacklog.PendingCount across all sites). Repo + service: - IAuditLogRepository.GetKpiSnapshotAsync(window, nowUtc) — single aggregate SELECT over the trailing window returning total + error counts; nowUtc is optional for production callers and pinned by integration tests against the shared MSSQL fixture so the global counts are deterministic. - AuditLogQueryService.GetKpiSnapshotAsync() — composes the repo aggregate with a sum of SiteAuditBacklog.PendingCount read from ICentralHealthAggregator. - AuditLogKpiSnapshot record in Commons/Types/. UI: - New AuditKpiTiles Blazor component (Components/Health/) — three Bootstrap card-tiles, click navigates to /audit/log with the matching pre-filter. - Health.razor wires the tiles in alongside the existing Notification Outbox KPIs; LoadAuditKpis() runs on every 10s refresh tick and degrades to em dashes + inline error if the query fails. - AuditLogPage extended to parse ?status= so the error-rate tile drill-in (?status=Failed) auto-loads the grid. Tests: - AuditLogRepositoryTests: GetKpiSnapshotAsync mixed-status + empty-window cases against the MSSQL migration fixture. - AuditLogQueryServiceTests: forwarding + backlog composition; sites with null SiteAuditBacklog contribute zero. - AuditKpiTilesTests: 9 bUnit tests covering tile render, error-rate maths with safe zero-events handling, em-dash unavailable path, click-through navigation, and warning/danger border thresholds. - HealthPageTests: new Renders_AuditKpiTiles_WithValues plus IAuditLogQueryService stub registration in the constructor so existing outbox tests still pass. - AuditLogPageScaffoldTests: ?status=Failed auto-load + unknown status drop.
70 lines
2.8 KiB
C#
70 lines
2.8 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Default <see cref="IAuditLogQueryService"/> implementation — a thin pass-through
|
|
/// to <see cref="IAuditLogRepository.QueryAsync"/>. Default page size is 100 (the
|
|
/// AuditResultsGrid default for #23 M7).
|
|
/// </summary>
|
|
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<IReadOnlyList<AuditEvent>> QueryAsync(
|
|
AuditLogQueryFilter filter,
|
|
AuditLogPaging? paging = null,
|
|
CancellationToken ct = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(filter);
|
|
var effective = paging ?? new AuditLogPaging(DefaultPageSize);
|
|
return _repository.QueryAsync(filter, effective, ct);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<AuditLogKpiSnapshot> 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 };
|
|
}
|
|
}
|