vitess.io/vitess@v0.16.2/web/vtadmin/src/components/routes/topology/Nodes.tsx (about) 1 /** 2 * Copyright 2022 The Vitess Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 import React from 'react'; 17 import { MarkerType, Node, Edge } from 'react-flow-renderer'; 18 19 export interface TopologyCell { 20 name?: string; 21 data?: string; 22 path: string; 23 children?: TopologyCellChild[]; 24 } 25 26 export type TopologyCellChild = string | TopologyCell; 27 28 export const generateGraph = ( 29 topology: { cell: TopologyCellChild }, 30 onExpand: (path: string) => void 31 ): { nodes: Array<Node>; edges: Array<Edge> } => { 32 return getNodesAndEdges(topology.cell as TopologyCell, '', -1, 0, onExpand); 33 }; 34 35 const getNodesAndEdges = ( 36 cell: TopologyCellChild, 37 path: string, 38 depth: number, 39 width: number, 40 onExpand: (path: string) => void 41 ): { nodes: Array<Node>; edges: Array<Edge> } => { 42 const isCell = typeof cell !== 'string'; 43 const isString = !isCell; 44 const nodes: Array<Node> = []; 45 const edges: Array<Edge> = []; 46 if (isString || cell?.name) { 47 const parentNode: Node = { 48 id: path, 49 position: { y: depth * 100, x: width * 150 }, 50 style: { width: 'min-content' }, 51 data: { 52 label: 53 isCell && cell?.data ? ( 54 <div className="w-fit"> 55 <div className="font-bold">{cell.name}</div> 56 <div className="mt-1 bg-gray-100 p-2 text-[10px] text-left font-mono whitespace-normal"> 57 {cell.data} 58 </div> 59 </div> 60 ) : ( 61 <div className="font-bold"> 62 {typeof cell === 'string' ? cell : cell.name} 63 <button onClick={() => onExpand(path)} className="btn btn-secondary btn-sm mt-1"> 64 Expand 65 </button> 66 </div> 67 ), 68 }, 69 }; 70 71 if (depth === 0) { 72 parentNode.type = 'input'; 73 } 74 75 if (isCell && !cell?.children) { 76 parentNode.type = 'output'; 77 } 78 79 nodes.push(parentNode); 80 } 81 82 if (isCell && cell?.children) { 83 let offset = 0; 84 cell.children.forEach((child, i) => { 85 const childPath = `${path}/${typeof child == 'string' ? child : child.name}`; 86 if (path !== '') { 87 edges.push({ 88 id: `${path}-${childPath}`, 89 source: path, 90 target: childPath, 91 markerEnd: { 92 type: MarkerType.ArrowClosed, 93 }, 94 }); 95 } 96 97 const { nodes: childNodes, edges: childEdges } = getNodesAndEdges( 98 child, 99 childPath, 100 depth + 1, 101 width + offset, 102 onExpand 103 ); 104 nodes.push(...childNodes); 105 edges.push(...childEdges); 106 offset += maxWidth(child); 107 }); 108 } 109 110 return { 111 nodes, 112 edges, 113 }; 114 }; 115 116 const maxWidth = (cell: TopologyCellChild): number => { 117 let width = 0; 118 119 if (typeof cell == 'string' || !cell.children || cell.children?.length === 0) { 120 return 1; 121 } 122 123 cell.children?.forEach((child) => { 124 const childWidth = maxWidth(child); 125 width += childWidth; 126 }); 127 128 return width; 129 };