From fbf3c26c2ae15cca70c1765715871ce62f118488 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 16 Jul 2026 02:20:05 -0400 Subject: [PATCH] contracts(b2-waveA): RawNode view-model + IRawTreeService read surface + RawRenameResult MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wave A contract-first fan-out types for the /raw project tree: - RawNode/RawNodeKind: shared tree view-model (Enterprise→Cluster→Folder→Driver→Device→TagGroup→Tag), lazy-load metadata, per-kind ids + DriverType propagation for menu gating. - IRawTreeService: committed READ surface (LoadRootsAsync + LoadChildrenAsync); WP1 extends with the mutation surface, WP2 consumes read-only. - RawRenameResult: rename outcome carrying non-blocking warnings (historized/UNS-referenced now; script-scan in Batch 3). Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox --- .../Uns/IRawTreeService.cs | 44 ++++++++ .../ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawNode.cs | 101 ++++++++++++++++++ .../Uns/RawRenameResult.cs | 24 +++++ 3 files changed, 169 insertions(+) create mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IRawTreeService.cs create mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawNode.cs create mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawRenameResult.cs diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IRawTreeService.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IRawTreeService.cs new file mode 100644 index 00000000..bc0050c8 --- /dev/null +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IRawTreeService.cs @@ -0,0 +1,44 @@ +namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns; + +/// +/// Loads and mutates the v3 Raw project tree (/raw) — the cluster-rooted +/// Enterprise → Cluster → Folder → Driver → Device → TagGroup → Tag hierarchy — from the config +/// database. The peer of for the Raw subtree. +/// +/// Contract split (Batch 2 Wave A): this file's committed surface is the read contract +/// the RawTree component consumes — (eager Enterprise→Cluster roots) +/// and (one lazy level per expand). B2-WP1 (RawTreeService) +/// extends this interface with the mutation surface — create/rename/delete per node type, +/// move-into-folder, and the per-node edit projections — enforcing the integrity rules with +/// user-readable errors (a delete blocked by a UnsTagReference names the referencing equipment). +/// Rename methods return so the UI can show non-blocking warnings. +/// Mutations reuse . WP2 must not add to this interface. +/// +/// +public interface IRawTreeService +{ + /// + /// Loads the eager top of the Raw tree: Enterprise → Cluster roots, each cluster marked + /// so its folders + drivers load on first expand. Empty + /// clusters are retained so they stay visible and authorable. The returned nodes are detached + /// view-models, safe to hold and mutate UI state on after the underlying context is disposed. + /// + /// A token to cancel the load. + /// The enterprise roots, populated down to (but not through) their clusters. + Task> LoadRootsAsync(CancellationToken ct = default); + + /// + /// Loads the direct children of one container node — the next level down, keyed off + /// 's and : + /// a Cluster yields its root Folders + Drivers; a Folder yields sub-Folders + Drivers; a Driver + /// yields its Devices; a Device yields its root TagGroups + Tags; a TagGroup yields sub-TagGroups + /// + Tags. Tags are leaves and yield nothing. Ordering is deterministic and ordinal. Reads + /// untracked; returns detached nodes. Per-device tag counts can be large — the implementation may + /// page, but the committed shape returns the level's nodes (paging knobs, if any, are WP1's to add + /// on the concrete type without changing this signature's meaning for the tree). + /// + /// The container node whose direct children to load. + /// A token to cancel the load. + /// The parent's direct child nodes; empty for a leaf or an empty container. + Task> LoadChildrenAsync(RawNode parent, CancellationToken ct = default); +} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawNode.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawNode.cs new file mode 100644 index 00000000..973a989c --- /dev/null +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawNode.cs @@ -0,0 +1,101 @@ +namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns; + +/// +/// The kind of node within the v3 Raw project tree (/raw). Determines how the +/// renderer styles the row, which context-menu it shows, and which entity (if any) it +/// links to. The Raw tree is cluster-rooted and mirrors the RawPath hierarchy: +/// Enterprise → Cluster → Folder(s) → Driver → Device → TagGroup(s) → Tag. +/// +public enum RawNodeKind +{ + /// Top-level grouping label (a string on cluster rows, not a DB entity). + Enterprise, + + /// A server cluster — the root of a Raw project tree. + Cluster, + + /// A driver-organizing RawFolder (nestable under a cluster or another folder). + Folder, + + /// A DriverInstance. + Driver, + + /// A Device under a driver (universal in v3 — every driver has ≥1). + Device, + + /// A tag-organizing TagGroup (nestable under a device or another group). + TagGroup, + + /// A raw Tag (signal) — a leaf. + Tag, +} + +/// +/// View-model for a single node in the Raw project tree. Carries the stable identity, +/// display text, per-kind ids, and lazy-load metadata the renderer needs, plus transient UI +/// state (expansion/loading) that is never persisted. Detached from EF — safe to hold and +/// mutate UI state on after the loading context is disposed. This is the shared contract +/// between (which produces nodes) and the RawTree +/// component (which renders + lazily expands them). +/// +public sealed class RawNode +{ + /// The kind of node — drives styling, context-menu, and entity linking. + public required RawNodeKind Kind { get; init; } + + /// Stable per-node key, unique within the tree (e.g. drv:{driverInstanceId}). + public required string Key { get; init; } + + /// Human-readable label shown in the tree (the node's Name, or Enterprise/Cluster label). + public required string DisplayName { get; init; } + + /// Owning cluster id for every node under a cluster (and the cluster itself); null for Enterprise. + public string? ClusterId { get; init; } + + /// + /// This node's own logical entity id (RawFolderId/DriverInstanceId/DeviceId/ + /// TagGroupId/TagId, or the ClusterId for a cluster); null for Enterprise. The + /// child loader keys off + this id to fetch the next level. + /// + public string? EntityId { get; init; } + + /// + /// The bound DriverType for (and propagated to Device/TagGroup/Tag + /// nodes beneath it, so tag editors and the browse-enable gate can dispatch without another query); + /// null for folders/clusters/enterprise. Use the DriverTypeNames constants when matching. + /// + public string? DriverType { get; init; } + + /// Whether a Driver/Device node is enabled (drives the enable/disable menu label + styling); true otherwise. + public bool Enabled { get; init; } = true; + + /// Direct child count (for the badge). For a device this is its tag-group + tag count. + public int ChildCount { get; set; } + + /// + /// Whether this node's children load lazily on expand. True for every container kind in the Raw tree + /// (Cluster/Folder/Driver/Device/TagGroup) — unlike the UNS tree, /raw actually exercises the + /// lazy plumbing (per-device tag counts can be large). Tags are leaves (false). + /// + public bool HasLazyChildren { get; init; } + + /// Lazily-materialised children — empty until flips on expand. + public List Children { get; } = new(); + + /// Optimistic-concurrency token for this node's backing row (empty for Enterprise); echoed on mutations. + public byte[] RowVersion { get; init; } = Array.Empty(); + + // --- Runtime UI state (not persisted) --- + + /// Whether the node is currently expanded in the UI. + public bool Expanded { get; set; } + + /// Whether the node's lazy children have been loaded. + public bool Loaded { get; set; } + + /// Whether a lazy-load is currently in flight for this node. + public bool Loading { get; set; } + + /// Last load error message, if any. + public string? Error { get; set; } +} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawRenameResult.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawRenameResult.cs new file mode 100644 index 00000000..6db0eea3 --- /dev/null +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawRenameResult.cs @@ -0,0 +1,24 @@ +namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns; + +/// +/// Outcome of a Raw-tree rename. Like it reports success/failure, +/// but a rename additionally surfaces : non-blocking advisories the UI shows +/// after the rename succeeds (a RawPath segment changed, so downstream references may need attention). +/// In this batch the warnings answerable from the schema are wired — a renamed node with historized +/// tags beneath it, or raw tags referenced by an equipment (UnsTagReference). Batch 3 adds the +/// script-substring scan to the same list. On failure is false, +/// carries the operator-facing message, and is empty. +/// +/// Whether the rename was applied. +/// The operator-facing failure message, or null on success. +/// Non-blocking advisories to show after a successful rename; empty when there are none. +public readonly record struct RawRenameResult(bool Ok, string? Error, IReadOnlyList Warnings) +{ + /// A successful rename carrying zero or more advisory warnings. + public static RawRenameResult Success(IReadOnlyList? warnings = null) => + new(true, null, warnings ?? Array.Empty()); + + /// A failed rename carrying the operator-facing message. + public static RawRenameResult Failure(string error) => + new(false, error, Array.Empty()); +}