From 9b78e6071d50f76c166a5120286a0b4247ac08c2 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 16 Jun 2026 19:46:44 -0400 Subject: [PATCH] fix(dcl): identify MxGateway native alarms by object-relative reference Surface native (Galaxy/MxGateway) alarms by their object-relative reference (e.g. "Z28061.HeartbeatTimeoutAlarm") instead of the gateway's full provider reference ("Galaxy!.."). The area is already preserved in Category and the object reference is globally unique within the galaxy, so the full provider prefix added only noise to the alarm identity operators see. MxGatewayAlarmMapper.MapTransition/MapSnapshot now set SourceReference from SourceObjectReference, falling back to AlarmFullReference only when the gateway omits the object reference. +2 mapper tests; full DCL suite green (158). --- .../Adapters/MxGatewayAlarmMapper.cs | 14 +++++- .../MxGatewayAlarmMapperTests.cs | 48 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/MxGatewayAlarmMapper.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/MxGatewayAlarmMapper.cs index e3a7b7c5..c91d6765 100644 --- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/MxGatewayAlarmMapper.cs +++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/MxGatewayAlarmMapper.cs @@ -86,7 +86,14 @@ public static class MxGatewayAlarmMapper /// The gateway alarm transition event proto message to map. /// The protocol-neutral . public static NativeAlarmTransition MapTransition(OnAlarmTransitionEvent body) => new( - SourceReference: body.AlarmFullReference, + // Identify the condition by the object-relative reference (e.g. + // "Z28061.HeartbeatTimeoutAlarm") rather than the gateway's full provider + // reference ("Galaxy!.."). The area is preserved in + // Category; the object reference is globally unique within the galaxy and + // is the form operators expect. Falls back to the full reference only if + // the gateway omits the object reference. + SourceReference: string.IsNullOrEmpty(body.SourceObjectReference) + ? body.AlarmFullReference : body.SourceObjectReference, SourceObjectReference: body.SourceObjectReference, AlarmTypeName: body.AlarmTypeName, Kind: MapKind(body.TransitionKind), @@ -112,7 +119,10 @@ public static class MxGatewayAlarmMapper /// The active alarm snapshot proto message to map. /// A with AlarmTransitionKind.Snapshot. public static NativeAlarmTransition MapSnapshot(ActiveAlarmSnapshot snapshot) => new( - SourceReference: snapshot.AlarmFullReference, + // See MapTransition: identify by the object-relative reference, not the + // full "Galaxy!.." provider reference. + SourceReference: string.IsNullOrEmpty(snapshot.SourceObjectReference) + ? snapshot.AlarmFullReference : snapshot.SourceObjectReference, SourceObjectReference: snapshot.SourceObjectReference, AlarmTypeName: snapshot.AlarmTypeName, Kind: AlarmTransitionKind.Snapshot, diff --git a/tests/ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests/MxGatewayAlarmMapperTests.cs b/tests/ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests/MxGatewayAlarmMapperTests.cs index 5f8eaf3e..438ffbdc 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests/MxGatewayAlarmMapperTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests/MxGatewayAlarmMapperTests.cs @@ -65,6 +65,54 @@ public class MxGatewayAlarmMapperTests Assert.Equal(1000, t.Condition.Severity); } + [Fact] + public void SourceReference_IsObjectRelative_NotFullProviderReference() + { + // The condition identity surfaced upward is the object-relative reference + // (e.g. "Z28061.HeartbeatTimeoutAlarm"), not the gateway's full provider + // reference ("Galaxy!.."). Area lives in Category. + var snap = new ActiveAlarmSnapshot + { + AlarmFullReference = "Galaxy!CVDAisle_1.Z28061.HeartbeatTimeoutAlarm", + SourceObjectReference = "Z28061.HeartbeatTimeoutAlarm", + AlarmTypeName = "Syst", + Category = "CVDAisle_1", + CurrentState = ProtoConditionState.Active, + Severity = 400 + }; + var ev = new OnAlarmTransitionEvent + { + AlarmFullReference = "Galaxy!CVDAisle_1.Z28061.HeartbeatTimeoutAlarm", + SourceObjectReference = "Z28061.HeartbeatTimeoutAlarm", + AlarmTypeName = "Syst", + TransitionKind = ProtoTransitionKind.Raise, + Severity = 400 + }; + + var snapT = MxGatewayAlarmMapper.MapSnapshot(snap); + var liveT = MxGatewayAlarmMapper.MapTransition(ev); + + Assert.Equal("Z28061.HeartbeatTimeoutAlarm", snapT.SourceReference); + Assert.Equal("Z28061.HeartbeatTimeoutAlarm", liveT.SourceReference); + Assert.Equal("CVDAisle_1", snapT.Category); + } + + [Fact] + public void SourceReference_FallsBackToFullReference_WhenObjectReferenceEmpty() + { + var snap = new ActiveAlarmSnapshot + { + AlarmFullReference = "Galaxy!Area.Obj.Alarm", + SourceObjectReference = "", + CurrentState = ProtoConditionState.Active, + Severity = 100 + }; + + var t = MxGatewayAlarmMapper.MapSnapshot(snap); + + Assert.Equal("Galaxy!Area.Obj.Alarm", t.SourceReference); + } + // ── CurrentValue / LimitValue (M2.13 / #27) ────────────────────────────── [Fact]