refactor(kpi): shared public KpiMetrics catalog — source consts + UI pages key off one symbol (#178)

This commit is contained in:
Joseph Doherty
2026-06-19 02:04:10 -04:00
parent 47f5ca687c
commit e51104af5f
9 changed files with 135 additions and 30 deletions
@@ -36,11 +36,12 @@ public sealed class AuditLogKpiSampleSource : IKpiSampleSource
/// </summary>
private static readonly TimeSpan Window = TimeSpan.FromHours(1);
// Metric catalog — the exact strings persisted in KpiSample.Metric. Stable
// identifiers the recorder + UI trend charts key on; do not rename.
private const string TotalEventsLastHourMetric = "totalEventsLastHour";
private const string ErrorEventsLastHourMetric = "errorEventsLastHour";
private const string BacklogTotalMetric = "backlogTotal";
// Metric catalog — the exact strings persisted in KpiSample.Metric. All three are
// charted, so they share the public Commons catalog: source + UI trend page key off
// one symbol (#178). Stable identifiers; do not rename (values are persisted).
private const string TotalEventsLastHourMetric = KpiMetrics.AuditLog.TotalEventsLastHour;
private const string ErrorEventsLastHourMetric = KpiMetrics.AuditLog.ErrorEventsLastHour;
private const string BacklogTotalMetric = KpiMetrics.AuditLog.BacklogTotal;
private readonly IAuditLogRepository _repository;
@@ -293,9 +293,9 @@ public partial class AuditLogPage : IDisposable
/// <summary>The metrics rendered in the panel, in display order.</summary>
private static readonly (string Metric, string Title, string? Unit)[] TrendMetrics =
{
("totalEventsLastHour", "Events / hour", null),
("errorEventsLastHour", "Error events / hour", null),
("backlogTotal", "Backlog", null),
(KpiMetrics.AuditLog.TotalEventsLastHour, "Events / hour", null),
(KpiMetrics.AuditLog.ErrorEventsLastHour, "Error events / hour", null),
(KpiMetrics.AuditLog.BacklogTotal, "Backlog", null),
};
/// <summary>Per-metric series state, keyed by the metric name.</summary>
@@ -593,13 +593,13 @@
var siteId = _trendSiteId;
(_connectionsDownSeries, _connectionsDownAvailable, _connectionsDownError) =
await LoadTrendSeriesAsync("connectionsDown", siteId, fromUtc, toUtc);
await LoadTrendSeriesAsync(KpiMetrics.SiteHealth.ConnectionsDown, siteId, fromUtc, toUtc);
(_deadLettersSeries, _deadLettersAvailable, _deadLettersError) =
await LoadTrendSeriesAsync("deadLetters", siteId, fromUtc, toUtc);
await LoadTrendSeriesAsync(KpiMetrics.SiteHealth.DeadLetters, siteId, fromUtc, toUtc);
(_scriptErrorsSeries, _scriptErrorsAvailable, _scriptErrorsError) =
await LoadTrendSeriesAsync("scriptErrors", siteId, fromUtc, toUtc);
await LoadTrendSeriesAsync(KpiMetrics.SiteHealth.ScriptErrors, siteId, fromUtc, toUtc);
(_sfBufferDepthSeries, _sfBufferDepthAvailable, _sfBufferDepthError) =
await LoadTrendSeriesAsync("sfBufferDepth", siteId, fromUtc, toUtc);
await LoadTrendSeriesAsync(KpiMetrics.SiteHealth.SfBufferDepth, siteId, fromUtc, toUtc);
}
finally
{
@@ -279,11 +279,11 @@
// 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);
await LoadSeries(KpiMetrics.NotificationOutbox.QueueDepth, fromUtc, toUtc);
(_parkedSeries, _parkedAvailable, _parkedError) =
await LoadSeries("parkedCount", fromUtc, toUtc);
await LoadSeries(KpiMetrics.NotificationOutbox.ParkedCount, fromUtc, toUtc);
(_deliveredSeries, _deliveredAvailable, _deliveredError) =
await LoadSeries("deliveredLastInterval", fromUtc, toUtc);
await LoadSeries(KpiMetrics.NotificationOutbox.DeliveredLastInterval, fromUtc, toUtc);
}
finally
{
@@ -591,11 +591,11 @@ public partial class SiteCallsReport
var fromUtc = toUtc - TimeSpan.FromHours(_windowHours);
(_bufferedSeries, _bufferedAvailable, _bufferedError) =
await LoadSeriesAsync("buffered", fromUtc, toUtc);
await LoadSeriesAsync(KpiMetrics.SiteCallAudit.Buffered, fromUtc, toUtc);
(_parkedSeries, _parkedAvailable, _parkedError) =
await LoadSeriesAsync("parked", fromUtc, toUtc);
await LoadSeriesAsync(KpiMetrics.SiteCallAudit.Parked, fromUtc, toUtc);
(_failedSeries, _failedAvailable, _failedError) =
await LoadSeriesAsync("failedLastInterval", fromUtc, toUtc);
await LoadSeriesAsync(KpiMetrics.SiteCallAudit.FailedLastInterval, fromUtc, toUtc);
}
finally
{
@@ -0,0 +1,98 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
/// <summary>
/// Canonical <em>charted</em> KPI metric-name identifiers (M6 "KPI History &amp;
/// Trends") — the value of <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi.KpiSample.Metric"/>
/// for every metric a Central UI trend chart renders. Each owning
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Kpi.IKpiSampleSource"/> emits
/// these strings and each trend page keys its <c>GetSeriesAsync</c> lookup on the same
/// strings; promoting them to one shared public symbol means the source and the page key
/// off a single declaration, so a future rename becomes a compiler-enforced single edit
/// rather than a silently-blanked chart.
/// </summary>
/// <remarks>
/// <para>
/// Each constant's value is the metric string <em>as persisted</em> in the tall/EAV
/// <c>KpiSample</c> table and matched at query time. These values are therefore part of
/// the on-disk data contract: this catalog is a symbol-promotion of the existing literals,
/// <strong>not</strong> a rename — changing any value would orphan historical samples.
/// </para>
/// <para>
/// Only the metrics a trend page actually charts are catalogued here. Sources may emit
/// additional internal metrics (e.g. the Notification Outbox <c>stuckCount</c> /
/// <c>oldestPendingAgeSeconds</c>, the Site Call Audit <c>deliveredLastInterval</c> /
/// <c>stuck</c> / <c>oldestPendingAgeSeconds</c>, and the bulk of the Site Health catalog)
/// that no page references; those stay private to their source until a page charts them.
/// Constants are grouped by source via nested static classes mirroring
/// <see cref="KpiSources"/>.
/// </para>
/// </remarks>
public static class KpiMetrics
{
/// <summary>
/// Charted Notification Outbox (#21) metrics — <see cref="KpiSources.NotificationOutbox"/>.
/// Rendered by the Central UI Notification Outbox KPIs trend panel.
/// </summary>
public static class NotificationOutbox
{
/// <summary>Pending-queue depth (rows awaiting delivery).</summary>
public const string QueueDepth = "queueDepth";
/// <summary>Parked-row count.</summary>
public const string ParkedCount = "parkedCount";
/// <summary>Rows delivered in the last KPI interval.</summary>
public const string DeliveredLastInterval = "deliveredLastInterval";
}
/// <summary>
/// Charted Site Call Audit (#22) metrics — <see cref="KpiSources.SiteCallAudit"/>.
/// Rendered by the Central UI Site Calls report trend panel.
/// </summary>
public static class SiteCallAudit
{
/// <summary>Buffered (non-terminal) cached-call count.</summary>
public const string Buffered = "buffered";
/// <summary>Parked cached-call count.</summary>
public const string Parked = "parked";
/// <summary>Cached calls that failed permanently in the last KPI interval.</summary>
public const string FailedLastInterval = "failedLastInterval";
}
/// <summary>
/// Charted Audit Log (#23) metrics — <see cref="KpiSources.AuditLog"/>.
/// Rendered by the Central UI Audit Log page trend panel.
/// </summary>
public static class AuditLog
{
/// <summary>Audit events recorded in the trailing hour.</summary>
public const string TotalEventsLastHour = "totalEventsLastHour";
/// <summary>Error-row audit events recorded in the trailing hour.</summary>
public const string ErrorEventsLastHour = "errorEventsLastHour";
/// <summary>Total pending (not-yet-forwarded) audit backlog.</summary>
public const string BacklogTotal = "backlogTotal";
}
/// <summary>
/// Charted Site Health (#11) metrics — <see cref="KpiSources.SiteHealth"/>.
/// Rendered by the Central UI Health-dashboard per-site trend panel.
/// </summary>
public static class SiteHealth
{
/// <summary>Data connections not in the Connected state.</summary>
public const string ConnectionsDown = "connectionsDown";
/// <summary>Dead-letter count reported by the site.</summary>
public const string DeadLetters = "deadLetters";
/// <summary>Script-execution error count reported by the site.</summary>
public const string ScriptErrors = "scriptErrors";
/// <summary>Summed store-and-forward buffer depth across all buffers.</summary>
public const string SfBufferDepth = "sfBufferDepth";
}
}
@@ -34,13 +34,15 @@ namespace ZB.MOM.WW.ScadaBridge.HealthMonitoring.Kpi;
public sealed class SiteHealthKpiSampleSource : IKpiSampleSource
{
// ── Metric catalog (the M6-agreed metric-name strings for this source) ──
// Declaration order matches the emission order in AddSiteSnapshot.
// Declaration order matches the emission order in AddSiteSnapshot. Charted metrics
// share the public Commons catalog so source + UI trend page key off one symbol
// (#178); the uncharted internal metrics stay private here.
private const string MetricConnectionsUp = "connectionsUp";
private const string MetricConnectionsDown = "connectionsDown";
private const string MetricScriptErrors = "scriptErrors";
private const string MetricConnectionsDown = KpiMetrics.SiteHealth.ConnectionsDown;
private const string MetricScriptErrors = KpiMetrics.SiteHealth.ScriptErrors;
private const string MetricAlarmEvalErrors = "alarmEvalErrors";
private const string MetricSfBufferDepth = "sfBufferDepth";
private const string MetricDeadLetters = "deadLetters";
private const string MetricSfBufferDepth = KpiMetrics.SiteHealth.SfBufferDepth;
private const string MetricDeadLetters = KpiMetrics.SiteHealth.DeadLetters;
private const string MetricParkedMessages = "parkedMessages";
private const string MetricDeployedInstances = "deployedInstances";
private const string MetricEnabledInstances = "enabledInstances";
@@ -34,10 +34,12 @@ namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox.Kpi;
/// </remarks>
public sealed class NotificationOutboxKpiSampleSource : IKpiSampleSource
{
private const string MetricQueueDepth = "queueDepth";
// Charted metrics share the public Commons catalog so source + UI trend page key
// off one symbol (#178). The uncharted internal metrics stay private here.
private const string MetricQueueDepth = KpiMetrics.NotificationOutbox.QueueDepth;
private const string MetricStuckCount = "stuckCount";
private const string MetricParkedCount = "parkedCount";
private const string MetricDeliveredLastInterval = "deliveredLastInterval";
private const string MetricParkedCount = KpiMetrics.NotificationOutbox.ParkedCount;
private const string MetricDeliveredLastInterval = KpiMetrics.NotificationOutbox.DeliveredLastInterval;
private const string MetricOldestPendingAgeSeconds = "oldestPendingAgeSeconds";
private readonly INotificationOutboxRepository _repository;
@@ -36,10 +36,12 @@ namespace ZB.MOM.WW.ScadaBridge.SiteCallAudit.Kpi;
public sealed class SiteCallAuditKpiSampleSource : IKpiSampleSource
{
// ── Metric catalog (the M6-agreed metric-name strings for this source) ──
// Declaration order matches the emission order in AddSnapshot.
private const string MetricBuffered = "buffered";
private const string MetricParked = "parked";
private const string MetricFailedLastInterval = "failedLastInterval";
// Declaration order matches the emission order in AddSnapshot. Charted metrics
// share the public Commons catalog so source + UI trend page key off one symbol
// (#178); the uncharted internal metrics stay private here.
private const string MetricBuffered = KpiMetrics.SiteCallAudit.Buffered;
private const string MetricParked = KpiMetrics.SiteCallAudit.Parked;
private const string MetricFailedLastInterval = KpiMetrics.SiteCallAudit.FailedLastInterval;
private const string MetricDeliveredLastInterval = "deliveredLastInterval";
private const string MetricStuck = "stuck";
private const string MetricOldestPendingAgeSeconds = "oldestPendingAgeSeconds";