fix(adminui): preserve edited alarm fields on Galaxy address re-pick
This commit is contained in:
@@ -336,14 +336,12 @@
|
|||||||
private void OnGalaxyAddressPicked(string address)
|
private void OnGalaxyAddressPicked(string address)
|
||||||
{
|
{
|
||||||
_galaxyAddress = address;
|
_galaxyAddress = address;
|
||||||
// A fresh {FullName} blob would drop any tag-level historize the operator already set on this
|
// Re-picking a Galaxy address owns ONLY the address-derived FullName key — apply it over the EXISTING
|
||||||
// edit; re-merge it so re-picking a Galaxy address never silently clears "Historize this tag".
|
// TagConfig so every other user-edited field survives verbatim. This preserves a hand-authored `alarm`
|
||||||
var config = TagHistorizeConfig.Set(
|
// object (FB-4: a re-pick must never clobber edited alarm fields) as well as the root history/array
|
||||||
JsonSerializer.Serialize(new { FullName = address }),
|
// intent and any driver/unknown keys. TagConfigJson.SetFullName uses the same preserve-unknown idiom
|
||||||
_historizeState.IsHistorized,
|
// as the historize/array merge seams.
|
||||||
_historizeState.HistorianTagname);
|
var config = TagConfigJson.SetFullName(_form.TagConfig, address);
|
||||||
// Re-merge any array intent for the same reason — a fresh {FullName} blob would otherwise drop it.
|
|
||||||
config = TagArrayConfig.Set(config, _arrayState.IsArray, _arrayState.ArrayLength);
|
|
||||||
_form.TagConfig = _galaxyPickedIsAlarm
|
_form.TagConfig = _galaxyPickedIsAlarm
|
||||||
? NativeAlarmModel.SeedDefaultAlarm(config)
|
? NativeAlarmModel.SeedDefaultAlarm(config)
|
||||||
: config;
|
: config;
|
||||||
|
|||||||
@@ -43,4 +43,19 @@ public static class TagConfigJson
|
|||||||
if (value is null) { o.Remove(name); return; }
|
if (value is null) { o.Remove(name); return; }
|
||||||
o[name] = JsonValue.Create(value is Enum e ? e.ToString() : value);
|
o[name] = JsonValue.Create(value is Enum e ? e.ToString() : value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Merges a freshly-picked Galaxy reference (<c>tag_name.AttributeName</c>) into <paramref name="json"/>
|
||||||
|
/// by setting ONLY the canonical PascalCase <c>FullName</c> key — every other key is preserved verbatim.
|
||||||
|
/// This is the Galaxy address re-pick seam: re-picking an address must update the bound attribute WITHOUT
|
||||||
|
/// discarding fields the operator already edited independently of the address — ESPECIALLY a hand-authored
|
||||||
|
/// <c>alarm</c> object (and root history/array intent, scaling, etc.). Null/blank/malformed input starts
|
||||||
|
/// from a bare <c>{"FullName":...}</c> object.
|
||||||
|
/// </summary>
|
||||||
|
public static string SetFullName(string? json, string fullName)
|
||||||
|
{
|
||||||
|
var o = ParseOrNew(json);
|
||||||
|
o["FullName"] = JsonValue.Create(fullName);
|
||||||
|
return Serialize(o);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for <see cref="TagConfigJson.SetFullName"/> — the pure merge helper the TagModal's Galaxy
|
||||||
|
/// address-picked handler uses to apply a freshly-picked <c>tag_name.AttributeName</c> reference. Re-picking
|
||||||
|
/// a Galaxy address must update ONLY the address-derived <c>FullName</c> key and carry every other
|
||||||
|
/// user-edited field over verbatim — ESPECIALLY a manually-authored <c>alarm</c> object (FB-4 / B2). The
|
||||||
|
/// previous handler rebuilt the config from a bare <c>{"FullName":...}</c> blob, silently dropping the alarm.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GalaxyAddressRepickMergeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Repick_preserves_an_already_edited_alarm_object()
|
||||||
|
{
|
||||||
|
// The operator authored an alarm, then re-picks the bound attribute to fix a typo.
|
||||||
|
const string current =
|
||||||
|
"""{"FullName":"Pump_001.OldAttr","alarm":{"alarmType":"LimitAlarm","severity":250,"historizeToAveva":false}}""";
|
||||||
|
|
||||||
|
var merged = TagConfigJson.SetFullName(current, "Pump_001.NewAttr");
|
||||||
|
|
||||||
|
// The new address is applied...
|
||||||
|
var fullName = ReadFullName(merged);
|
||||||
|
fullName.ShouldBe("Pump_001.NewAttr");
|
||||||
|
// ...and the hand-edited alarm survives the re-pick intact.
|
||||||
|
var alarm = NativeAlarmModel.FromJson(merged);
|
||||||
|
alarm.IsAlarm.ShouldBeTrue();
|
||||||
|
alarm.AlarmType.ShouldBe("LimitAlarm");
|
||||||
|
alarm.Severity.ShouldBe(250);
|
||||||
|
alarm.HistorizeToAveva.ShouldBe(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Repick_with_no_alarm_just_sets_the_address()
|
||||||
|
{
|
||||||
|
var merged = TagConfigJson.SetFullName("""{"FullName":"Pump_001.OldAttr"}""", "Pump_001.NewAttr");
|
||||||
|
|
||||||
|
ReadFullName(merged).ShouldBe("Pump_001.NewAttr");
|
||||||
|
NativeAlarmModel.FromJson(merged).IsAlarm.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Repick_preserves_other_user_edited_non_address_fields()
|
||||||
|
{
|
||||||
|
// History intent + an array shape + an arbitrary scaling key the operator set independently of the
|
||||||
|
// address — all must survive a re-pick that only owns FullName.
|
||||||
|
const string current =
|
||||||
|
"""{"FullName":"Pump_001.OldAttr","isHistorized":true,"historianTagname":"Line3_Temp","isArray":true,"arrayLength":8,"scale":0.1}""";
|
||||||
|
|
||||||
|
var merged = TagConfigJson.SetFullName(current, "Pump_001.NewAttr");
|
||||||
|
|
||||||
|
ReadFullName(merged).ShouldBe("Pump_001.NewAttr");
|
||||||
|
|
||||||
|
var hist = TagHistorizeConfig.Read(merged);
|
||||||
|
hist.IsHistorized.ShouldBeTrue();
|
||||||
|
hist.HistorianTagname.ShouldBe("Line3_Temp");
|
||||||
|
|
||||||
|
var arr = TagArrayConfig.Read(merged);
|
||||||
|
arr.IsArray.ShouldBeTrue();
|
||||||
|
arr.ArrayLength.ShouldBe(8u);
|
||||||
|
|
||||||
|
merged.ShouldContain("\"scale\":0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Set_on_empty_or_malformed_input_starts_from_a_bare_FullName_object()
|
||||||
|
{
|
||||||
|
var merged = TagConfigJson.SetFullName("not json", "Pump_001.NewAttr");
|
||||||
|
|
||||||
|
ReadFullName(merged).ShouldBe("Pump_001.NewAttr");
|
||||||
|
merged.ShouldStartWith("{");
|
||||||
|
merged.ShouldEndWith("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirror of TagModal.ReadFullName — extracts FullName from a Galaxy TagConfig blob.
|
||||||
|
private static string ReadFullName(string json)
|
||||||
|
{
|
||||||
|
using var doc = System.Text.Json.JsonDocument.Parse(json);
|
||||||
|
return doc.RootElement.TryGetProperty("FullName", out var fn) ? fn.GetString() ?? "" : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user