diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/AlarmDispatcherTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/AlarmDispatcherTests.cs
index 7c85796..677dc37 100644
--- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/AlarmDispatcherTests.cs
+++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/AlarmDispatcherTests.cs
@@ -17,6 +17,38 @@ public sealed class AlarmDispatcherTests
{
private const string SessionId = "session-001";
+ /// Verifies an Acknowledge transition is attributed to the ACK_* operator and comment.
+ [Fact]
+ public void OnTransition_WhenAcknowledged_UsesAckOperatorAndComment()
+ {
+ FakeAlarmConsumer consumer = new FakeAlarmConsumer();
+ MxAccessEventQueue queue = new MxAccessEventQueue();
+ MxAccessAlarmEventSink sink = new MxAccessAlarmEventSink(queue, new MxAccessEventMapper());
+ using AlarmDispatcher dispatcher = new AlarmDispatcher(consumer, sink, SessionId);
+
+ consumer.RaiseTransition(new MxAlarmTransitionEvent
+ {
+ PreviousState = MxAlarmStateKind.UnackAlm,
+ Record = new MxAlarmSnapshotRecord
+ {
+ AlarmGuid = Guid.NewGuid(),
+ ProviderName = "Galaxy",
+ Group = "TestArea",
+ TagName = "TestMachine_001.TestAlarm001",
+ State = MxAlarmStateKind.AckAlm,
+ AlarmComment = "Test alarm #1",
+ AckOperatorName = "alice",
+ AckComment = "checked",
+ },
+ });
+
+ Assert.True(queue.TryDequeue(out WorkerEvent? evt));
+ OnAlarmTransitionEvent body = evt!.Event.OnAlarmTransition;
+ Assert.Equal(AlarmTransitionKind.Acknowledge, body.TransitionKind);
+ Assert.Equal("alice", body.OperatorUser);
+ Assert.Equal("checked", body.OperatorComment);
+ }
+
/// Verifies that alarm transitions land in the queue with correctly mapped fields.
[Fact]
public void OnTransition_WhenAlarmTransitionRaised_LandsInQueueWithMappedFields()
diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs
index afef255..3941a21 100644
--- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs
+++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs
@@ -801,4 +801,27 @@ public sealed class WnWrapAlarmConsumerXmlTests
ProviderName = "Galaxy",
};
}
+
+ [Fact]
+ public void ParseSnapshotXml_WithAckElements_PopulatesAckFields()
+ {
+ const string xml =
+ "2D8A9BE88FE04B0F8D996BB4D56620C7" +
+ "2026/10/192400" +
+ "DESKTOP-6JL3KKOGalaxyTestArea" +
+ "TestMachine_001.TestAlarm001DSCtruetrue" +
+ "500ACK_ALM" +
+ "ArchestrAmxa" +
+ "AdministratorTest alarm #1" +
+ "mgack-authwrite 180714";
+
+ Dictionary records = WnWrapAlarmConsumer.ParseSnapshotXml(xml);
+
+ MxAlarmSnapshotRecord record = Assert.Single(records.Values);
+ Assert.Equal("Administrator", record.AckOperatorName);
+ Assert.Equal("ArchestrA", record.AckOperatorDomain);
+ Assert.Equal("mxa", record.AckOperatorNode);
+ Assert.Equal("mgack-authwrite 180714", record.AckComment);
+ Assert.Equal("Test alarm #1", record.AlarmComment);
+ }
}
diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/AlarmDispatcher.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/AlarmDispatcher.cs
index 388266e..722db2e 100644
--- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/AlarmDispatcher.cs
+++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/AlarmDispatcher.cs
@@ -244,6 +244,7 @@ public sealed class AlarmDispatcher : IDisposable
string fullReference = AlarmRecordTransitionMapper.ComposeFullReference(
record.ProviderName, record.Group, record.TagName);
+ bool useAck = kind == AlarmTransitionKind.Acknowledge && HasAckAttribution(record);
sink.EnqueueTransition(
alarmFullReference: fullReference,
@@ -253,15 +254,22 @@ public sealed class AlarmDispatcher : IDisposable
severity: record.Priority,
originalRaiseTimestampUtc: null,
transitionTimestampUtc: record.TransitionTimestampUtc,
- operatorUser: record.OperatorName,
- operatorComment: record.AlarmComment,
+ operatorUser: useAck ? record.AckOperatorName : record.OperatorName,
+ operatorComment: useAck ? record.AckComment : record.AlarmComment,
category: record.Group,
description: string.Empty,
degraded: record.Degraded);
}
+ private static bool HasAckAttribution(MxAlarmSnapshotRecord record) =>
+ !string.IsNullOrEmpty(record.AckOperatorName) || !string.IsNullOrEmpty(record.AckComment);
+
+ private static bool IsAcked(MxAlarmStateKind state) =>
+ state == MxAlarmStateKind.AckAlm || state == MxAlarmStateKind.AckRtn;
+
private static ActiveAlarmSnapshot MapToSnapshot(MxAlarmSnapshotRecord record, bool truncated)
{
+ bool useAck = IsAcked(record.State) && HasAckAttribution(record);
ActiveAlarmSnapshot snapshot = new ActiveAlarmSnapshot
{
AlarmFullReference = AlarmRecordTransitionMapper.ComposeFullReference(
@@ -270,8 +278,8 @@ public sealed class AlarmDispatcher : IDisposable
AlarmTypeName = record.Type,
CurrentState = MapConditionState(record.State),
Severity = record.Priority,
- OperatorUser = record.OperatorName,
- OperatorComment = record.AlarmComment,
+ OperatorUser = useAck ? record.AckOperatorName : record.OperatorName,
+ OperatorComment = useAck ? record.AckComment : record.AlarmComment,
Category = record.Group,
Description = string.Empty,
Degraded = record.Degraded,
diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAlarmSnapshot.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAlarmSnapshot.cs
index b8556d2..c431728 100644
--- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAlarmSnapshot.cs
+++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAlarmSnapshot.cs
@@ -39,6 +39,18 @@ public sealed class MxAlarmSnapshotRecord
public string OperatorName { get; set; } = string.Empty;
/// Gets or sets the alarm comment.
public string AlarmComment { get; set; } = string.Empty;
+
+ /// Gets or sets the acknowledgement comment (wnwrap ACK_COMMENT).
+ public string AckComment { get; set; } = string.Empty;
+
+ /// Gets or sets the acknowledging operator's name (wnwrap ACK_OPERATOR_NAME).
+ public string AckOperatorName { get; set; } = string.Empty;
+
+ /// Gets or sets the acknowledging operator's domain (wnwrap ACK_DOMAIN_NAME).
+ public string AckOperatorDomain { get; set; } = string.Empty;
+
+ /// Gets or sets the acknowledging node, i.e. the MXAccess client name (wnwrap ACK_OPERATOR_NODE).
+ public string AckOperatorNode { get; set; } = string.Empty;
///
/// Gets or sets a value indicating whether this record was synthesized
/// by the subtag-provider fallback rather than emitted by the native
diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs
index 5f3a7f4..f109dc3 100644
--- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs
+++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs
@@ -792,6 +792,10 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer
string? operatorNode = null;
string? operatorName = null;
string? alarmComment = null;
+ string? ackComment = null;
+ string? ackOperatorName = null;
+ string? ackOperatorDomain = null;
+ string? ackOperatorNode = null;
foreach (XmlNode child in alarmNode.ChildNodes)
{
@@ -816,6 +820,10 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer
case "OPERATOR_NODE": operatorNode ??= child.InnerText; break;
case "OPERATOR_NAME": operatorName ??= child.InnerText; break;
case "ALARM_COMMENT": alarmComment ??= child.InnerText; break;
+ case "ACK_COMMENT": ackComment ??= child.InnerText; break;
+ case "ACK_OPERATOR_NAME": ackOperatorName ??= child.InnerText; break;
+ case "ACK_DOMAIN_NAME": ackOperatorDomain ??= child.InnerText; break;
+ case "ACK_OPERATOR_NODE": ackOperatorNode ??= child.InnerText; break;
}
}
@@ -843,6 +851,10 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer
OperatorNode = operatorNode ?? string.Empty,
OperatorName = operatorName ?? string.Empty,
AlarmComment = alarmComment ?? string.Empty,
+ AckComment = ackComment ?? string.Empty,
+ AckOperatorName = ackOperatorName ?? string.Empty,
+ AckOperatorDomain = ackOperatorDomain ?? string.Empty,
+ AckOperatorNode = ackOperatorNode ?? string.Empty,
};
}
return records;