feat(ui): Audit KPI tiles on Health dashboard (#23 M7)
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.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
@page "/monitoring/health"
|
||||
@attribute [Authorize]
|
||||
@using ScadaLink.CentralUI.Components.Health
|
||||
@using ScadaLink.CentralUI.Services
|
||||
@using ScadaLink.Commons.Types
|
||||
@using ScadaLink.Commons.Types.Enums
|
||||
@using ScadaLink.Commons.Entities.Sites
|
||||
@using ScadaLink.Commons.Interfaces.Repositories
|
||||
@@ -10,6 +13,7 @@
|
||||
@inject ICentralHealthAggregator HealthAggregator
|
||||
@inject ISiteRepository SiteRepository
|
||||
@inject CommunicationService CommunicationService
|
||||
@inject IAuditLogQueryService AuditLogQueryService
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
@@ -56,6 +60,12 @@
|
||||
<div class="text-muted small mb-3">Notification Outbox KPIs unavailable: @_outboxKpiError</div>
|
||||
}
|
||||
|
||||
@* Audit Log (#23) M7 Bundle E — three KPI tiles for the Audit channel
|
||||
(volume / error rate / backlog). Refreshed alongside the site states. *@
|
||||
<AuditKpiTiles Snapshot="@_auditKpi"
|
||||
IsAvailable="@_auditKpiAvailable"
|
||||
ErrorMessage="@_auditKpiError" />
|
||||
|
||||
@if (_siteStates.Count == 0)
|
||||
{
|
||||
<div class="alert alert-info">No site health reports received yet.</div>
|
||||
@@ -347,6 +357,13 @@
|
||||
private bool _outboxKpiAvailable;
|
||||
private string? _outboxKpiError;
|
||||
|
||||
// Audit Log (#23) M7 Bundle E — Audit KPI tiles. Volume + error rate come
|
||||
// from a 1h aggregate over the central AuditLog table; backlog sums the
|
||||
// per-site SiteAuditBacklog.PendingCount via the health aggregator.
|
||||
private AuditLogKpiSnapshot? _auditKpi;
|
||||
private bool _auditKpiAvailable;
|
||||
private string? _auditKpiError;
|
||||
|
||||
private static bool SiteHasActiveErrors(SiteHealthState state)
|
||||
{
|
||||
var report = state.LatestReport;
|
||||
@@ -384,6 +401,7 @@
|
||||
{
|
||||
_siteStates = HealthAggregator.GetAllSiteStates();
|
||||
await LoadOutboxKpis();
|
||||
await LoadAuditKpis();
|
||||
}
|
||||
|
||||
private async Task LoadOutboxKpis()
|
||||
@@ -416,6 +434,24 @@
|
||||
private string OutboxTileValue(int value) =>
|
||||
_outboxKpiAvailable ? value.ToString() : "—";
|
||||
|
||||
// Audit KPI loader: wraps the service call so a transient DB outage degrades
|
||||
// the three tiles to em dashes with an inline error rather than killing the
|
||||
// dashboard. Mirrors LoadOutboxKpis's error handling shape.
|
||||
private async Task LoadAuditKpis()
|
||||
{
|
||||
try
|
||||
{
|
||||
_auditKpi = await AuditLogQueryService.GetKpiSnapshotAsync();
|
||||
_auditKpiAvailable = true;
|
||||
_auditKpiError = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_auditKpiAvailable = false;
|
||||
_auditKpiError = $"KPI query failed: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSiteName(string siteId)
|
||||
{
|
||||
return _siteNames.GetValueOrDefault(siteId, siteId);
|
||||
|
||||
Reference in New Issue
Block a user