From 43811ebc1f6a905054c60318c89aee3e0df0233f Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 09:52:47 -0400 Subject: [PATCH 01/19] minimally working tree layout --- .../src/components/CorePanel.tsx | 3 + .../src/components/TreeDisplay.tsx | 132 ++++++++++++++++++ packages/ott-vis-panel/src/module.ts | 4 + 3 files changed, 139 insertions(+) create mode 100644 packages/ott-vis-panel/src/components/TreeDisplay.tsx diff --git a/packages/ott-vis-panel/src/components/CorePanel.tsx b/packages/ott-vis-panel/src/components/CorePanel.tsx index 15ff53e88..ce9613ef3 100644 --- a/packages/ott-vis-panel/src/components/CorePanel.tsx +++ b/packages/ott-vis-panel/src/components/CorePanel.tsx @@ -8,6 +8,7 @@ import GlobalView from "./views/GlobalView"; import RegionView from "./views/RegionView"; import { LoadingState } from "@grafana/schema"; import { useEventBus, type BusEvent } from "eventbus"; +import TreeDisplay from "./TreeDisplay"; interface Props extends PanelProps {} @@ -49,6 +50,8 @@ export const CorePanel: React.FC = ({ options, data, width, height }) => return ; } else if (options.view === "region") { return ; + } else if (options.view === "tree") { + return ; } else { return
Invalid view
; } diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx new file mode 100644 index 000000000..42af341e8 --- /dev/null +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -0,0 +1,132 @@ +import React, { useEffect, useMemo, useRef } from 'react'; +import * as d3 from 'd3'; +import type { SystemState } from 'ott-vis/types'; + +interface TreeDisplayProps { + systemState: SystemState; + width: number; + height: number; +} + +const color = d3.scaleOrdinal(d3.schemeCategory10); + +interface TreeNode { + id: string; + region: string; + group: string; + children: TreeNode[]; +} + +function buildTree(systemState: SystemState): TreeNode { + const tree: TreeNode = { + id: "root", + region: "global", + group: "root", + children: [], + }; + for (const balancer of systemState) { + const balancerNode: TreeNode = { + id: balancer.id, + region: balancer.region, + group: "balancer", + children: [], + }; + tree.children.push(balancerNode); + for (const monolith of balancer.monoliths) { + const monolithNode: TreeNode = { + id: monolith.id, + region: monolith.region, + group: "monolith", + children: [], + }; + balancerNode.children.push(monolithNode); + for (const room of monolith.rooms) { + const roomNode: TreeNode = { + id: room.name, + region: monolith.region, + group: "room", + children: Array.from({ length: room.clients }, (_, index) => { + return { + id: `${room.name}-${index}`, + region: monolith.region, + group: "client", + children: [], + }; + }), + }; + monolithNode.children.push(roomNode); + } + } + } + return tree; +} + +const TreeDisplay: React.FC = ({ systemState, width, height }) => { + const svgRef = useRef(null); + const systemTree = useMemo(() => buildTree(systemState), [systemState]); + + useEffect(() => { + if (systemTree && svgRef.current) { + const treeLayout = d3.tree().size([width, height]).nodeSize([60, 120]); + + const root = d3.hierarchy(systemTree); + + // @ts-expect-error asdf + treeLayout(root); + + // Create a new D3 diagonal generator + const diagonal = d3 + .linkHorizontal() + .x((d: any) => d.y) + .y((d: any) => d.x); + + // Select the SVG element and bind the hierarchy data to it + const svg = d3.select(svgRef.current); + const g = svg.select('g.chart'); + const nodes = g.selectAll('.node').data(root.descendants()); + const links = g.selectAll('.link').data(root.links()); + + links + .enter() + .append('path') + .attr('class', 'link') + .attr('d', diagonal) + .attr('fill', 'none') + .attr("stroke", "white") + .attr('stroke-width', 1.5); + + nodes + .enter() + .append('circle') + .attr('class', 'node') + .attr('cy', (d: any) => d.x) + .attr('cx', (d: any) => d.y) + .attr('r', 20) + .attr("stroke", "white") + .attr('stroke-width', 2) + .attr('fill', d => color(d.data.group)) + .attr("data-nodeid", d => d.data.id); + + // Update existing nodes and links + nodes.attr('cx', (d: any) => d.y).attr('cy', (d: any) => d.x); + links.attr('d', diagonal); + + // Remove any nodes or links that are no longer needed + nodes.exit().remove(); + links.exit().remove(); + + let zoom = d3.zoom().on('zoom', handleZoom); + function handleZoom(e: any) { + d3.select('g.chart') + .attr('transform', e.transform); + } + svg.call(zoom); + } + }, [systemTree, width, height]); + + return + + ; +}; + +export default TreeDisplay; diff --git a/packages/ott-vis-panel/src/module.ts b/packages/ott-vis-panel/src/module.ts index 9bd61cb20..880641ca1 100644 --- a/packages/ott-vis-panel/src/module.ts +++ b/packages/ott-vis-panel/src/module.ts @@ -19,6 +19,10 @@ export const plugin = new PanelPlugin(CorePanel).setPanelOptions(bu value: "region", label: "Region", }, + { + value: "tree", + label: "Tree", + } ], }, }) From 4ed533fa7ca6017fae85cf0aa94b8ef51b916426 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 10:24:54 -0400 Subject: [PATCH 02/19] add some new aggregation utils --- packages/ott-vis-panel/src/aggregate.spec.ts | 42 +++++++++++++- packages/ott-vis-panel/src/aggregate.ts | 60 +++++++++++++++++++- 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/packages/ott-vis-panel/src/aggregate.spec.ts b/packages/ott-vis-panel/src/aggregate.spec.ts index 3a88c0630..fb2716e64 100644 --- a/packages/ott-vis-panel/src/aggregate.spec.ts +++ b/packages/ott-vis-panel/src/aggregate.spec.ts @@ -1,5 +1,11 @@ import type { SystemState } from "ott-vis"; -import { aggMonolithRooms, countRoomClients, groupMonolithsByRegion } from "./aggregate"; +import { + aggMonolithRooms, + countRoomClients, + dedupeMonoliths, + dedupeRooms, + groupMonolithsByRegion, +} from "./aggregate"; const sampleSystemState: SystemState = [ { @@ -116,4 +122,38 @@ describe("aggregation helpers", () => { cdg: ["0c85b46e-d343-46a3-ae4f-5f2aa1a8bdac", "f21df607-b572-4bdd-aa2f-3fead21bba86"], }); }); + + it("dedupes rooms", () => { + const rooms = [ + { name: "foo", clients: 1 }, + { name: "bar", clients: 2 }, + { name: "foo", clients: 1 }, + ]; + expect(dedupeRooms(rooms)).toEqual([ + { name: "foo", clients: 2 }, + { name: "bar", clients: 2 }, + ]); + }); + + it("dedupes rooms using sample data", () => { + const rooms = sampleSystemState.flatMap(b => b.monoliths.flatMap(m => m.rooms)); + expect(dedupeRooms(rooms)).toEqual([ + { name: "foo", clients: 3 }, + { name: "bar", clients: 2 }, + { name: "baz", clients: 3 }, + { name: "qux", clients: 4 }, + ]); + }); + + it("dedupes monoliths", () => { + const monoliths = [ + { id: "a", region: "x", rooms: [{ name: "foo", clients: 2 }] }, + { id: "b", region: "x", rooms: [] }, + { id: "a", region: "x", rooms: [{ name: "foo", clients: 1 }] }, + ]; + expect(dedupeMonoliths(monoliths)).toEqual([ + { id: "a", region: "x", rooms: [{ name: "foo", clients: 3 }] }, + { id: "b", region: "x", rooms: [] }, + ]); + }); }); diff --git a/packages/ott-vis-panel/src/aggregate.ts b/packages/ott-vis-panel/src/aggregate.ts index 94b3f36d7..bf976094f 100644 --- a/packages/ott-vis-panel/src/aggregate.ts +++ b/packages/ott-vis-panel/src/aggregate.ts @@ -1,4 +1,4 @@ -import type { SystemState } from "ott-vis"; +import type { Monolith, Room, SystemState } from "ott-vis"; /** * Builds a map of room names to the number of clients in each room from the room state. @@ -46,3 +46,61 @@ export function groupMonolithsByRegion(state: SystemState): Record [k, Array.from(v)])); } + +/** + * Reduces two room states into a single room state. + * @param rA + * @param rB + * @returns + */ +function reduceRoom(rA: Room, rB: Room): Room { + if (rA.name !== rB.name) { + throw new Error("Cannot reduce rooms with different names"); + } + return { + name: rA.name, + clients: rA.clients + rB.clients, + }; +} + +function reduceMonolith(mA: Monolith, mB: Monolith): Monolith { + if (mA.id !== mB.id) { + throw new Error("Cannot reduce monoliths with different ids"); + } + return { + id: mA.id, + region: mA.region, + rooms: dedupeRooms([...mA.rooms, ...mB.rooms]), + }; +} + +export function dedupeItems( + items: T[], + getKey: (item: T) => string, + reduce: (a: T, b: T) => T +): T[] { + const itemMap = new Map(); + for (const item of items) { + const key = getKey(item); + let existingItem = itemMap.get(key); + if (!existingItem) { + existingItem = item; + itemMap.set(key, existingItem); + continue; + } + itemMap.set(key, reduce(existingItem, item)); + } + return Array.from(itemMap.values()); +} + +/** + * Takes a list of rooms and produces a new list of rooms such that each room only appears once. + * @param rooms List of all rooms across all balancers + */ +export function dedupeRooms(rooms: Room[]): Room[] { + return dedupeItems(rooms, room => room.name, reduceRoom); +} + +export function dedupeMonoliths(monoliths: Monolith[]): Monolith[] { + return dedupeItems(monoliths, monolith => monolith.id, reduceMonolith); +} From 9ddc06a1e8bb91295ecedc217d04aedda44762e6 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 10:25:01 -0400 Subject: [PATCH 03/19] format --- .../src/components/TreeDisplay.tsx | 53 ++++++++++--------- packages/ott-vis-panel/src/module.ts | 2 +- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index 42af341e8..4519b0516 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useMemo, useRef } from 'react'; -import * as d3 from 'd3'; -import type { SystemState } from 'ott-vis/types'; +import React, { useEffect, useMemo, useRef } from "react"; +import * as d3 from "d3"; +import type { SystemState } from "ott-vis/types"; interface TreeDisplayProps { systemState: SystemState; @@ -82,51 +82,52 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) // Select the SVG element and bind the hierarchy data to it const svg = d3.select(svgRef.current); - const g = svg.select('g.chart'); - const nodes = g.selectAll('.node').data(root.descendants()); - const links = g.selectAll('.link').data(root.links()); + const g = svg.select("g.chart"); + const nodes = g.selectAll(".node").data(root.descendants()); + const links = g.selectAll(".link").data(root.links()); links .enter() - .append('path') - .attr('class', 'link') - .attr('d', diagonal) - .attr('fill', 'none') + .append("path") + .attr("class", "link") + .attr("d", diagonal) + .attr("fill", "none") .attr("stroke", "white") - .attr('stroke-width', 1.5); + .attr("stroke-width", 1.5); nodes .enter() - .append('circle') - .attr('class', 'node') - .attr('cy', (d: any) => d.x) - .attr('cx', (d: any) => d.y) - .attr('r', 20) + .append("circle") + .attr("class", "node") + .attr("cy", (d: any) => d.x) + .attr("cx", (d: any) => d.y) + .attr("r", 20) .attr("stroke", "white") - .attr('stroke-width', 2) - .attr('fill', d => color(d.data.group)) + .attr("stroke-width", 2) + .attr("fill", d => color(d.data.group)) .attr("data-nodeid", d => d.data.id); // Update existing nodes and links - nodes.attr('cx', (d: any) => d.y).attr('cy', (d: any) => d.x); - links.attr('d', diagonal); + nodes.attr("cx", (d: any) => d.y).attr("cy", (d: any) => d.x); + links.attr("d", diagonal); // Remove any nodes or links that are no longer needed nodes.exit().remove(); links.exit().remove(); - let zoom = d3.zoom().on('zoom', handleZoom); + let zoom = d3.zoom().on("zoom", handleZoom); function handleZoom(e: any) { - d3.select('g.chart') - .attr('transform', e.transform); + d3.select("g.chart").attr("transform", e.transform); } svg.call(zoom); } }, [systemTree, width, height]); - return - - ; + return ( + + + + ); }; export default TreeDisplay; diff --git a/packages/ott-vis-panel/src/module.ts b/packages/ott-vis-panel/src/module.ts index 880641ca1..bcd785726 100644 --- a/packages/ott-vis-panel/src/module.ts +++ b/packages/ott-vis-panel/src/module.ts @@ -22,7 +22,7 @@ export const plugin = new PanelPlugin(CorePanel).setPanelOptions(bu { value: "tree", label: "Tree", - } + }, ], }, }) From d153a2072b9e82275ab81eed1cddc3b105cd7fa2 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 10:44:40 -0400 Subject: [PATCH 04/19] add comment --- packages/ott-vis-panel/src/aggregate.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ott-vis-panel/src/aggregate.ts b/packages/ott-vis-panel/src/aggregate.ts index bf976094f..f5eafd15f 100644 --- a/packages/ott-vis-panel/src/aggregate.ts +++ b/packages/ott-vis-panel/src/aggregate.ts @@ -57,6 +57,7 @@ function reduceRoom(rA: Room, rB: Room): Room { if (rA.name !== rB.name) { throw new Error("Cannot reduce rooms with different names"); } + // FIXME: (perf) This is a potentially hot path, and we should avoid creating a new object here. return { name: rA.name, clients: rA.clients + rB.clients, @@ -67,6 +68,7 @@ function reduceMonolith(mA: Monolith, mB: Monolith): Monolith { if (mA.id !== mB.id) { throw new Error("Cannot reduce monoliths with different ids"); } + // FIXME: (perf) This is a potentially hot path, and we should avoid creating a new object here. return { id: mA.id, region: mA.region, From d6624ac1b7b6f29a03a7f6ceb351cc75e26d5628 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 10:45:30 -0400 Subject: [PATCH 05/19] refactor how building the full tree works --- .../src/components/TreeDisplay.tsx | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index 4519b0516..e2fda33bf 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useMemo, useRef } from "react"; import * as d3 from "d3"; -import type { SystemState } from "ott-vis/types"; +import type { Monolith, SystemState } from "ott-vis/types"; +import { dedupeMonoliths } from "aggregate"; interface TreeDisplayProps { systemState: SystemState; @@ -17,13 +18,20 @@ interface TreeNode { children: TreeNode[]; } -function buildTree(systemState: SystemState): TreeNode { +function buildFullTree(systemState: SystemState): TreeNode { const tree: TreeNode = { id: "root", region: "global", group: "root", children: [], }; + const monoliths = systemState.flatMap(balancer => balancer.monoliths); + const monolithNodes: Map = new Map( + buildMonolithTrees(monoliths).map(monolith => { + return [monolith.id, monolith]; + }) + ); + for (const balancer of systemState) { const balancerNode: TreeNode = { id: balancer.id, @@ -33,37 +41,42 @@ function buildTree(systemState: SystemState): TreeNode { }; tree.children.push(balancerNode); for (const monolith of balancer.monoliths) { - const monolithNode: TreeNode = { - id: monolith.id, - region: monolith.region, - group: "monolith", - children: [], - }; - balancerNode.children.push(monolithNode); - for (const room of monolith.rooms) { - const roomNode: TreeNode = { - id: room.name, - region: monolith.region, - group: "room", - children: Array.from({ length: room.clients }, (_, index) => { - return { - id: `${room.name}-${index}`, - region: monolith.region, - group: "client", - children: [], - }; - }), - }; - monolithNode.children.push(roomNode); - } + balancerNode.children.push(monolithNodes.get(monolith.id) as TreeNode); } } return tree; } +function buildMonolithTrees(monoliths: Monolith[]): TreeNode[] { + return dedupeMonoliths(monoliths).map(monolith => { + const roomNodes: TreeNode[] = monolith.rooms.map(room => { + return { + id: room.name, + region: monolith.region, + group: "room", + children: Array.from({ length: room.clients }, (_, index) => { + return { + id: `${room.name}-${index}`, + region: monolith.region, + group: "client", + children: [], + }; + }), + }; + }); + const monolithNode: TreeNode = { + id: monolith.id, + region: monolith.region, + group: "monolith", + children: roomNodes, + }; + return monolithNode; + }); +} + const TreeDisplay: React.FC = ({ systemState, width, height }) => { const svgRef = useRef(null); - const systemTree = useMemo(() => buildTree(systemState), [systemState]); + const systemTree = useMemo(() => buildFullTree(systemState), [systemState]); useEffect(() => { if (systemTree && svgRef.current) { From b0fa4a5b9a35ce296bb7d59ddc9602290893163b Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 10:51:12 -0400 Subject: [PATCH 06/19] fix type errors --- packages/ott-vis-panel/src/components/TreeDisplay.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index e2fda33bf..162be56dc 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -80,11 +80,10 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) useEffect(() => { if (systemTree && svgRef.current) { - const treeLayout = d3.tree().size([width, height]).nodeSize([60, 120]); + const treeLayout = d3.tree().nodeSize([60, 120]); const root = d3.hierarchy(systemTree); - // @ts-expect-error asdf treeLayout(root); // Create a new D3 diagonal generator From 53183389228382d86756c97495c52a7f986298ec Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 13:05:00 -0400 Subject: [PATCH 07/19] merge duplicate monoliths and manually connect balancers to monoliths --- .../src/components/TreeDisplay.spec.tsx | 35 +++ .../src/components/TreeDisplay.tsx | 217 ++++++++++++++---- 2 files changed, 212 insertions(+), 40 deletions(-) create mode 100644 packages/ott-vis-panel/src/components/TreeDisplay.spec.tsx diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.spec.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.spec.tsx new file mode 100644 index 000000000..3da4bbd70 --- /dev/null +++ b/packages/ott-vis-panel/src/components/TreeDisplay.spec.tsx @@ -0,0 +1,35 @@ +import * as d3 from "d3"; +import { sizeOfTree } from "./TreeDisplay"; + +describe("TreeDisplay", () => { + it("should find the size of any d3 tree", () => { + interface FooTree { + name: string; + children: FooTree[]; + } + const tree: FooTree = { + name: "root", + children: [ + { + name: "child1", + children: [ + { + name: "child1.1", + children: [], + }, + ], + }, + { + name: "child2", + children: [], + }, + ], + }; + const treeLayout = d3.tree().nodeSize([10, 10]); + const root = d3.hierarchy(tree); + treeLayout(root); + const [width, height] = sizeOfTree(root); + expect(width).toBeGreaterThan(0); + expect(height).toBeGreaterThan(0); + }); +}); diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index 162be56dc..f799fa195 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -18,6 +18,7 @@ interface TreeNode { children: TreeNode[]; } +// @ts-expect-error currently unused and i don't want to remove it yet function buildFullTree(systemState: SystemState): TreeNode { const tree: TreeNode = { id: "root", @@ -74,58 +75,185 @@ function buildMonolithTrees(monoliths: Monolith[]): TreeNode[] { }); } +/** + * Gets the physical size of a tree after it's been laid out. Does not account for the size of the actual nodes, just the space they take up. + * @returns [width, height] + */ +export function sizeOfTree(tree: d3.HierarchyNode): [number, number] { + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + tree.each(node => { + // @ts-expect-error d3 adds x and y to the node + minX = Math.min(minX, node.x); + // @ts-expect-error d3 adds x and y to the node + minY = Math.min(minY, node.y); + // @ts-expect-error d3 adds x and y to the node + maxX = Math.max(maxX, node.x); + // @ts-expect-error d3 adds x and y to the node + maxY = Math.max(maxY, node.y); + }); + return [maxX - minX, maxY - minY]; +} + +interface Node { + id: string; + x: number; + y: number; +} + +interface BalancerNode extends Node { + region: string; + group: string; +} + +interface MonolithNode extends Node { + tree: d3.HierarchyNode; +} + +const NODE_RADIUS = 20; + const TreeDisplay: React.FC = ({ systemState, width, height }) => { const svgRef = useRef(null); - const systemTree = useMemo(() => buildFullTree(systemState), [systemState]); + // const systemTree = useMemo(() => buildFullTree(systemState), [systemState]); + const monolithTrees = useMemo( + () => buildMonolithTrees(systemState.flatMap(b => b.monoliths)), + [systemState] + ); useEffect(() => { - if (systemTree && svgRef.current) { - const treeLayout = d3.tree().nodeSize([60, 120]); + if (svgRef.current) { + // because d3-hierarchy doesn't support trees with multiple parents, we need to do manual layouts for balancers and monoliths, but we can use the built-in tree layout for monolith down to clients - const root = d3.hierarchy(systemTree); + const svg = d3.select(svgRef.current); + const wholeGraph = svg.select("g.chart"); + const gb2mLinks = wholeGraph.selectAll("g.b2m-links"); - treeLayout(root); + // build all the sub-trees first + const builtMonolithTrees: d3.HierarchyNode[] = []; + for (const monolithTree of monolithTrees) { + const treeLayout = d3.tree().nodeSize([60, 120]); + const root = d3.hierarchy(monolithTree); + treeLayout(root); + builtMonolithTrees.push(root); + } - // Create a new D3 diagonal generator - const diagonal = d3 - .linkHorizontal() - .x((d: any) => d.y) - .y((d: any) => d.x); + // create nodes for all the balancers + const balancerNodes = systemState.map((balancer, i) => { + const node: BalancerNode = { + id: balancer.id, + region: balancer.region, + group: "balancer", + x: 0, + y: i * (NODE_RADIUS * 2 + 20), + }; + return node; + }); - // Select the SVG element and bind the hierarchy data to it - const svg = d3.select(svgRef.current); - const g = svg.select("g.chart"); - const nodes = g.selectAll(".node").data(root.descendants()); - const links = g.selectAll(".link").data(root.links()); + const balancerGroup = wholeGraph.select("g.balancers"); + const balancerCircles = balancerGroup.selectAll(".balancer").data(balancerNodes); + balancerCircles + .enter() + .append("circle") + .attr("class", "balancer") + .attr("r", NODE_RADIUS) + .attr("fill", d => color(d.group)) + .attr("stroke", "white") + .attr("stroke-width", 2) + .attr("cx", d => d.x) + .attr("cy", d => d.y); + balancerCircles.exit().remove(); + + // compute positions of monolith trees + const monolithTreeHeights = builtMonolithTrees.map(tree => sizeOfTree(tree)[1]); + const monolithTreeYs = monolithTreeHeights.reduce( + (acc, height, i) => { + acc.push(acc[i] + height + 60); + return acc; + }, + [0] + ); + const monolithNodes = monolithTrees.map((monolith, i) => { + const node: MonolithNode = { + tree: builtMonolithTrees[i], + id: monolith.id, + x: 100, + y: monolithTreeYs[i], + }; + return node; + }); - links + // create groups for all the monoliths + const monolithGroup = wholeGraph.select("g.monoliths"); + const monolithGroups = monolithGroup.selectAll(".monolith").data(monolithNodes); + monolithGroups + .enter() + .append("g") + .attr("class", "monolith") + .attr("transform", (d, i) => `translate(${d.x}, ${d.y})`) + .each(function (d) { + const diagonal = d3 + .linkHorizontal() + .x((d: any) => d.y) + .y((d: any) => d.x); + + const monolith = d3.select(this); + const monolithLinks = monolith.selectAll(".treelink").data(d.tree.links()); + monolithLinks + .enter() + .append("path") + .attr("class", "treelink") + .attr("d", diagonal) + .attr("fill", "none") + .attr("stroke", "white") + .attr("stroke-width", 1.5); + monolithLinks.exit().remove(); + + const monolithCircles = monolith + .selectAll(".monolith") + .data(d.tree.descendants()); + monolithCircles + .enter() + .append("circle") + .attr("class", "monolith") + .attr("r", NODE_RADIUS) + .attr("fill", d => color(d.data.group)) + .attr("stroke", "white") + .attr("stroke-width", 2) + .attr("cx", (d: any) => d.y) + .attr("cy", (d: any) => d.x); + monolithCircles.exit().remove(); + }); + + // create the links between balancers and monoliths + interface B2MLink { + source: BalancerNode; + target: MonolithNode; + } + const diagonal = d3 + .linkHorizontal() + .x(d => d.x) + .y(d => d.y); + + const b2mLinkData = balancerNodes.flatMap(balancer => { + return monolithNodes.map(monolith => { + return { + source: balancer, + target: monolith, + }; + }); + }); + const balancerMonolithLinks = gb2mLinks.selectAll(".b2m-link").data(b2mLinkData); + balancerMonolithLinks .enter() .append("path") - .attr("class", "link") + .attr("class", "b2m-link") .attr("d", diagonal) .attr("fill", "none") .attr("stroke", "white") .attr("stroke-width", 1.5); - - nodes - .enter() - .append("circle") - .attr("class", "node") - .attr("cy", (d: any) => d.x) - .attr("cx", (d: any) => d.y) - .attr("r", 20) - .attr("stroke", "white") - .attr("stroke-width", 2) - .attr("fill", d => color(d.data.group)) - .attr("data-nodeid", d => d.data.id); - - // Update existing nodes and links - nodes.attr("cx", (d: any) => d.y).attr("cy", (d: any) => d.x); - links.attr("d", diagonal); - - // Remove any nodes or links that are no longer needed - nodes.exit().remove(); - links.exit().remove(); + balancerMonolithLinks.exit().remove(); let zoom = d3.zoom().on("zoom", handleZoom); function handleZoom(e: any) { @@ -133,11 +261,20 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) } svg.call(zoom); } - }, [systemTree, width, height]); + }, [systemState, monolithTrees, width, height]); return ( - - + + + + + + ); }; From a7a55ec2d9101efcaffa389a520c381f7c065091 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 13:15:19 -0400 Subject: [PATCH 08/19] better balancer node positioning --- .../src/components/TreeDisplay.tsx | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index f799fa195..0a317f30a 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -139,14 +139,38 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) builtMonolithTrees.push(root); } - // create nodes for all the balancers + // compute positions of monolith trees + const monolithTreeHeights = builtMonolithTrees.map(tree => sizeOfTree(tree)[1]); + const monolithTreeYs = monolithTreeHeights.reduce( + (acc, height, i) => { + acc.push(acc[i] + height + 60); + return acc; + }, + [0] + ); + const monolithNodes = monolithTrees.map((monolith, i) => { + const node: MonolithNode = { + tree: builtMonolithTrees[i], + id: monolith.id, + x: 100, + y: monolithTreeYs[i], + }; + return node; + }); + + // create nodes for all the balancers evenly spaced along the full height of the monolith trees + // but also guarenteeing that they don't overlap with each other or the monoliths with some padding + const fullHeight = monolithTreeYs[monolithTreeYs.length - 1]; + const lerp = d3.interpolateNumber(0, fullHeight); + const lerpincr = 1 / systemState.length; + const yincr = Math.max(lerp(lerpincr), NODE_RADIUS * 2 + 20); const balancerNodes = systemState.map((balancer, i) => { const node: BalancerNode = { id: balancer.id, region: balancer.region, group: "balancer", x: 0, - y: i * (NODE_RADIUS * 2 + 20), + y: i * yincr, }; return node; }); @@ -165,25 +189,6 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .attr("cy", d => d.y); balancerCircles.exit().remove(); - // compute positions of monolith trees - const monolithTreeHeights = builtMonolithTrees.map(tree => sizeOfTree(tree)[1]); - const monolithTreeYs = monolithTreeHeights.reduce( - (acc, height, i) => { - acc.push(acc[i] + height + 60); - return acc; - }, - [0] - ); - const monolithNodes = monolithTrees.map((monolith, i) => { - const node: MonolithNode = { - tree: builtMonolithTrees[i], - id: monolith.id, - x: 100, - y: monolithTreeYs[i], - }; - return node; - }); - // create groups for all the monoliths const monolithGroup = wholeGraph.select("g.monoliths"); const monolithGroups = monolithGroup.selectAll(".monolith").data(monolithNodes); From 5a61823a70e83d67a8f20f78e51772d277fc08cc Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 13:33:15 -0400 Subject: [PATCH 09/19] fix monolith trees overlapping --- packages/ott-vis-panel/src/components/TreeDisplay.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index 0a317f30a..50e825866 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -140,10 +140,11 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) } // compute positions of monolith trees - const monolithTreeHeights = builtMonolithTrees.map(tree => sizeOfTree(tree)[1]); + // note: we are actually using the width here because the trees are being rotated 90 deg + const monolithTreeHeights = builtMonolithTrees.map(tree => sizeOfTree(tree)[0]); const monolithTreeYs = monolithTreeHeights.reduce( (acc, height, i) => { - acc.push(acc[i] + height + 60); + acc.push(acc[i] + height + 20); return acc; }, [0] From f830bdf263f01bd088b7adb20af41188361636e2 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 13:59:26 -0400 Subject: [PATCH 10/19] adjust radius of clients --- packages/ott-vis-panel/src/components/TreeDisplay.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index 50e825866..a1b7ce22d 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -114,6 +114,13 @@ interface MonolithNode extends Node { const NODE_RADIUS = 20; +function radius(node: TreeNode) { + if (node.group === "client") { + return 8; + } + return NODE_RADIUS; +} + const TreeDisplay: React.FC = ({ systemState, width, height }) => { const svgRef = useRef(null); // const systemTree = useMemo(() => buildFullTree(systemState), [systemState]); @@ -133,7 +140,7 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) // build all the sub-trees first const builtMonolithTrees: d3.HierarchyNode[] = []; for (const monolithTree of monolithTrees) { - const treeLayout = d3.tree().nodeSize([60, 120]); + const treeLayout = d3.tree().nodeSize([NODE_RADIUS * 2, 120]); const root = d3.hierarchy(monolithTree); treeLayout(root); builtMonolithTrees.push(root); @@ -223,7 +230,7 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .enter() .append("circle") .attr("class", "monolith") - .attr("r", NODE_RADIUS) + .attr("r", d => radius(d.data)) .attr("fill", d => color(d.data.group)) .attr("stroke", "white") .attr("stroke-width", 2) From 0af69f126574eecded571038f7ceaf4a40e8d302 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 14:03:46 -0400 Subject: [PATCH 11/19] fix tree placement for monoliths with no rooms --- packages/ott-vis-panel/src/components/TreeDisplay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index a1b7ce22d..09fde8d2a 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -151,7 +151,7 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) const monolithTreeHeights = builtMonolithTrees.map(tree => sizeOfTree(tree)[0]); const monolithTreeYs = monolithTreeHeights.reduce( (acc, height, i) => { - acc.push(acc[i] + height + 20); + acc.push(acc[i] + Math.max(height, (NODE_RADIUS * 2 + 10))); return acc; }, [0] From 812759eba0ac1d85ca2aefd5c76b0f98b39826ca Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 14:15:26 -0400 Subject: [PATCH 12/19] hook up event bus to tree display --- .../src/components/TreeDisplay.tsx | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index 09fde8d2a..06b396d13 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useRef } from "react"; import * as d3 from "d3"; import type { Monolith, SystemState } from "ott-vis/types"; import { dedupeMonoliths } from "aggregate"; +import { useEventBus } from "eventbus"; interface TreeDisplayProps { systemState: SystemState; @@ -194,7 +195,8 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .attr("stroke", "white") .attr("stroke-width", 2) .attr("cx", d => d.x) - .attr("cy", d => d.y); + .attr("cy", d => d.y) + .attr("data-nodeid", d => d.id); balancerCircles.exit().remove(); // create groups for all the monoliths @@ -235,7 +237,8 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .attr("stroke", "white") .attr("stroke-width", 2) .attr("cx", (d: any) => d.y) - .attr("cy", (d: any) => d.x); + .attr("cy", (d: any) => d.x) + .attr("data-nodeid", d => d.data.id); monolithCircles.exit().remove(); }); @@ -276,6 +279,21 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) } }, [systemState, monolithTrees, width, height]); + const eventBus = useEventBus(); + useEffect(() => { + const sub = eventBus.subscribe(event => { + d3.select(`[data-nodeid="${event.node_id}"]`) + .transition() + .duration(100) + .attrTween("stroke", () => d3.interpolateRgb("#f00", "#fff")) + .attrTween("stroke-width", () => t => d3.interpolateNumber(4, 1.5)(t).toString()); + }); + + return () => { + sub.unsubscribe(); + }; + }, [eventBus]); + return ( Date: Thu, 14 Mar 2024 14:24:27 -0400 Subject: [PATCH 13/19] add attributes for source and target node ids for links --- packages/ott-vis-panel/src/components/TreeDisplay.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index 06b396d13..b4a29ae09 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -222,7 +222,9 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .attr("d", diagonal) .attr("fill", "none") .attr("stroke", "white") - .attr("stroke-width", 1.5); + .attr("stroke-width", 1.5) + .attr("data-nodeid-source", d => d.source.data.id) + .attr("data-nodeid-target", d => d.target.data.id); monolithLinks.exit().remove(); const monolithCircles = monolith @@ -268,7 +270,9 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .attr("d", diagonal) .attr("fill", "none") .attr("stroke", "white") - .attr("stroke-width", 1.5); + .attr("stroke-width", 1.5) + .attr("data-nodeid-source", d => d.source.id) + .attr("data-nodeid-target", d => d.target.id); balancerMonolithLinks.exit().remove(); let zoom = d3.zoom().on("zoom", handleZoom); From 668f516086723a11f605394f7537d7a2bdf35355 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 14:25:16 -0400 Subject: [PATCH 14/19] format --- packages/ott-vis-panel/src/components/TreeDisplay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index b4a29ae09..a15f786ad 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -152,7 +152,7 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) const monolithTreeHeights = builtMonolithTrees.map(tree => sizeOfTree(tree)[0]); const monolithTreeYs = monolithTreeHeights.reduce( (acc, height, i) => { - acc.push(acc[i] + Math.max(height, (NODE_RADIUS * 2 + 10))); + acc.push(acc[i] + Math.max(height, NODE_RADIUS * 2 + 10)); return acc; }, [0] From 47a349d15c36156674c7a1d0d0377a453c00df28 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 15:00:30 -0400 Subject: [PATCH 15/19] fix zoom conflicting with other panels --- packages/ott-vis-panel/src/components/TreeDisplay.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index a15f786ad..b4236af4c 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -275,9 +275,9 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .attr("data-nodeid-target", d => d.target.id); balancerMonolithLinks.exit().remove(); - let zoom = d3.zoom().on("zoom", handleZoom); + const zoom = d3.zoom().on("zoom", handleZoom); function handleZoom(e: any) { - d3.select("g.chart").attr("transform", e.transform); + svg.select("g.chart").attr("transform", e.transform); } svg.call(zoom); } From 1c76e5dc151dab4dc03f0a1e6136eda566622693 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 15:31:07 -0400 Subject: [PATCH 16/19] add text to nodes in tree --- .../src/components/TreeDisplay.tsx | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index b4236af4c..b16ec760b 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -190,7 +190,7 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .enter() .append("circle") .attr("class", "balancer") - .attr("r", NODE_RADIUS) + .attr("r", NODE_RADIUS + 10) .attr("fill", d => color(d.group)) .attr("stroke", "white") .attr("stroke-width", 2) @@ -198,6 +198,22 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .attr("cy", d => d.y) .attr("data-nodeid", d => d.id); balancerCircles.exit().remove(); + const balancerTexts = balancerGroup.selectAll(".balancer-text").data(balancerNodes); + balancerTexts + .enter() + .append("text") + .attr("class", "balancer-text") + .attr("text-anchor", "middle") + .attr("alignment-baseline", "middle") + .attr("font-family", "Inter, Helvetica, Arial, sans-serif") + .attr("font-size", 10) + .attr("stroke-width", 0) + .attr("fill", "white") + .attr("x", d => d.x) + .attr("y", d => d.y + 4) + .text(d => `${d.region.substring(0, 3)} ${d.id}`.substring(0, 10)); + balancerTexts.exit().remove(); + // create groups for all the monoliths const monolithGroup = wholeGraph.select("g.monoliths"); @@ -242,6 +258,23 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .attr("cy", (d: any) => d.x) .attr("data-nodeid", d => d.data.id); monolithCircles.exit().remove(); + const monolithTexts = monolith.selectAll(".monolith-text").data(d.tree.descendants()); + monolithTexts + .enter() + // intentionally not showing room and client names -- user generated content can contain offensive material + .filter(d => d.data.group === "monolith") + .append("text") + .attr("class", "monolith-text") + .attr("text-anchor", "middle") + .attr("alignment-baseline", "middle") + .attr("font-family", "Inter, Helvetica, Arial, sans-serif") + .attr("font-size", 10) + .attr("stroke-width", 0) + .attr("fill", "white") + .attr("x", (d: any) => d.y) + .attr("y", (d: any) => d.x + 4) + .text(d => `${d.data.id}`.substring(0, 6)); + monolithTexts.exit().remove(); }); // create the links between balancers and monoliths From df6adeba90957c7894f2f746b9f9c205a16de8a8 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 15:31:48 -0400 Subject: [PATCH 17/19] make TreeDisplay remember current zoom position across rerenders --- .../ott-vis-panel/src/components/TreeDisplay.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index b16ec760b..82b752cea 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import * as d3 from "d3"; import type { Monolith, SystemState } from "ott-vis/types"; import { dedupeMonoliths } from "aggregate"; @@ -130,12 +130,17 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) [systemState] ); + const [chartTransform, setChartTransform] = useState("translate(0, 0)"); + useEffect(() => { if (svgRef.current) { // because d3-hierarchy doesn't support trees with multiple parents, we need to do manual layouts for balancers and monoliths, but we can use the built-in tree layout for monolith down to clients const svg = d3.select(svgRef.current); - const wholeGraph = svg.select("g.chart"); + const wholeGraph = svg.select("g.chart").attr( + "transform", + chartTransform, + ); const gb2mLinks = wholeGraph.selectAll("g.b2m-links"); // build all the sub-trees first @@ -311,10 +316,11 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) const zoom = d3.zoom().on("zoom", handleZoom); function handleZoom(e: any) { svg.select("g.chart").attr("transform", e.transform); + setChartTransform(e.transform); } svg.call(zoom); } - }, [systemState, monolithTrees, width, height]); + }, [systemState, monolithTrees, width, height, chartTransform]); const eventBus = useEventBus(); useEffect(() => { From bfd789d49a903157a1264dbfe617f028d3fdc07d Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 15:32:07 -0400 Subject: [PATCH 18/19] add tree panel in provisioned dashboard --- .../provisioning/dashboards/dashboard.json | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/ott-vis/provisioning/dashboards/dashboard.json b/packages/ott-vis/provisioning/dashboards/dashboard.json index 69c0a67ad..05f0a61af 100644 --- a/packages/ott-vis/provisioning/dashboards/dashboard.json +++ b/packages/ott-vis/provisioning/dashboards/dashboard.json @@ -90,6 +90,42 @@ "title": "Region", "type": "ott-vis-panel" }, + { + "datasource": { + "type": "ott-vis-datasource", + "uid": "P8AFEECD30EDC727B" + }, + "gridPos": { + "h": 14, + "w": 24, + "x": 0, + "y": 19 + }, + "id": 6, + "options": { + "view": "tree" + }, + "targets": [ + { + "datasource": { + "type": "ott-vis-datasource", + "uid": "P8AFEECD30EDC727B" + }, + "refId": "A" + }, + { + "datasource": { + "type": "ott-vis-datasource", + "uid": "P8AFEECD30EDC727B" + }, + "hide": false, + "refId": "B", + "stream": true + } + ], + "title": "Tree", + "type": "ott-vis-panel" + }, { "datasource": { "type": "datasource", @@ -128,7 +164,7 @@ "h": 8, "w": 9, "x": 0, - "y": 19 + "y": 33 }, "id": 4, "options": { @@ -257,7 +293,7 @@ "h": 8, "w": 7, "x": 9, - "y": 19 + "y": 33 }, "id": 2, "options": { @@ -336,7 +372,7 @@ "h": 8, "w": 8, "x": 16, - "y": 19 + "y": 33 }, "id": 3, "options": { @@ -370,6 +406,6 @@ "timezone": "", "title": "Provisioned ott-vis dashboard", "uid": "c5ea3bcd-f966-47d8-8456-4536ddc45ff0", - "version": 4, + "version": 28, "weekStart": "" } From c67248c9771173678e2fffa7b5258c399b951e18 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 14 Mar 2024 15:57:19 -0400 Subject: [PATCH 19/19] format --- packages/ott-vis-panel/src/components/TreeDisplay.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/ott-vis-panel/src/components/TreeDisplay.tsx b/packages/ott-vis-panel/src/components/TreeDisplay.tsx index 82b752cea..002469a91 100644 --- a/packages/ott-vis-panel/src/components/TreeDisplay.tsx +++ b/packages/ott-vis-panel/src/components/TreeDisplay.tsx @@ -137,10 +137,7 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) // because d3-hierarchy doesn't support trees with multiple parents, we need to do manual layouts for balancers and monoliths, but we can use the built-in tree layout for monolith down to clients const svg = d3.select(svgRef.current); - const wholeGraph = svg.select("g.chart").attr( - "transform", - chartTransform, - ); + const wholeGraph = svg.select("g.chart").attr("transform", chartTransform); const gb2mLinks = wholeGraph.selectAll("g.b2m-links"); // build all the sub-trees first @@ -219,7 +216,6 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .text(d => `${d.region.substring(0, 3)} ${d.id}`.substring(0, 10)); balancerTexts.exit().remove(); - // create groups for all the monoliths const monolithGroup = wholeGraph.select("g.monoliths"); const monolithGroups = monolithGroup.selectAll(".monolith").data(monolithNodes); @@ -263,7 +259,9 @@ const TreeDisplay: React.FC = ({ systemState, width, height }) .attr("cy", (d: any) => d.x) .attr("data-nodeid", d => d.data.id); monolithCircles.exit().remove(); - const monolithTexts = monolith.selectAll(".monolith-text").data(d.tree.descendants()); + const monolithTexts = monolith + .selectAll(".monolith-text") + .data(d.tree.descendants()); monolithTexts .enter() // intentionally not showing room and client names -- user generated content can contain offensive material