visualizer: split inputs to separate nodes in tree
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -18,26 +18,117 @@ import {
|
||||
DependencyGraph,
|
||||
DependencyGraphTypes,
|
||||
} from '@backstage/core-components';
|
||||
import { AppTree } from '@backstage/frontend-plugin-api';
|
||||
import { AppNode, AppTree } from '@backstage/frontend-plugin-api';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import { useMemo } from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
function resolveGraphData(tree: AppTree) {
|
||||
type NodeType =
|
||||
| ({ type: 'node'; id: string } & AppNode)
|
||||
| { type: 'input'; id: string; name: string };
|
||||
|
||||
function inputId({ node, input }: { node: AppNode; input: string }) {
|
||||
return `${node.spec.id}$$${input}`;
|
||||
}
|
||||
|
||||
function resolveGraphData(tree: AppTree): {
|
||||
nodes: NodeType[];
|
||||
edges: { from: string; to: string }[];
|
||||
} {
|
||||
const nodes = [...tree.nodes.values()]
|
||||
.filter(n => n.instance)
|
||||
.map(n => ({ ...n, id: n.spec.id }));
|
||||
.filter(node => node.instance)
|
||||
.map(node => ({ ...node, id: node.spec.id, type: 'node' as const }));
|
||||
|
||||
return {
|
||||
nodes,
|
||||
edges: nodes
|
||||
.filter(n => n.edges.attachedTo)
|
||||
.map(n => ({
|
||||
from: n.spec.id,
|
||||
to: n.edges.attachedTo!.node.spec.id,
|
||||
label: n.edges.attachedTo!.input,
|
||||
})),
|
||||
nodes: [
|
||||
...nodes,
|
||||
...nodes.flatMap(node =>
|
||||
[...node.edges.attachments.keys()].map(input => ({
|
||||
id: inputId({ node, input }),
|
||||
type: 'input' as const,
|
||||
name: input,
|
||||
})),
|
||||
),
|
||||
],
|
||||
edges: [
|
||||
...nodes
|
||||
.filter(node => node.edges.attachedTo)
|
||||
.map(node => ({
|
||||
from: inputId(node.edges.attachedTo!),
|
||||
to: node.spec.id,
|
||||
})),
|
||||
...nodes.flatMap(node =>
|
||||
[...node.edges.attachments.keys()].map(input => ({
|
||||
from: node.spec.id,
|
||||
to: inputId({ node, input }),
|
||||
})),
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
node: {
|
||||
fill: (node: NodeType) =>
|
||||
node.type === 'node'
|
||||
? theme.palette.primary.light
|
||||
: theme.palette.grey[500],
|
||||
stroke: theme.palette.primary.light,
|
||||
},
|
||||
text: {
|
||||
fill: theme.palette.primary.contrastText,
|
||||
},
|
||||
}));
|
||||
|
||||
/** @public */
|
||||
export function Node(props: { node: NodeType }) {
|
||||
const { node } = props;
|
||||
const classes = useStyles(node);
|
||||
const [width, setWidth] = useState(0);
|
||||
const [height, setHeight] = useState(0);
|
||||
const idRef = useRef<SVGTextElement | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// set the width to the length of the ID
|
||||
if (idRef.current) {
|
||||
let { height: renderedHeight, width: renderedWidth } =
|
||||
idRef.current.getBBox();
|
||||
renderedHeight = Math.round(renderedHeight);
|
||||
renderedWidth = Math.round(renderedWidth);
|
||||
|
||||
if (renderedHeight !== height || renderedWidth !== width) {
|
||||
setWidth(renderedWidth);
|
||||
setHeight(renderedHeight);
|
||||
}
|
||||
}
|
||||
}, [width, height]);
|
||||
|
||||
const padding = 10;
|
||||
const paddedWidth = width + padding * 2;
|
||||
const paddedHeight = height + padding * 2;
|
||||
|
||||
return (
|
||||
<g>
|
||||
<rect
|
||||
className={classes.node}
|
||||
width={paddedWidth}
|
||||
height={paddedHeight}
|
||||
rx={10}
|
||||
/>
|
||||
<text
|
||||
ref={idRef}
|
||||
className={classes.text}
|
||||
y={paddedHeight / 2}
|
||||
x={paddedWidth / 2}
|
||||
textAnchor="middle"
|
||||
alignmentBaseline="middle"
|
||||
>
|
||||
{node.type === 'node' ? node.id : node.name}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
export function TreeVisualizer({ tree }: { tree: AppTree }) {
|
||||
const graphData = useMemo(() => resolveGraphData(tree), [tree]);
|
||||
|
||||
@@ -50,7 +141,8 @@ export function TreeVisualizer({ tree }: { tree: AppTree }) {
|
||||
nodeMargin={10}
|
||||
edgeMargin={30}
|
||||
rankMargin={100}
|
||||
direction={DependencyGraphTypes.Direction.BOTTOM_TOP}
|
||||
renderNode={Node}
|
||||
direction={DependencyGraphTypes.Direction.TOP_BOTTOM}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user