Merge pull request #13673 from andrmaz/feature/core-components

pr(core-components): edit dependency graph edge
This commit is contained in:
Ben Lambert
2022-10-13 11:59:10 +01:00
committed by GitHub
11 changed files with 154 additions and 14 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/core-components': patch
'@backstage/plugin-catalog-graph': patch
---
Added `curve` prop to the `DependencyGraph` component to select the type of layout
+1
View File
@@ -242,6 +242,7 @@ export interface DependencyGraphProps<NodeData, EdgeData>
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;
curve?: 'curveStepBefore' | 'curveMonotoneX';
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;
@@ -162,6 +162,14 @@ export interface DependencyGraphProps<NodeData, EdgeData>
* Default: `enabled`
*/
zoom?: 'enabled' | 'disabled' | 'enable-on-click';
/**
* A factory for curve generators addressing both lines and areas.
*
* @remarks
*
* Default: 'curveMonotoneX'
*/
curve?: 'curveStepBefore' | 'curveMonotoneX';
}
const WORKSPACE_ID = 'workspace';
@@ -194,6 +202,7 @@ export function DependencyGraph<NodeData, EdgeData>(
renderLabel,
defs,
zoom = 'enabled',
curve = 'curveMonotoneX',
...svgProps
} = props;
const theme: BackstageTheme = useTheme();
@@ -424,6 +433,7 @@ export function DependencyGraph<NodeData, EdgeData>(
setEdge={setEdge}
render={renderLabel}
edge={edge}
curve={curve}
/>
);
})}
@@ -21,6 +21,7 @@ import { RenderLabelProps } from './types';
const fromNode = 'node';
const toNode = 'other-node';
const curve: 'curveStepBefore' | 'curveMonotoneX' = 'curveMonotoneX';
const edge = {
points: [
@@ -46,6 +47,7 @@ const minProps = {
setEdge,
renderElement,
edge,
curve,
};
const label = 'label';
@@ -25,7 +25,7 @@ import {
DependencyEdge,
LabelPosition,
} from './types';
import { ARROW_MARKER_ID, EDGE_TEST_ID, LABEL_TEST_ID } from './constants';
import { EDGE_TEST_ID, LABEL_TEST_ID } from './constants';
import { DefaultLabel } from './DefaultLabel';
import dagre from 'dagre';
@@ -47,7 +47,7 @@ export type DependencyGraphEdgeClassKey = 'path' | 'label';
const useStyles = makeStyles(
(theme: BackstageTheme) => ({
path: {
strokeWidth: 2,
strokeWidth: 1,
stroke: theme.palette.textSubtle,
fill: 'none',
transition: `${theme.transitions.duration.shortest}ms`,
@@ -70,23 +70,19 @@ export type EdgeComponentProps<T = unknown> = {
id: dagre.Edge,
edge: DependencyEdge<T>,
) => dagre.graphlib.Graph<{}>;
curve: 'curveStepBefore' | 'curveMonotoneX';
};
const renderDefault = (props: RenderLabelProps<unknown>) => (
<DefaultLabel {...props} />
);
const createPath = d3Shape
.line<EdgePoint>()
.x(d => d.x)
.y(d => d.y)
.curve(d3Shape.curveMonotoneX);
export function Edge<EdgeData>({
render = renderDefault,
setEdge,
id,
edge,
curve,
}: EdgeComponentProps<EdgeData>) {
const { x = 0, y = 0, width, height, points } = edge;
const labelProps: DependencyEdge<EdgeData> = edge;
@@ -114,6 +110,16 @@ export function Edge<EdgeData>({
let path: string = '';
const createPath = React.useMemo(
() =>
d3Shape
.line<EdgePoint>()
.x(d => d.x)
.y(d => d.y)
.curve(d3Shape[curve]),
[curve],
);
if (points) {
const finitePoints = points.filter(
(point: EdgePoint) => isFinite(point.x) && isFinite(point.y),
@@ -124,12 +130,7 @@ export function Edge<EdgeData>({
return (
<>
{path && (
<path
data-testid={EDGE_TEST_ID}
className={classes.path}
markerEnd={`url(#${ARROW_MARKER_ID})`}
d={path}
/>
<path data-testid={EDGE_TEST_ID} className={classes.path} d={path} />
)}
{labelProps.label ? (
<g
+2
View File
@@ -30,6 +30,7 @@ export const CatalogGraphPage: (props: {
mergeRelations?: boolean | undefined;
direction?: Direction | undefined;
showFilters?: boolean | undefined;
curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined;
}
| undefined;
}) => JSX.Element;
@@ -120,6 +121,7 @@ export const EntityRelationsGraph: (props: {
renderLabel?:
| DependencyGraphTypes.RenderLabelFunction<EntityEdge>
| undefined;
curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined;
}) => JSX.Element;
// @public
@@ -40,6 +40,7 @@ import {
EntityRelationsGraph,
RelationPairs,
} from '../EntityRelationsGraph';
import { CurveFilter } from './CurveFilter';
import { DirectionFilter } from './DirectionFilter';
import { MaxDepthFilter } from './MaxDepthFilter';
import { SelectedKindsFilter } from './SelectedKindsFilter';
@@ -110,6 +111,7 @@ export const CatalogGraphPage = (props: {
mergeRelations?: boolean;
direction?: Direction;
showFilters?: boolean;
curve?: 'curveStepBefore' | 'curveMonotoneX';
};
}) => {
const { relationPairs = ALL_RELATION_PAIRS, initialState } = props;
@@ -130,6 +132,8 @@ export const CatalogGraphPage = (props: {
setMergeRelations,
direction,
setDirection,
curve,
setCurve,
rootEntityNames,
setRootEntityNames,
showFilters,
@@ -201,6 +205,7 @@ export const CatalogGraphPage = (props: {
relationPairs={relationPairs}
/>
<DirectionFilter value={direction} onChange={setDirection} />
<CurveFilter value={curve} onChange={setCurve} />
<SwitchFilter
value={unidirectional}
onChange={setUnidirectional}
@@ -245,6 +250,7 @@ export const CatalogGraphPage = (props: {
relationPairs={relationPairs}
className={classes.graph}
zoom="enabled"
curve={curve}
/>
</Paper>
</Grid>
@@ -0,0 +1,47 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { CurveFilter } from './CurveFilter';
describe('<CurveFilter/>', () => {
test('should display current curve label', () => {
const onChange = jest.fn();
const { getByText } = render(
<CurveFilter value="curveMonotoneX" onChange={onChange} />,
);
expect(getByText('Monotone X')).toBeInTheDocument();
});
test('should select an alternative curve factory', async () => {
const onChange = jest.fn();
const { getByText, getByTestId } = render(
<CurveFilter value="curveStepBefore" onChange={onChange} />,
);
expect(getByText('Step Before')).toBeInTheDocument();
await userEvent.click(getByTestId('select'));
await userEvent.click(getByText('Monotone X'));
await waitFor(() => {
expect(getByText('Monotone X')).toBeInTheDocument();
expect(onChange).toHaveBeenCalledWith('curveMonotoneX');
});
});
});
@@ -0,0 +1,49 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Select } from '@backstage/core-components';
import { Box } from '@material-ui/core';
import React, { useCallback } from 'react';
type Curve = 'curveStepBefore' | 'curveMonotoneX';
const CURVE_DISPLAY_NAMES: Record<Curve, string> = {
curveMonotoneX: 'Monotone X',
curveStepBefore: 'Step Before',
};
export type Props = {
value: Curve;
onChange: (value: 'curveStepBefore' | 'curveMonotoneX') => void;
};
const curves: Array<Curve> = ['curveMonotoneX', 'curveStepBefore'];
export const CurveFilter = ({ value, onChange }: Props) => {
const handleChange = useCallback(v => onChange(v as Curve), [onChange]);
return (
<Box pb={1} pt={1}>
<Select
label="Curve"
selected={value}
items={curves.map(v => ({
label: CURVE_DISPLAY_NAMES[v],
value: v,
}))}
onChange={handleChange}
/>
</Box>
);
};
@@ -46,6 +46,10 @@ export type CatalogGraphPageValue = {
setMergeRelations: Dispatch<React.SetStateAction<boolean>>;
direction: Direction;
setDirection: Dispatch<React.SetStateAction<Direction>>;
curve: 'curveStepBefore' | 'curveMonotoneX';
setCurve: Dispatch<
React.SetStateAction<'curveStepBefore' | 'curveMonotoneX'>
>;
showFilters: boolean;
toggleShowFilters: DispatchWithoutAction;
};
@@ -62,6 +66,7 @@ export function useCatalogGraphPage({
mergeRelations?: boolean;
direction?: Direction;
showFilters?: boolean;
curve?: 'curveStepBefore' | 'curveMonotoneX';
};
}): CatalogGraphPageValue {
const location = useLocation();
@@ -77,6 +82,7 @@ export function useCatalogGraphPage({
mergeRelations?: string[] | string;
direction?: string[] | Direction;
showFilters?: string[] | string;
curve?: string[] | 'curveStepBefore' | 'curveMonotoneX';
},
[location.search],
);
@@ -122,6 +128,11 @@ export function useCatalogGraphPage({
? query.direction
: initialState?.direction ?? Direction.LEFT_RIGHT,
);
const [curve, setCurve] = useState<'curveStepBefore' | 'curveMonotoneX'>(() =>
typeof query.curve === 'string'
? query.curve
: initialState?.curve ?? 'curveMonotoneX',
);
const [showFilters, setShowFilters] = useState<boolean>(() =>
typeof query.showFilters === 'string'
? query.showFilters === 'true'
@@ -249,6 +260,8 @@ export function useCatalogGraphPage({
setMergeRelations,
direction,
setDirection,
curve,
setCurve,
showFilters,
toggleShowFilters,
};
@@ -82,6 +82,7 @@ export const EntityRelationsGraph = (props: {
zoom?: 'enabled' | 'disabled' | 'enable-on-click';
renderNode?: DependencyGraphTypes.RenderNodeFunction<EntityNode>;
renderLabel?: DependencyGraphTypes.RenderLabelFunction<EntityEdge>;
curve?: 'curveStepBefore' | 'curveMonotoneX';
}) => {
const {
rootEntityNames,
@@ -97,6 +98,7 @@ export const EntityRelationsGraph = (props: {
zoom = 'enabled',
renderNode,
renderLabel,
curve,
} = props;
const theme = useTheme();
@@ -143,6 +145,7 @@ export const EntityRelationsGraph = (props: {
labelPosition={DependencyGraphTypes.LabelPosition.RIGHT}
labelOffset={theme.spacing(1)}
zoom={zoom}
curve={curve}
/>
)}
</div>