feat(mtconnect): typed AdminUI tag-config editor + browse round-trip (Task 18 Part A)
Registers the MTConnect typed tag editor in TagConfigEditorMap /
TagConfigValidator (keyed off DriverTypeNames.MTConnect, never a literal) and
adds the razor shell over Task 17's MTConnectTagConfigModel.
The shell stays trivial: the /probe-sourced mtCategory/mtType/mtSubType/units
row is rendered disabled + readonly with NO binding of any kind, and no
inferred-type hint is shown — MTConnectDataTypeInference.Infer() also keys on
the DataItem `representation`, which the tag-config model does not carry, so
calling it here would agree with the browse picker for ordinary items and
silently disagree for exactly the DATA_SET/TABLE/TIME_SERIES ones.
Also fixes the picker -> editor round trip, which was broken in both
directions:
- RawBrowseCommitMapper.BuildTagConfig had no MTConnect branch, so a committed
tag landed as the generic {"address": "<dataItemId>"} while the model read
only `fullName`. The data plane kept working (the driver accepts all three id
spellings), so the only symptom was an empty DataItem-id field in the editor
— and saving from that state stamped fullName:"" over the tag.
- MTConnectTagConfigModel.FromJson now accepts dataItemId/address as fallbacks
(fixing tags committed before this change) and ToJson normalises onto
`fullName`, dropping the aliases so the two spellings cannot drift.
This commit is contained in:
@@ -132,6 +132,11 @@ public static class RawBrowseCommitMapper
|
||||
return new FocasTagConfigModel { Address = address }.ToJson();
|
||||
if (Is(driverType, DriverTypeNames.S7))
|
||||
return new S7TagConfigModel { Address = address }.ToJson();
|
||||
// MTConnect: the DataItem id. The driver also accepts "address"/"dataItemId", but the typed
|
||||
// editor's canonical spelling is fullName — committing under the generic key below would open
|
||||
// in that editor with an EMPTY id field and blank it on save.
|
||||
if (Is(driverType, DriverTypeNames.MTConnect))
|
||||
return new MTConnectTagConfigModel { FullName = address }.ToJson();
|
||||
if (Is(driverType, DriverTypeNames.Galaxy))
|
||||
return WriteSingleKey("attributeRef", address);
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
|
||||
/// </remarks>
|
||||
public sealed class MTConnectTagConfigModel
|
||||
{
|
||||
/// <summary>The MTConnect DataItem's <c>id</c> attribute — the driver's read/subscribe key. Required.</summary>
|
||||
/// <summary>The MTConnect DataItem's <c>id</c> attribute — the driver's read/subscribe key. Required.
|
||||
/// Loaded from <c>fullName</c>, or from the driver's alternate spellings <c>dataItemId</c> /
|
||||
/// <c>address</c> (see <see cref="IdKeys"/>); always saved as <c>fullName</c>.</summary>
|
||||
public string FullName { get; set; } = "";
|
||||
|
||||
/// <summary>Logical data type override for the DataItem's value. Defaults to <see cref="DriverDataType.String"/>
|
||||
@@ -65,6 +67,18 @@ public sealed class MTConnectTagConfigModel
|
||||
|
||||
private JsonObject _bag = new();
|
||||
|
||||
/// <summary>
|
||||
/// The accepted spellings of the DataItem id, in precedence order, mirroring the driver's own
|
||||
/// <c>MTConnectRawTagConfigDto</c>: <c>fullName</c> (this model's canonical key),
|
||||
/// <c>dataItemId</c> (what an operator hand-authoring the blob reaches for), and <c>address</c>
|
||||
/// (what <c>RawBrowseCommitMapper</c> wrote for MTConnect before it grew a typed-editor branch).
|
||||
/// Reading only <c>fullName</c> made every already-committed tag open with an EMPTY id field and
|
||||
/// get blanked on save, while the data plane kept working — the symptom looked cosmetic.
|
||||
/// <see cref="ToJson"/> normalises onto <c>fullName</c> and drops the aliases, so the two
|
||||
/// spellings can never drift apart on a later edit.
|
||||
/// </summary>
|
||||
private static readonly string[] IdKeys = ["fullName", "dataItemId", "address"];
|
||||
|
||||
/// <summary>Loads a model from a TagConfig JSON string, defaulting any absent field and retaining
|
||||
/// every original key (so fields this editor doesn't expose survive a load→save).</summary>
|
||||
/// <param name="json">The raw TagConfig JSON string, or <c>null</c> for a new/empty config.</param>
|
||||
@@ -74,7 +88,7 @@ public sealed class MTConnectTagConfigModel
|
||||
var o = TagConfigJson.ParseOrNew(json);
|
||||
return new MTConnectTagConfigModel
|
||||
{
|
||||
FullName = TagConfigJson.GetString(o, "fullName") ?? "",
|
||||
FullName = ReadId(o),
|
||||
DataType = TagConfigJson.GetEnum(o, "dataType", DriverDataType.String),
|
||||
MtCategory = TagConfigJson.GetString(o, "mtCategory"),
|
||||
MtType = TagConfigJson.GetString(o, "mtType"),
|
||||
@@ -94,6 +108,10 @@ public sealed class MTConnectTagConfigModel
|
||||
/// <returns>The serialised TagConfig JSON string.</returns>
|
||||
public string ToJson()
|
||||
{
|
||||
// Normalise onto the canonical key: a blob loaded via an alias must not keep the alias, or the
|
||||
// two spellings drift on the next edit and the driver silently reads the stale one.
|
||||
TagConfigJson.Set(_bag, "dataItemId", null);
|
||||
TagConfigJson.Set(_bag, "address", null);
|
||||
TagConfigJson.Set(_bag, "fullName", FullName.Trim());
|
||||
TagConfigJson.Set(_bag, "dataType", DataType);
|
||||
TagConfigJson.Set(_bag, "mtCategory", Blank(MtCategory));
|
||||
@@ -127,4 +145,19 @@ public sealed class MTConnectTagConfigModel
|
||||
// Normalises a blank/whitespace-only string to null so TagConfigJson.Set omits the key rather than
|
||||
// persisting an empty string.
|
||||
private static string? Blank(string? value) => string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
|
||||
// First non-blank id spelling wins (see IdKeys). A present-but-blank "fullName" must not shadow a
|
||||
// populated alias — that is exactly the state a save from the broken editor left behind.
|
||||
private static string ReadId(JsonObject o)
|
||||
{
|
||||
foreach (var key in IdKeys)
|
||||
{
|
||||
if (Blank(TagConfigJson.GetString(o, key)) is { } id)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public static class TagConfigEditorMap
|
||||
// asserts bidirectional parity). Task 11 repoints all Sql keys onto DriverTypeNames.Sql.
|
||||
[SqlDriver.DriverTypeName] = typeof(Components.Shared.Uns.TagEditors.SqlTagConfigEditor),
|
||||
[DriverTypeNames.Mqtt] = typeof(Components.Shared.Uns.TagEditors.MqttTagConfigEditor),
|
||||
[DriverTypeNames.MTConnect] = typeof(Components.Shared.Uns.TagEditors.MTConnectTagConfigEditor),
|
||||
};
|
||||
|
||||
/// <summary>Returns the editor component type for a driver type, or null if none is registered.</summary>
|
||||
|
||||
@@ -27,6 +27,7 @@ public static class TagConfigValidator
|
||||
// Keyed off SqlDriver.DriverTypeName (= "Sql") — see the note in TagConfigEditorMap.
|
||||
[SqlDriver.DriverTypeName] = j => SqlTagConfigModel.FromJson(j).Validate(),
|
||||
[DriverTypeNames.Mqtt] = j => MqttTagConfigModel.FromJson(j).Validate(),
|
||||
[DriverTypeNames.MTConnect] = j => MTConnectTagConfigModel.FromJson(j).Validate(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user