feat(uns): lazy per-equipment tag + virtual-tag load

Add LoadEquipmentChildrenAsync to IUnsTreeService and UnsTreeService; returns
Tag nodes (ordered by Name) then VirtualTag nodes (ordered by Name) as leaf
nodes with ChildCount=0, HasLazyChildren=false, keys tag:{id}/vtag:{id}.
This commit is contained in:
Joseph Doherty
2026-06-08 12:29:52 -04:00
parent c264441b74
commit b33cf1c80d
3 changed files with 138 additions and 1 deletions
@@ -4,7 +4,7 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
/// Loads the structural portion of the unified-namespace (UNS) browse tree —
/// Enterprise → Cluster → Area → Line → Equipment — from the config database.
/// Equipment children (tags/virtual tags) are summarised by count only and loaded
/// lazily by the renderer; this service never returns Tag/VirtualTag leaf nodes.
/// lazily by the renderer via <see cref="LoadEquipmentChildrenAsync"/>.
/// </summary>
public interface IUnsTreeService
{
@@ -16,4 +16,14 @@ public interface IUnsTreeService
/// <param name="ct">A token to cancel the load.</param>
/// <returns>The enterprise root nodes, each populated down to equipment.</returns>
Task<IReadOnlyList<UnsNode>> LoadStructureAsync(CancellationToken ct = default);
/// <summary>
/// Lazily loads the Tag and VirtualTag leaf nodes for a single equipment node.
/// Tags are returned first (ordered by Name), followed by VirtualTags (ordered by Name).
/// Leaf nodes carry <c>ChildCount = 0</c> and <c>HasLazyChildren = false</c>.
/// </summary>
/// <param name="equipmentId">The equipment whose children to load.</param>
/// <param name="ct">A token to cancel the load.</param>
/// <returns>Tag nodes followed by VirtualTag nodes; empty if the equipment has none.</returns>
Task<IReadOnlyList<UnsNode>> LoadEquipmentChildrenAsync(string equipmentId, CancellationToken ct = default);
}
@@ -76,4 +76,49 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
return UnsTreeAssembly.Build(clusters, areas, lines, equipment);
}
/// <inheritdoc />
public async Task<IReadOnlyList<UnsNode>> LoadEquipmentChildrenAsync(
string equipmentId,
CancellationToken ct = default)
{
await using var db = await dbFactory.CreateDbContextAsync(ct);
var tagNodes = await db.Tags
.AsNoTracking()
.Where(t => t.EquipmentId == equipmentId)
.OrderBy(t => t.Name)
.Select(t => new UnsNode
{
Kind = UnsNodeKind.Tag,
Key = $"tag:{t.TagId}",
DisplayName = $"{t.Name} ({t.DataType})",
EntityId = t.TagId,
ClusterId = null,
ChildCount = 0,
HasLazyChildren = false,
})
.ToListAsync(ct);
var vtagNodes = await db.VirtualTags
.AsNoTracking()
.Where(v => v.EquipmentId == equipmentId)
.OrderBy(v => v.Name)
.Select(v => new UnsNode
{
Kind = UnsNodeKind.VirtualTag,
Key = $"vtag:{v.VirtualTagId}",
DisplayName = $"{v.Name} (VirtualTag)",
EntityId = v.VirtualTagId,
ClusterId = null,
ChildCount = 0,
HasLazyChildren = false,
})
.ToListAsync(ct);
var result = new List<UnsNode>(tagNodes.Count + vtagNodes.Count);
result.AddRange(tagNodes);
result.AddRange(vtagNodes);
return result;
}
}