using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy; namespace ZB.MOM.WW.MxGateway.Server.Dashboard; /// /// One node in the dashboard Browse hierarchy tree. Wraps a Galaxy object /// and its child objects; the object's attributes are the leaf "tags" the /// operator can right-click and add to the subscription panel. /// public sealed class DashboardBrowseNode { /// The underlying Galaxy object for this node. public required GalaxyObject Object { get; init; } /// Child objects contained by this object, sorted areas-first then by name. public List Children { get; } = []; /// The label shown for this node in the tree. public string DisplayName => !string.IsNullOrWhiteSpace(Object.BrowseName) ? Object.BrowseName : !string.IsNullOrWhiteSpace(Object.ContainedName) ? Object.ContainedName : Object.TagName; /// True when this node is a Galaxy area rather than an instance object. public bool IsArea => Object.IsArea; /// The object's attributes — the browsable tags. public IReadOnlyList Attributes => Object.Attributes; /// True when the node has child objects or attributes to expand. public bool HasChildren => Children.Count > 0 || Object.Attributes.Count > 0; } /// /// Builds the dashboard Browse tree from the flat Galaxy object list held /// by IGalaxyHierarchyCache. Pure and side-effect free so the /// parent/child linkage and ordering rules are unit-testable. /// public static class DashboardBrowseTreeBuilder { /// Builds the root nodes of the Browse tree. /// The flat Galaxy object list. /// The root nodes, sorted areas-first then alphabetically. public static IReadOnlyList Build(IReadOnlyList objects) { ArgumentNullException.ThrowIfNull(objects); Dictionary nodes = new(objects.Count); foreach (GalaxyObject galaxyObject in objects) { // Last write wins on a duplicate gobject id — Galaxy ids are unique // in practice, but guard so the dictionary build never throws. nodes[galaxyObject.GobjectId] = new DashboardBrowseNode { Object = galaxyObject }; } List roots = []; foreach (DashboardBrowseNode node in nodes.Values) { int parentId = node.Object.ParentGobjectId; if (parentId != 0 && parentId != node.Object.GobjectId && nodes.TryGetValue(parentId, out DashboardBrowseNode? parent)) { parent.Children.Add(node); } else { roots.Add(node); } } SortRecursive(roots); return roots; } private static void SortRecursive(List nodes) { nodes.Sort(CompareNodes); foreach (DashboardBrowseNode node in nodes) { SortRecursive(node.Children); } } // Areas sort before instance objects; within a group, by display name. private static int CompareNodes(DashboardBrowseNode left, DashboardBrowseNode right) { if (left.IsArea != right.IsArea) { return left.IsArea ? -1 : 1; } return string.Compare(left.DisplayName, right.DisplayName, StringComparison.OrdinalIgnoreCase); } }