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  };