Merge pull request #7431 from szubster/dependency-graph-type-safety
Documentation and better type safety of DependencyGraph
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/core-components': minor
|
||||
'@backstage/plugin-catalog-graph': minor
|
||||
---
|
||||
|
||||
Add documentation and more type safety around DependencyGraph
|
||||
@@ -17,7 +17,6 @@ import { ComponentProps } from 'react';
|
||||
import { Context } from 'react';
|
||||
import { default as CSS_2 } from 'csstype';
|
||||
import { CSSProperties } from 'react';
|
||||
import { default as dagre_2 } from 'dagre';
|
||||
import { ElementType } from 'react';
|
||||
import { ErrorInfo } from 'react';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
@@ -53,15 +52,11 @@ export function AlertDisplay(_props: {}): JSX.Element | null;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "Alignment" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
enum Alignment {
|
||||
// (undocumented)
|
||||
DOWN_LEFT = 'DL',
|
||||
// (undocumented)
|
||||
DOWN_RIGHT = 'DR',
|
||||
// (undocumented)
|
||||
UP_LEFT = 'UL',
|
||||
// (undocumented)
|
||||
UP_RIGHT = 'UR',
|
||||
}
|
||||
|
||||
@@ -264,11 +259,10 @@ export type CustomProviderClassKey = 'form' | 'button';
|
||||
// @public (undocumented)
|
||||
export function DashboardIcon(props: IconComponentProps): JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "CustomType" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "DependencyEdge" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
type DependencyEdge<T = CustomType> = T & {
|
||||
// @public
|
||||
type DependencyEdge<T = {}> = T & {
|
||||
from: string;
|
||||
to: string;
|
||||
label?: string;
|
||||
@@ -276,8 +270,10 @@ type DependencyEdge<T = CustomType> = T & {
|
||||
|
||||
// Warning: (ae-missing-release-tag) "DependencyGraph" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function DependencyGraph(props: DependencyGraphProps): JSX.Element;
|
||||
// @public
|
||||
export function DependencyGraph<NodeData, EdgeData>(
|
||||
props: DependencyGraphProps<NodeData, EdgeData>,
|
||||
): JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "DependencyGraphDefaultLabelClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -301,40 +297,42 @@ export type DependencyGraphNodeClassKey = 'node';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "DependencyGraphProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type DependencyGraphProps = React_2.SVGProps<SVGSVGElement> & {
|
||||
edges: DependencyEdge[];
|
||||
nodes: DependencyNode[];
|
||||
direction?: Direction;
|
||||
// @public
|
||||
export interface DependencyGraphProps<NodeData, EdgeData>
|
||||
extends React_2.SVGProps<SVGSVGElement> {
|
||||
acyclicer?: 'greedy';
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
|
||||
align?: Alignment;
|
||||
nodeMargin?: number;
|
||||
defs?: SVGDefsElement | SVGDefsElement[];
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
|
||||
direction?: Direction;
|
||||
edgeMargin?: number;
|
||||
rankMargin?: number;
|
||||
edgeRanks?: number;
|
||||
edges: DependencyEdge<EdgeData>[];
|
||||
edgeWeight?: number;
|
||||
labelOffset?: number;
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
|
||||
labelPosition?: LabelPosition;
|
||||
nodeMargin?: number;
|
||||
nodes: DependencyNode<NodeData>[];
|
||||
paddingX?: number;
|
||||
paddingY?: number;
|
||||
acyclicer?: 'greedy';
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
|
||||
ranker?: Ranker;
|
||||
labelPosition?: LabelPosition;
|
||||
labelOffset?: number;
|
||||
edgeRanks?: number;
|
||||
edgeWeight?: number;
|
||||
renderNode?: RenderNodeFunction;
|
||||
renderLabel?: RenderLabelFunction;
|
||||
defs?: SVGDefsElement | SVGDefsElement[];
|
||||
rankMargin?: number;
|
||||
renderLabel?: RenderLabelFunction<EdgeData>;
|
||||
renderNode?: RenderNodeFunction<NodeData>;
|
||||
zoom?: 'enabled' | 'disabled' | 'enable-on-click';
|
||||
};
|
||||
}
|
||||
|
||||
declare namespace DependencyGraphTypes {
|
||||
export {
|
||||
DependencyEdge,
|
||||
GraphEdge,
|
||||
RenderLabelProps,
|
||||
RenderLabelFunction,
|
||||
DependencyNode,
|
||||
GraphNode,
|
||||
RenderNodeProps,
|
||||
RenderNodeFunction,
|
||||
EdgeProperties,
|
||||
Direction,
|
||||
Alignment,
|
||||
Ranker,
|
||||
@@ -345,22 +343,18 @@ export { DependencyGraphTypes };
|
||||
|
||||
// Warning: (ae-missing-release-tag) "DependencyNode" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
type DependencyNode<T = CustomType> = T & {
|
||||
// @public
|
||||
type DependencyNode<T = {}> = T & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "Direction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
enum Direction {
|
||||
// (undocumented)
|
||||
BOTTOM_TOP = 'BT',
|
||||
// (undocumented)
|
||||
LEFT_RIGHT = 'LR',
|
||||
// (undocumented)
|
||||
RIGHT_LEFT = 'RL',
|
||||
// (undocumented)
|
||||
TOP_BOTTOM = 'TB',
|
||||
}
|
||||
|
||||
@@ -387,20 +381,6 @@ export type DismissbleBannerClassKey =
|
||||
// @public (undocumented)
|
||||
export function DocsIcon(props: IconComponentProps): JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EdgeProperties" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
type EdgeProperties = {
|
||||
label?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
labeloffset?: number;
|
||||
labelpos?: LabelPosition;
|
||||
minlen?: number;
|
||||
weight?: number;
|
||||
[customKey: string]: any;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EmailIcon" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -520,18 +500,6 @@ export type GaugeClassKey = 'root' | 'overlay' | 'circle' | 'colorUnknown';
|
||||
// @public (undocumented)
|
||||
export function GitHubIcon(props: IconComponentProps): JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "GraphEdge" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
type GraphEdge<T = CustomType> = DependencyEdge<T> &
|
||||
dagre_2.GraphEdge &
|
||||
EdgeProperties;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "GraphNode" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
type GraphNode<T = CustomType> = dagre_2.Node<DependencyNode<T>>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "GroupIcon" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -738,7 +706,7 @@ export type ItemCardHeaderProps = Partial<WithStyles<typeof styles_2>> & {
|
||||
|
||||
// Warning: (ae-missing-release-tag) "LabelPosition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
enum LabelPosition {
|
||||
// (undocumented)
|
||||
CENTER = 'c',
|
||||
@@ -888,37 +856,43 @@ export function Progress(
|
||||
|
||||
// Warning: (ae-missing-release-tag) "Ranker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
enum Ranker {
|
||||
// (undocumented)
|
||||
LONGEST_PATH = 'longest-path',
|
||||
// (undocumented)
|
||||
NETWORK_SIMPLEX = 'network-simplex',
|
||||
// (undocumented)
|
||||
TIGHT_TREE = 'tight-tree',
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RenderLabelFunction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
type RenderLabelFunction = (props: RenderLabelProps<any>) => React.ReactNode;
|
||||
// @public
|
||||
type RenderLabelFunction<T = {}> = (
|
||||
props: RenderLabelProps<T>,
|
||||
) => React_2.ReactNode;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RenderLabelProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
|
||||
//
|
||||
// @public (undocumented)
|
||||
type RenderLabelProps<T = CustomType> = {
|
||||
// @public
|
||||
type RenderLabelProps<T = unknown> = {
|
||||
edge: DependencyEdge<T>;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RenderNodeFunction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
|
||||
//
|
||||
// @public (undocumented)
|
||||
type RenderNodeFunction = (props: RenderNodeProps<any>) => React.ReactNode;
|
||||
// @public
|
||||
type RenderNodeFunction<T = {}> = (
|
||||
props: RenderNodeProps<T>,
|
||||
) => React_2.ReactNode;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RenderNodeProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
|
||||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
|
||||
//
|
||||
// @public (undocumented)
|
||||
type RenderNodeProps<T = CustomType> = {
|
||||
// @public
|
||||
type RenderNodeProps<T = unknown> = {
|
||||
node: DependencyNode<T>;
|
||||
};
|
||||
|
||||
@@ -2498,6 +2472,8 @@ export type WarningPanelClassKey =
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/components/DependencyGraph/types.d.ts:14:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/core-components" does not have an export "DependencyNode"
|
||||
// src/components/DependencyGraph/types.d.ts:18:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/core-components" does not have an export "DependencyNode"
|
||||
// src/components/TabbedLayout/RoutedTabs.d.ts:9:5 - (ae-forgotten-export) The symbol "SubRoute" needs to be exported by the entry point index.d.ts
|
||||
// src/components/Table/Table.d.ts:19:5 - (ae-forgotten-export) The symbol "SelectedFilters" needs to be exported by the entry point index.d.ts
|
||||
// src/layout/ErrorBoundary/ErrorBoundary.d.ts:7:5 - (ae-forgotten-export) The symbol "SlackChannel" needs to be exported by the entry point index.d.ts
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"@types/dagre": "^0.7.44",
|
||||
"@types/react": "*",
|
||||
"@types/react-sparklines": "^1.7.0",
|
||||
"@types/react-text-truncate": "^0.14.0",
|
||||
@@ -79,6 +78,7 @@
|
||||
"@types/d3-selection": "^2.0.0",
|
||||
"@types/d3-shape": "^3.0.1",
|
||||
"@types/d3-zoom": "^2.0.0",
|
||||
"@types/dagre": "^0.7.44",
|
||||
"@types/google-protobuf": "^3.7.2",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32",
|
||||
|
||||
@@ -29,39 +29,148 @@ import {
|
||||
Ranker,
|
||||
RenderNodeFunction,
|
||||
RenderLabelFunction,
|
||||
GraphEdge,
|
||||
GraphNode,
|
||||
LabelPosition,
|
||||
} from './types';
|
||||
import { Node } from './Node';
|
||||
import { Edge } from './Edge';
|
||||
import { Edge, GraphEdge } from './Edge';
|
||||
import { ARROW_MARKER_ID } from './constants';
|
||||
|
||||
export type DependencyGraphProps = React.SVGProps<SVGSVGElement> & {
|
||||
edges: DependencyEdge[];
|
||||
nodes: DependencyNode[];
|
||||
/**
|
||||
* Properties of {@link DependencyGraph}
|
||||
*
|
||||
* @remarks
|
||||
* <NodeData> and <EdgeData> are useful when rendering custom or edge labels
|
||||
*/
|
||||
export interface DependencyGraphProps<NodeData, EdgeData>
|
||||
extends React.SVGProps<SVGSVGElement> {
|
||||
/**
|
||||
* Edges of graph
|
||||
*/
|
||||
edges: DependencyEdge<EdgeData>[];
|
||||
/**
|
||||
* Nodes of Graph
|
||||
*/
|
||||
nodes: DependencyNode<NodeData>[];
|
||||
/**
|
||||
* Graph {@link DependencyGraphTypes.Direction | direction}
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Default: `DependencyGraphTypes.Direction.TOP_BOTTOM`
|
||||
*/
|
||||
direction?: Direction;
|
||||
/**
|
||||
* Node {@link DependencyGraphTypes.Alignment | alignment}
|
||||
*/
|
||||
align?: Alignment;
|
||||
/**
|
||||
* Margin between nodes on each rank
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Default: 50
|
||||
*/
|
||||
nodeMargin?: number;
|
||||
/**
|
||||
* Margin between edges
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Default: 10
|
||||
*/
|
||||
edgeMargin?: number;
|
||||
/**
|
||||
* Margin between each rank
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Default: 50
|
||||
*/
|
||||
rankMargin?: number;
|
||||
/**
|
||||
* Margin on left and right of whole graph
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Default: 0
|
||||
*/
|
||||
paddingX?: number;
|
||||
/**
|
||||
* Margin on top and bottom of whole graph
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Default: 0
|
||||
*/
|
||||
paddingY?: number;
|
||||
/**
|
||||
* Heuristic used to find set of edges that will make graph acyclic
|
||||
*/
|
||||
acyclicer?: 'greedy';
|
||||
/**
|
||||
* {@link DependencyGraphTypes.Ranker | Algorithm} used to rank nodes
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Default: `DependencyGraphTypes.Ranker.NETWORK_SIMPLEX`
|
||||
*/
|
||||
ranker?: Ranker;
|
||||
/**
|
||||
* {@link DependencyGraphTypes.LabelPosition | Position} of label in relation to edge
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Default: `DependencyGraphTypes.LabelPosition.RIGHT`
|
||||
*/
|
||||
labelPosition?: LabelPosition;
|
||||
/**
|
||||
* How much to move label away from edge
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Applies only when {@link DependencyGraphProps.labelPosition} is `DependencyGraphTypes.LabelPosition.LEFT` or
|
||||
* `DependencyGraphTypes.LabelPosition.RIGHT`
|
||||
*/
|
||||
labelOffset?: number;
|
||||
/**
|
||||
* Minimum number of ranks to keep between connected nodes
|
||||
*/
|
||||
edgeRanks?: number;
|
||||
/**
|
||||
* Weight applied to edges in graph
|
||||
*/
|
||||
edgeWeight?: number;
|
||||
renderNode?: RenderNodeFunction;
|
||||
renderLabel?: RenderLabelFunction;
|
||||
/**
|
||||
* Custom node rendering component
|
||||
*/
|
||||
renderNode?: RenderNodeFunction<NodeData>;
|
||||
/**
|
||||
* Custom label rendering component
|
||||
*/
|
||||
renderLabel?: RenderLabelFunction<EdgeData>;
|
||||
/**
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs | Defs} shared by rendered SVG to be used by
|
||||
* {@link DependencyGraphProps.renderNode} and/or {@link DependencyGraphProps.renderLabel}
|
||||
*/
|
||||
defs?: SVGDefsElement | SVGDefsElement[];
|
||||
/**
|
||||
* Controls zoom behavior of graph
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Default: `enabled`
|
||||
*/
|
||||
zoom?: 'enabled' | 'disabled' | 'enable-on-click';
|
||||
};
|
||||
}
|
||||
|
||||
const WORKSPACE_ID = 'workspace';
|
||||
|
||||
export function DependencyGraph(props: DependencyGraphProps) {
|
||||
/**
|
||||
* Graph component used to visualize relations between entities
|
||||
*/
|
||||
export function DependencyGraph<NodeData, EdgeData>(
|
||||
props: DependencyGraphProps<NodeData, EdgeData>,
|
||||
) {
|
||||
const {
|
||||
edges,
|
||||
nodes,
|
||||
@@ -88,7 +197,7 @@ export function DependencyGraph(props: DependencyGraphProps) {
|
||||
const [containerWidth, setContainerWidth] = React.useState<number>(100);
|
||||
const [containerHeight, setContainerHeight] = React.useState<number>(100);
|
||||
|
||||
const graph = React.useRef<dagre.graphlib.Graph<{}>>(
|
||||
const graph = React.useRef<dagre.graphlib.Graph<DependencyNode<NodeData>>>(
|
||||
new dagre.graphlib.Graph(),
|
||||
);
|
||||
const [graphWidth, setGraphWidth] = React.useState<number>(
|
||||
@@ -256,13 +365,13 @@ export function DependencyGraph(props: DependencyGraphProps) {
|
||||
updateGraph,
|
||||
]);
|
||||
|
||||
function setNode(id: string, node: DependencyNode) {
|
||||
function setNode(id: string, node: DependencyNode<NodeData>) {
|
||||
graph.current.setNode(id, node);
|
||||
updateGraph();
|
||||
return graph.current;
|
||||
}
|
||||
|
||||
function setEdge(id: dagre.Edge, edge: DependencyEdge) {
|
||||
function setEdge(id: dagre.Edge, edge: DependencyEdge<EdgeData>) {
|
||||
graph.current.setEdge(id, edge);
|
||||
updateGraph();
|
||||
return graph.current;
|
||||
@@ -303,7 +412,7 @@ export function DependencyGraph(props: DependencyGraphProps) {
|
||||
viewBox={`0 0 ${graphWidth} ${graphHeight}`}
|
||||
>
|
||||
{graphEdges.map(e => {
|
||||
const edge = graph.current.edge(e) as GraphEdge;
|
||||
const edge = graph.current.edge(e) as GraphEdge<EdgeData>;
|
||||
if (!edge) return null;
|
||||
return (
|
||||
<Edge
|
||||
@@ -316,7 +425,7 @@ export function DependencyGraph(props: DependencyGraphProps) {
|
||||
);
|
||||
})}
|
||||
{graphNodes.map((id: string) => {
|
||||
const node = graph.current.node(id) as GraphNode;
|
||||
const node = graph.current.node(id);
|
||||
if (!node) return null;
|
||||
return (
|
||||
<Node
|
||||
|
||||
@@ -23,6 +23,10 @@ const fromNode = 'node';
|
||||
const toNode = 'other-node';
|
||||
|
||||
const edge = {
|
||||
points: [
|
||||
{ x: 10, y: 20 },
|
||||
{ x: 20, y: 20 },
|
||||
],
|
||||
from: fromNode,
|
||||
to: toNode,
|
||||
};
|
||||
@@ -38,10 +42,6 @@ const renderElement = jest.fn((props: RenderLabelProps) => (
|
||||
));
|
||||
|
||||
const minProps = {
|
||||
points: [
|
||||
{ x: 10, y: 20 },
|
||||
{ x: 20, y: 20 },
|
||||
],
|
||||
id,
|
||||
setEdge,
|
||||
renderElement,
|
||||
|
||||
@@ -20,13 +20,26 @@ import isFinite from 'lodash/isFinite';
|
||||
import makeStyles from '@material-ui/core/styles/makeStyles';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import {
|
||||
GraphEdge,
|
||||
RenderLabelProps,
|
||||
RenderLabelFunction,
|
||||
DependencyEdge,
|
||||
LabelPosition,
|
||||
} from './types';
|
||||
import { ARROW_MARKER_ID, EDGE_TEST_ID, LABEL_TEST_ID } from './constants';
|
||||
import { DefaultLabel } from './DefaultLabel';
|
||||
import dagre from 'dagre';
|
||||
|
||||
/* Based on: https://github.com/dagrejs/dagre/wiki#configuring-the-layout */
|
||||
export type EdgeProperties = {
|
||||
label?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
labeloffset?: number;
|
||||
labelpos?: LabelPosition;
|
||||
minlen?: number;
|
||||
weight?: number;
|
||||
};
|
||||
export type GraphEdge<T> = DependencyEdge<T> & dagre.GraphEdge & EdgeProperties;
|
||||
|
||||
export type DependencyGraphEdgeClassKey = 'path' | 'label';
|
||||
|
||||
@@ -47,14 +60,19 @@ const useStyles = makeStyles(
|
||||
|
||||
type EdgePoint = dagre.GraphEdge['points'][0];
|
||||
|
||||
export type EdgeComponentProps<T = any> = {
|
||||
export type EdgeComponentProps<T = unknown> = {
|
||||
id: dagre.Edge;
|
||||
edge: GraphEdge<T>;
|
||||
render?: RenderLabelFunction;
|
||||
setEdge: (id: dagre.Edge, edge: DependencyEdge) => dagre.graphlib.Graph<{}>;
|
||||
render?: RenderLabelFunction<T>;
|
||||
setEdge: (
|
||||
id: dagre.Edge,
|
||||
edge: DependencyEdge<T>,
|
||||
) => dagre.graphlib.Graph<{}>;
|
||||
};
|
||||
|
||||
const renderDefault = (props: RenderLabelProps) => <DefaultLabel {...props} />;
|
||||
const renderDefault = (props: RenderLabelProps<unknown>) => (
|
||||
<DefaultLabel {...props} />
|
||||
);
|
||||
|
||||
const createPath = d3Shape
|
||||
.line<EdgePoint>()
|
||||
@@ -62,13 +80,14 @@ const createPath = d3Shape
|
||||
.y(d => d.y)
|
||||
.curve(d3Shape.curveMonotoneX);
|
||||
|
||||
export function Edge({
|
||||
export function Edge<EdgeData>({
|
||||
render = renderDefault,
|
||||
setEdge,
|
||||
id,
|
||||
edge,
|
||||
}: EdgeComponentProps) {
|
||||
const { x = 0, y = 0, width, height, points, ...labelProps } = edge;
|
||||
}: EdgeComponentProps<EdgeData>) {
|
||||
const { x = 0, y = 0, width, height, points } = edge;
|
||||
const labelProps: DependencyEdge<EdgeData> = edge;
|
||||
const classes = useStyles();
|
||||
|
||||
const labelRef = React.useRef<SVGGElement>(null);
|
||||
|
||||
@@ -20,21 +20,16 @@ import { render } from '@testing-library/react';
|
||||
import { Node } from './Node';
|
||||
import { RenderNodeProps } from './types';
|
||||
|
||||
const node = { id: 'abc' };
|
||||
const node = { id: 'abc', x: 0, y: 0, width: 0, height: 0 };
|
||||
const setNode = jest.fn(() => new dagre.graphlib.Graph());
|
||||
const renderElement = jest.fn((props: RenderNodeProps) => (
|
||||
<text>{props.node.id}</text>
|
||||
));
|
||||
|
||||
const minProps = {
|
||||
id: node.id,
|
||||
node,
|
||||
setNode,
|
||||
render: renderElement,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
|
||||
describe('<Node />', () => {
|
||||
@@ -50,7 +45,7 @@ describe('<Node />', () => {
|
||||
|
||||
it('renders the supplied element', () => {
|
||||
const { getByText } = render(<Node {...minProps} />);
|
||||
expect(getByText(minProps.id)).toBeInTheDocument();
|
||||
expect(getByText(minProps.node.id)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('passes down node properties to the render method', () => {
|
||||
@@ -62,13 +57,13 @@ describe('<Node />', () => {
|
||||
|
||||
it('calls setNode with node ID and actual size after rendering', () => {
|
||||
const { getByText } = render(<Node {...minProps} />);
|
||||
expect(getByText(minProps.id)).toBeInTheDocument();
|
||||
expect(getByText(minProps.node.id)).toBeInTheDocument();
|
||||
|
||||
// Updates the node in the graph
|
||||
expect(setNode).toHaveBeenCalledWith(node.id, {
|
||||
...node,
|
||||
height: 100,
|
||||
width: 100,
|
||||
...node,
|
||||
});
|
||||
|
||||
// Does not pass down width/height to node
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
import React from 'react';
|
||||
import makeStyles from '@material-ui/core/styles/makeStyles';
|
||||
import { DefaultNode } from './DefaultNode';
|
||||
import { RenderNodeFunction, RenderNodeProps, GraphNode } from './types';
|
||||
import { RenderNodeFunction, RenderNodeProps, DependencyNode } from './types';
|
||||
import { NODE_TEST_ID } from './constants';
|
||||
import dagre from 'dagre';
|
||||
|
||||
export type DependencyGraphNodeClassKey = 'node';
|
||||
|
||||
@@ -31,20 +32,23 @@ const useStyles = makeStyles(
|
||||
{ name: 'BackstageDependencyGraphNode' },
|
||||
);
|
||||
|
||||
export type NodeComponentProps<T = any> = {
|
||||
export type GraphNode<T> = dagre.Node<DependencyNode<T>>;
|
||||
|
||||
export type NodeComponentProps<T> = {
|
||||
node: GraphNode<T>;
|
||||
render?: RenderNodeFunction;
|
||||
render?: RenderNodeFunction<T>;
|
||||
setNode: dagre.graphlib.Graph['setNode'];
|
||||
};
|
||||
|
||||
const renderDefault = (props: RenderNodeProps) => <DefaultNode {...props} />;
|
||||
|
||||
export function Node({
|
||||
export function Node<T>({
|
||||
render = renderDefault,
|
||||
setNode,
|
||||
node,
|
||||
}: NodeComponentProps) {
|
||||
const { width, height, x = 0, y = 0, ...nodeProps } = node;
|
||||
}: NodeComponentProps<T>) {
|
||||
const { width, height, x = 0, y = 0 } = node;
|
||||
const nodeProps: DependencyNode<T> = node;
|
||||
const classes = useStyles();
|
||||
const nodeRef = React.useRef<SVGGElement | null>(null);
|
||||
|
||||
|
||||
@@ -14,73 +14,132 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import dagre from 'dagre';
|
||||
/**
|
||||
* Types used to customize and provide data to {@link DependencyGraph}
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
type CustomType = { [customKey: string]: any };
|
||||
import React from 'react';
|
||||
|
||||
/* Edges */
|
||||
export type DependencyEdge<T = CustomType> = T & {
|
||||
/**
|
||||
* Edge of {@link DependencyGraph}
|
||||
*/
|
||||
export type DependencyEdge<T = {}> = T & {
|
||||
/**
|
||||
* ID of {@link DependencyNode} from where the Edge start
|
||||
*/
|
||||
from: string;
|
||||
/**
|
||||
* ID of {@link DependencyNode} to where the Edge goes to
|
||||
*/
|
||||
to: string;
|
||||
/**
|
||||
* Label assigned and rendered with the Edge
|
||||
*/
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export type GraphEdge<T = CustomType> = DependencyEdge<T> &
|
||||
dagre.GraphEdge &
|
||||
EdgeProperties;
|
||||
/**
|
||||
* Properties of {@link DependencyGraphTypes.RenderLabelFunction} for {@link DependencyGraphTypes.DependencyEdge}
|
||||
*/
|
||||
export type RenderLabelProps<T = unknown> = { edge: DependencyEdge<T> };
|
||||
|
||||
export type RenderLabelProps<T = CustomType> = { edge: DependencyEdge<T> };
|
||||
|
||||
export type RenderLabelFunction = (
|
||||
props: RenderLabelProps<any>,
|
||||
/**
|
||||
* Custom React component for edge labels
|
||||
*/
|
||||
export type RenderLabelFunction<T = {}> = (
|
||||
props: RenderLabelProps<T>,
|
||||
) => React.ReactNode;
|
||||
|
||||
/* Nodes */
|
||||
export type DependencyNode<T = CustomType> = T & {
|
||||
/**
|
||||
* Node of {@link DependencyGraph}
|
||||
*/
|
||||
export type DependencyNode<T = {}> = T & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type GraphNode<T = CustomType> = dagre.Node<DependencyNode<T>>;
|
||||
/**
|
||||
* Properties of {@link DependencyGraphTypes.RenderNodeFunction} for {@link DependencyGraphTypes.DependencyNode}
|
||||
*/
|
||||
export type RenderNodeProps<T = unknown> = { node: DependencyNode<T> };
|
||||
|
||||
export type RenderNodeProps<T = CustomType> = { node: DependencyNode<T> };
|
||||
|
||||
export type RenderNodeFunction = (
|
||||
props: RenderNodeProps<any>,
|
||||
/**
|
||||
* Custom React component for graph {@link DependencyGraphTypes.DependencyNode}
|
||||
*/
|
||||
export type RenderNodeFunction<T = {}> = (
|
||||
props: RenderNodeProps<T>,
|
||||
) => React.ReactNode;
|
||||
|
||||
/* Based on: https://github.com/dagrejs/dagre/wiki#configuring-the-layout */
|
||||
|
||||
export type EdgeProperties = {
|
||||
label?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
labeloffset?: number;
|
||||
labelpos?: LabelPosition;
|
||||
minlen?: number;
|
||||
weight?: number;
|
||||
[customKey: string]: any;
|
||||
};
|
||||
|
||||
/**
|
||||
* Graph direction
|
||||
*/
|
||||
export enum Direction {
|
||||
/**
|
||||
* Top to Bottom
|
||||
*/
|
||||
TOP_BOTTOM = 'TB',
|
||||
/**
|
||||
* Bottom to Top
|
||||
*/
|
||||
BOTTOM_TOP = 'BT',
|
||||
/**
|
||||
* Left to Right
|
||||
*/
|
||||
LEFT_RIGHT = 'LR',
|
||||
/**
|
||||
* Right to Left
|
||||
*/
|
||||
RIGHT_LEFT = 'RL',
|
||||
}
|
||||
|
||||
/**
|
||||
* Node alignment
|
||||
*/
|
||||
export enum Alignment {
|
||||
/**
|
||||
* Up Left
|
||||
*/
|
||||
UP_LEFT = 'UL',
|
||||
/**
|
||||
* Up Right
|
||||
*/
|
||||
UP_RIGHT = 'UR',
|
||||
/**
|
||||
* Down Left
|
||||
*/
|
||||
DOWN_LEFT = 'DL',
|
||||
/**
|
||||
* Down Right
|
||||
*/
|
||||
DOWN_RIGHT = 'DR',
|
||||
}
|
||||
|
||||
/**
|
||||
* Algorithm used to rand nodes in graph
|
||||
*/
|
||||
export enum Ranker {
|
||||
/**
|
||||
* {@link https://en.wikipedia.org/wiki/Network_simplex_algorithm | Network Simplex} algorithm
|
||||
*/
|
||||
NETWORK_SIMPLEX = 'network-simplex',
|
||||
/**
|
||||
* Tight Tree algorithm
|
||||
*/
|
||||
TIGHT_TREE = 'tight-tree',
|
||||
/**
|
||||
* Longest path algorithm
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Simplest and fastest
|
||||
*/
|
||||
LONGEST_PATH = 'longest-path',
|
||||
}
|
||||
|
||||
/**
|
||||
* Position of label in relation to the edge
|
||||
*/
|
||||
export enum LabelPosition {
|
||||
LEFT = 'l',
|
||||
RIGHT = 'r',
|
||||
|
||||
@@ -93,13 +93,23 @@ export const EntityCatalogGraphCard: ({
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type EntityEdge = DependencyGraphTypes.DependencyEdge<{
|
||||
export type EntityEdge = DependencyGraphTypes.DependencyEdge<EntityEdgeData>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityEdgeData" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export type EntityEdgeData = {
|
||||
relations: string[];
|
||||
label: 'visible';
|
||||
}>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type EntityNode = DependencyGraphTypes.DependencyNode<{
|
||||
export type EntityNode = DependencyGraphTypes.DependencyNode<EntityNodeData>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityNodeData" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export type EntityNodeData = {
|
||||
name: string;
|
||||
kind?: string;
|
||||
title?: string;
|
||||
@@ -107,7 +117,7 @@ export type EntityNode = DependencyGraphTypes.DependencyNode<{
|
||||
focused?: boolean;
|
||||
color?: 'primary' | 'secondary' | 'default';
|
||||
onClick?: MouseEventHandler<unknown>;
|
||||
}>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const EntityRelationsGraph: ({
|
||||
|
||||
@@ -31,12 +31,6 @@ describe('<CustomLabel />', () => {
|
||||
relations: [RELATION_PARENT_OF],
|
||||
from: 'from-id',
|
||||
to: 'to-id',
|
||||
id: 'id',
|
||||
x: 111,
|
||||
y: 222,
|
||||
width: 100,
|
||||
height: 25,
|
||||
points: [],
|
||||
}}
|
||||
/>
|
||||
</svg>,
|
||||
@@ -54,12 +48,6 @@ describe('<CustomLabel />', () => {
|
||||
relations: [RELATION_PARENT_OF, RELATION_CHILD_OF],
|
||||
from: 'from-id',
|
||||
to: 'to-id',
|
||||
id: 'id',
|
||||
x: 111,
|
||||
y: 222,
|
||||
width: 100,
|
||||
height: 25,
|
||||
points: [],
|
||||
}}
|
||||
/>
|
||||
</svg>,
|
||||
|
||||
@@ -17,7 +17,7 @@ import { DependencyGraphTypes } from '@backstage/core-components';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import makeStyles from '@material-ui/core/styles/makeStyles';
|
||||
import React from 'react';
|
||||
import { GraphEdge } from './types';
|
||||
import { EntityEdgeData } from './types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const useStyles = makeStyles((theme: BackstageTheme) => ({
|
||||
@@ -31,7 +31,7 @@ const useStyles = makeStyles((theme: BackstageTheme) => ({
|
||||
|
||||
export function CustomLabel({
|
||||
edge: { relations },
|
||||
}: DependencyGraphTypes.RenderLabelProps<GraphEdge>) {
|
||||
}: DependencyGraphTypes.RenderLabelProps<EntityEdgeData>) {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<text className={classes.text} textAnchor="middle">
|
||||
|
||||
@@ -36,10 +36,6 @@ describe('<CustomNode />', () => {
|
||||
name: 'name',
|
||||
namespace: 'namespace',
|
||||
id: 'kind:namespace/name',
|
||||
x: 111,
|
||||
y: 222,
|
||||
width: 100,
|
||||
height: 25,
|
||||
color: 'primary',
|
||||
}}
|
||||
/>
|
||||
@@ -59,10 +55,6 @@ describe('<CustomNode />', () => {
|
||||
name: 'name',
|
||||
namespace: 'default',
|
||||
id: 'kind:default/name',
|
||||
x: 111,
|
||||
y: 222,
|
||||
width: 100,
|
||||
height: 25,
|
||||
}}
|
||||
/>
|
||||
</svg>,
|
||||
@@ -83,10 +75,6 @@ describe('<CustomNode />', () => {
|
||||
namespace: 'namespace',
|
||||
onClick,
|
||||
id: 'kind:namespace/name',
|
||||
x: 111,
|
||||
y: 222,
|
||||
width: 100,
|
||||
height: 25,
|
||||
}}
|
||||
/>
|
||||
</svg>,
|
||||
@@ -108,10 +96,6 @@ describe('<CustomNode />', () => {
|
||||
namespace: 'namespace',
|
||||
title: 'Custom Title',
|
||||
id: 'kind:namespace/name',
|
||||
x: 111,
|
||||
y: 222,
|
||||
width: 100,
|
||||
height: 25,
|
||||
}}
|
||||
/>
|
||||
</svg>,
|
||||
|
||||
@@ -20,7 +20,7 @@ import { makeStyles } from '@material-ui/core/styles';
|
||||
import classNames from 'classnames';
|
||||
import React, { useLayoutEffect, useRef, useState } from 'react';
|
||||
import { EntityKindIcon } from './EntityKindIcon';
|
||||
import { GraphNode } from './types';
|
||||
import { EntityNodeData } from './types';
|
||||
|
||||
const useStyles = makeStyles((theme: BackstageTheme) => ({
|
||||
node: {
|
||||
@@ -65,7 +65,7 @@ export function CustomNode({
|
||||
title,
|
||||
onClick,
|
||||
},
|
||||
}: DependencyGraphTypes.RenderNodeProps<GraphNode>) {
|
||||
}: DependencyGraphTypes.RenderNodeProps<EntityNodeData>) {
|
||||
const classes = useStyles();
|
||||
const [width, setWidth] = useState(0);
|
||||
const [height, setHeight] = useState(0);
|
||||
|
||||
@@ -17,4 +17,9 @@ export { EntityRelationsGraph } from './EntityRelationsGraph';
|
||||
export { ALL_RELATION_PAIRS } from './relations';
|
||||
export type { RelationPairs } from './relations';
|
||||
export { Direction } from './types';
|
||||
export type { EntityEdge, EntityNode } from './types';
|
||||
export type {
|
||||
EntityEdgeData,
|
||||
EntityEdge,
|
||||
EntityNodeData,
|
||||
EntityNode,
|
||||
} from './types';
|
||||
|
||||
@@ -17,11 +17,9 @@ import { DependencyGraphTypes } from '@backstage/core-components';
|
||||
import { MouseEventHandler } from 'react';
|
||||
|
||||
/**
|
||||
* Edge between two entities.
|
||||
*
|
||||
* @public
|
||||
* Additional Data for entities
|
||||
*/
|
||||
export type EntityEdge = DependencyGraphTypes.DependencyEdge<{
|
||||
export type EntityEdgeData = {
|
||||
/**
|
||||
* Up to two relations that are connecting an entity.
|
||||
*/
|
||||
@@ -31,14 +29,19 @@ export type EntityEdge = DependencyGraphTypes.DependencyEdge<{
|
||||
*/
|
||||
// Not used, but has to be non empty to draw a label at all!
|
||||
label: 'visible';
|
||||
}>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Node representing an entity.
|
||||
* Edge between two entities.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EntityNode = DependencyGraphTypes.DependencyNode<{
|
||||
export type EntityEdge = DependencyGraphTypes.DependencyEdge<EntityEdgeData>;
|
||||
|
||||
/**
|
||||
* Additional data for Entity Node
|
||||
*/
|
||||
export type EntityNodeData = {
|
||||
/**
|
||||
* Name of the entity.
|
||||
*/
|
||||
@@ -68,11 +71,14 @@ export type EntityNode = DependencyGraphTypes.DependencyNode<{
|
||||
* Optional click handler.
|
||||
*/
|
||||
onClick?: MouseEventHandler<unknown>;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type GraphEdge = DependencyGraphTypes.GraphEdge<EntityEdge>;
|
||||
|
||||
export type GraphNode = DependencyGraphTypes.GraphNode<EntityNode>;
|
||||
/**
|
||||
* Node representing an entity.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EntityNode = DependencyGraphTypes.DependencyNode<EntityNodeData>;
|
||||
|
||||
/**
|
||||
* Render direction of the graph.
|
||||
|
||||
Reference in New Issue
Block a user