refactor(kpi): K13/K15 trend review fixups — per-metric isolation, disable-during-load + logging, loading-flag finally, test coverage
This commit is contained in:
+50
-26
@@ -178,20 +178,20 @@
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<KpiTrendChart Title="Queue Depth"
|
||||
Points="@_queueDepthSeries"
|
||||
IsAvailable="@_trendsAvailable"
|
||||
ErrorMessage="@_trendsError" />
|
||||
IsAvailable="@_queueDepthAvailable"
|
||||
ErrorMessage="@_queueDepthError" />
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<KpiTrendChart Title="Parked"
|
||||
Points="@_parkedSeries"
|
||||
IsAvailable="@_trendsAvailable"
|
||||
ErrorMessage="@_trendsError" />
|
||||
IsAvailable="@_parkedAvailable"
|
||||
ErrorMessage="@_parkedError" />
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<KpiTrendChart Title="Delivered / interval"
|
||||
Points="@_deliveredSeries"
|
||||
IsAvailable="@_trendsAvailable"
|
||||
ErrorMessage="@_trendsError" />
|
||||
IsAvailable="@_deliveredAvailable"
|
||||
ErrorMessage="@_deliveredError" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -213,13 +213,24 @@
|
||||
|
||||
// ── Trends (T11: first KPI-history consumer) ──
|
||||
// Window in hours: 24h (default) or 168h (7d). Toggling re-queries.
|
||||
// Per-metric isolation (mirrors the K14 SiteCallsReport pattern): each metric
|
||||
// carries its own series + availability + error, each loaded via its own
|
||||
// try/catch so one metric's failure only blanks that one chart and the others
|
||||
// still render their already-fetched data.
|
||||
private int _windowHours = 24;
|
||||
private bool _trendsLoading;
|
||||
private bool _trendsAvailable = true;
|
||||
private string? _trendsError;
|
||||
|
||||
private IReadOnlyList<KpiSeriesPoint>? _queueDepthSeries;
|
||||
private bool _queueDepthAvailable = true;
|
||||
private string? _queueDepthError;
|
||||
|
||||
private IReadOnlyList<KpiSeriesPoint>? _parkedSeries;
|
||||
private bool _parkedAvailable = true;
|
||||
private string? _parkedError;
|
||||
|
||||
private IReadOnlyList<KpiSeriesPoint>? _deliveredSeries;
|
||||
private bool _deliveredAvailable = true;
|
||||
private string? _deliveredError;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -264,21 +275,15 @@
|
||||
var toUtc = DateTime.UtcNow;
|
||||
var fromUtc = toUtc - TimeSpan.FromHours(_windowHours);
|
||||
|
||||
// Best-effort: one query failure must NOT break the page — on any
|
||||
// exception the charts fall back to the unavailable placeholder while
|
||||
// the KPI tiles above stay rendered.
|
||||
_queueDepthSeries = await GetSeries("queueDepth", fromUtc, toUtc);
|
||||
_parkedSeries = await GetSeries("parkedCount", fromUtc, toUtc);
|
||||
_deliveredSeries = await GetSeries("deliveredLastInterval", fromUtc, toUtc);
|
||||
|
||||
_trendsAvailable = true;
|
||||
_trendsError = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_trendsAvailable = false;
|
||||
_trendsError = "Trend data unavailable.";
|
||||
Logger.LogWarning(ex, "Failed to load notification-outbox KPI trend series.");
|
||||
// Per-metric isolation: each series is loaded via its own try/catch so
|
||||
// one metric's failure only blanks that one chart while the others still
|
||||
// render their fetched data — and a throw never breaks the KPI tiles above.
|
||||
(_queueDepthSeries, _queueDepthAvailable, _queueDepthError) =
|
||||
await LoadSeries("queueDepth", fromUtc, toUtc);
|
||||
(_parkedSeries, _parkedAvailable, _parkedError) =
|
||||
await LoadSeries("parkedCount", fromUtc, toUtc);
|
||||
(_deliveredSeries, _deliveredAvailable, _deliveredError) =
|
||||
await LoadSeries("deliveredLastInterval", fromUtc, toUtc);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -286,9 +291,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
private Task<IReadOnlyList<KpiSeriesPoint>> GetSeries(string metric, DateTime fromUtc, DateTime toUtc) =>
|
||||
KpiHistory.GetSeriesAsync(
|
||||
KpiSources.NotificationOutbox, metric, KpiScopes.Global, scopeKey: null, fromUtc, toUtc);
|
||||
/// <summary>
|
||||
/// Fetch one NotificationOutbox / Global series, swallowing any failure into the
|
||||
/// chart's unavailable state. Returns the points (null on failure), the
|
||||
/// availability flag, and a short error message for the chart placeholder.
|
||||
/// </summary>
|
||||
private async Task<(IReadOnlyList<KpiSeriesPoint>? Points, bool Available, string? Error)>
|
||||
LoadSeries(string metric, DateTime fromUtc, DateTime toUtc)
|
||||
{
|
||||
try
|
||||
{
|
||||
var points = await KpiHistory.GetSeriesAsync(
|
||||
KpiSources.NotificationOutbox, metric, KpiScopes.Global, scopeKey: null, fromUtc, toUtc);
|
||||
return (points, true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// A KPI-history hiccup must never break the page — the chart degrades to
|
||||
// its unavailable placeholder while the KPI tiles above stay rendered.
|
||||
Logger.LogWarning(ex, "Failed to load notification-outbox KPI trend series for metric {Metric}.", metric);
|
||||
return (null, false, "Trend data unavailable.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadGlobalKpis()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user