fix(adminui): say why a Sparkplug array/unsupported metric cannot be committed (G2-F2)
Since R06:M-5 the Sparkplug browser states no address for a metric the ingestor will not read and labels its descriptor 'not bindable', but the commit refusal still said the attribute lookup returned nothing. RawBrowseModal keeps the attribute's descriptor on the selection, and DescribeUncommittableLeaf reports a 'cannot be bound in this version' message echoing it. A lookup that really returned nothing keeps the old text. A test pins the marker to the browser's own string. Refs #745 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -503,6 +503,7 @@
|
||||
string name = sel.Leaf.DisplayName;
|
||||
string? driverDataType = null;
|
||||
IReadOnlyDictionary<string, string>? addressFields = null;
|
||||
string? descriptor = null;
|
||||
try
|
||||
{
|
||||
var attrs = await BrowserService.AttributesAsync(_token, nodeId, default);
|
||||
@@ -514,6 +515,8 @@
|
||||
// reconstructed from the node id: a metric name may contain '/', so the id cannot be
|
||||
// split back into (group, edgeNode, device?, metric) — see RawBrowseCommitMapper.
|
||||
addressFields = attrs[0].AddressFields;
|
||||
// The descriptor line, so a commit refusal can say WHY a leaf is unbindable (G2-F2).
|
||||
descriptor = attrs[0].SecurityClass;
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -523,7 +526,7 @@
|
||||
}
|
||||
|
||||
_selectedIds.Add(nodeId);
|
||||
_selected[nodeId] = new SelectedLeaf(nodeId, name, driverDataType, sel.FolderPath, addressFields);
|
||||
_selected[nodeId] = new SelectedLeaf(nodeId, name, driverDataType, sel.FolderPath, addressFields, descriptor);
|
||||
|
||||
// A metric is also a legal rebirth scope (it resolves up to its owning edge node), so a toggle
|
||||
// moves the scope the same way a folder click does.
|
||||
@@ -768,7 +771,7 @@
|
||||
// committing it would produce a tag that deploys clean and then reports BadNodeIdUnknown
|
||||
// forever with nothing to point at.
|
||||
_commitErrors = _selected.Values
|
||||
.Select(s => RawBrowseCommitMapper.DescribeUncommittableLeaf(_driverType, s.Name, s.AddressFields))
|
||||
.Select(s => RawBrowseCommitMapper.DescribeUncommittableLeaf(_driverType, s.Name, s.AddressFields, s.Descriptor))
|
||||
.Where(e => e is not null)
|
||||
.Select(e => e!)
|
||||
.ToList();
|
||||
@@ -843,7 +846,8 @@
|
||||
string Name,
|
||||
string? DriverDataType,
|
||||
IReadOnlyList<string> FolderPath,
|
||||
IReadOnlyDictionary<string, string>? AddressFields = null);
|
||||
IReadOnlyDictionary<string, string>? AddressFields = null,
|
||||
string? Descriptor = null);
|
||||
|
||||
/// <summary>A described Request-rebirth scope: what gets published, and to what.</summary>
|
||||
/// <param name="Scope">The browse node id handed to the session verbatim.</param>
|
||||
|
||||
@@ -361,6 +361,10 @@ public static class RawBrowseCommitMapper
|
||||
TryGetField(addressFields, MqttTagConfigKeys.Topic) ?? fullName);
|
||||
}
|
||||
|
||||
/// <summary>The phrase the MQTT/Sparkplug browser puts on the descriptor of a metric it refuses to
|
||||
/// bind (<c>MqttBrowseSession.DescribeMetric</c>); matched case-insensitively.</summary>
|
||||
public const string NotBindableMarker = "not bindable";
|
||||
|
||||
/// <summary>
|
||||
/// Describes why a selected browse leaf cannot be committed as a working tag, or <c>null</c> when it
|
||||
/// can. The AdminUI calls this before mapping so an unbindable selection fails <b>at commit, in
|
||||
@@ -369,6 +373,9 @@ public static class RawBrowseCommitMapper
|
||||
/// <param name="driverType">The owning device's driver type.</param>
|
||||
/// <param name="browseName">The leaf's browse name, for the message.</param>
|
||||
/// <param name="addressFields">The structured address the browse session stated, if any.</param>
|
||||
/// <param name="descriptor">The leaf attribute's descriptor line (<c>AttributeInfo.SecurityClass</c>),
|
||||
/// when the lookup returned one. MQTT uses it to tell a deliberately unbindable Sparkplug metric
|
||||
/// (<see cref="NotBindableMarker"/>) from a lookup that returned nothing.</param>
|
||||
/// <returns>The operator-facing reason, or <c>null</c> when the leaf is committable.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -394,9 +401,10 @@ public static class RawBrowseCommitMapper
|
||||
public static string? DescribeUncommittableLeaf(
|
||||
string driverType,
|
||||
string browseName,
|
||||
IReadOnlyDictionary<string, string>? addressFields)
|
||||
IReadOnlyDictionary<string, string>? addressFields,
|
||||
string? descriptor = null)
|
||||
{
|
||||
if (Is(driverType, DriverTypeNames.Mqtt)) return DescribeUncommittableMqttLeaf(browseName, addressFields);
|
||||
if (Is(driverType, DriverTypeNames.Mqtt)) return DescribeUncommittableMqttLeaf(browseName, addressFields, descriptor);
|
||||
if (Is(driverType, DriverTypeNames.Sql)) return DescribeUncommittableSqlLeaf(browseName, addressFields);
|
||||
return null;
|
||||
}
|
||||
@@ -404,13 +412,25 @@ public static class RawBrowseCommitMapper
|
||||
/// <summary>Describes an MQTT leaf that states no address at all, or <c>null</c> when it states one.</summary>
|
||||
/// <param name="browseName">The leaf's browse name, for the message.</param>
|
||||
/// <param name="addressFields">The structured address the browse session stated, if any.</param>
|
||||
/// <param name="descriptor">The attribute's descriptor line (<c>AttributeInfo.SecurityClass</c>), if the
|
||||
/// lookup returned one.</param>
|
||||
/// <returns>The operator-facing reason, or <c>null</c>.</returns>
|
||||
private static string? DescribeUncommittableMqttLeaf(
|
||||
string browseName, IReadOnlyDictionary<string, string>? addressFields)
|
||||
string browseName, IReadOnlyDictionary<string, string>? addressFields, string? descriptor)
|
||||
{
|
||||
if (TryGetField(addressFields, MqttTagConfigKeys.MetricName) is not null) return null;
|
||||
if (TryGetField(addressFields, MqttTagConfigKeys.Topic) is not null) return null;
|
||||
|
||||
// G2-F2: the Sparkplug browser deliberately states no address for a metric the ingestor will not
|
||||
// read (an *Array type, or a type with no driver mapping — R06:M-5), and says so on its descriptor
|
||||
// line. That is a "not bindable in this version" refusal, not a failed lookup, so say which.
|
||||
if (descriptor is not null && descriptor.Contains(NotBindableMarker, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return $"'{browseName}' cannot be bound in this version: the Sparkplug browser reports it as "
|
||||
+ $"\"{descriptor}\". Array metrics and metrics of an unsupported type are not read yet; "
|
||||
+ "remove it from the selection.";
|
||||
}
|
||||
|
||||
return $"'{browseName}' could not be committed: the browse session reported no MQTT address for it "
|
||||
+ "(its attribute lookup returned nothing). Re-open the browser and re-select it, or author "
|
||||
+ "the tag manually.";
|
||||
|
||||
Reference in New Issue
Block a user