feat(curve): add curve filter to graph page
Signed-off-by: andrmaz <60042277+andrmaz@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/plugin-catalog-graph': patch
|
||||
---
|
||||
|
||||
Added select to toggle the curve factory of the dependency graph
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
RenderNodeFunction,
|
||||
RenderLabelFunction,
|
||||
LabelPosition,
|
||||
Curve,
|
||||
} from './types';
|
||||
import { Node } from './Node';
|
||||
import { Edge, GraphEdge } from './Edge';
|
||||
@@ -162,6 +163,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?: Curve;
|
||||
}
|
||||
|
||||
const WORKSPACE_ID = 'workspace';
|
||||
@@ -194,6 +203,7 @@ export function DependencyGraph<NodeData, EdgeData>(
|
||||
renderLabel,
|
||||
defs,
|
||||
zoom = 'enabled',
|
||||
curve = 'curveMonotoneX',
|
||||
...svgProps
|
||||
} = props;
|
||||
const theme: BackstageTheme = useTheme();
|
||||
@@ -424,6 +434,7 @@ export function DependencyGraph<NodeData, EdgeData>(
|
||||
setEdge={setEdge}
|
||||
render={renderLabel}
|
||||
edge={edge}
|
||||
curve={curve}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
RenderLabelFunction,
|
||||
DependencyEdge,
|
||||
LabelPosition,
|
||||
Curve,
|
||||
} from './types';
|
||||
import { EDGE_TEST_ID, LABEL_TEST_ID } from './constants';
|
||||
import { DefaultLabel } from './DefaultLabel';
|
||||
@@ -70,23 +71,19 @@ export type EdgeComponentProps<T = unknown> = {
|
||||
id: dagre.Edge,
|
||||
edge: DependencyEdge<T>,
|
||||
) => dagre.graphlib.Graph<{}>;
|
||||
curve: Curve;
|
||||
};
|
||||
|
||||
const renderDefault = (props: RenderLabelProps<unknown>) => (
|
||||
<DefaultLabel {...props} />
|
||||
);
|
||||
|
||||
const createPath = d3Shape
|
||||
.line<EdgePoint>()
|
||||
.x(d => d.x)
|
||||
.y(d => d.y)
|
||||
.curve(d3Shape.curveStepBefore);
|
||||
|
||||
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 +111,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),
|
||||
|
||||
@@ -75,6 +75,12 @@ export type RenderNodeFunction<T = {}> = (
|
||||
props: RenderNodeProps<T>,
|
||||
) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* @see {@link @types/d3-shape/index.d.ts#curveMonotoneX}
|
||||
* @see {@link @types/d3-shape/index.d.ts#curveStepBefore}
|
||||
*/
|
||||
export type Curve = 'curveStepBefore' | 'curveMonotoneX';
|
||||
|
||||
/**
|
||||
* Graph direction
|
||||
*
|
||||
|
||||
@@ -35,11 +35,13 @@ import React, { MouseEvent, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import {
|
||||
ALL_RELATION_PAIRS,
|
||||
Curve,
|
||||
Direction,
|
||||
EntityNode,
|
||||
EntityRelationsGraph,
|
||||
RelationPairs,
|
||||
} from '../EntityRelationsGraph';
|
||||
import { CurveFilter } from './CurveFilter';
|
||||
import { DirectionFilter } from './DirectionFilter';
|
||||
import { MaxDepthFilter } from './MaxDepthFilter';
|
||||
import { SelectedKindsFilter } from './SelectedKindsFilter';
|
||||
@@ -110,6 +112,7 @@ export const CatalogGraphPage = (props: {
|
||||
mergeRelations?: boolean;
|
||||
direction?: Direction;
|
||||
showFilters?: boolean;
|
||||
curve?: Curve;
|
||||
};
|
||||
}) => {
|
||||
const { relationPairs = ALL_RELATION_PAIRS, initialState } = props;
|
||||
@@ -130,6 +133,8 @@ export const CatalogGraphPage = (props: {
|
||||
setMergeRelations,
|
||||
direction,
|
||||
setDirection,
|
||||
curve,
|
||||
setCurve,
|
||||
rootEntityNames,
|
||||
setRootEntityNames,
|
||||
showFilters,
|
||||
@@ -201,6 +206,7 @@ export const CatalogGraphPage = (props: {
|
||||
relationPairs={relationPairs}
|
||||
/>
|
||||
<DirectionFilter value={direction} onChange={setDirection} />
|
||||
<CurveFilter value={curve} onChange={setCurve} />
|
||||
<SwitchFilter
|
||||
value={unidirectional}
|
||||
onChange={setUnidirectional}
|
||||
@@ -245,6 +251,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).toBeCalledWith('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';
|
||||
import { Curve } from '../EntityRelationsGraph';
|
||||
|
||||
const CURVE_DISPLAY_NAMES: Record<Curve, string> = {
|
||||
curveMonotoneX: 'Monotone X',
|
||||
curveStepBefore: 'Step Before',
|
||||
};
|
||||
|
||||
export type Props = {
|
||||
value: Curve;
|
||||
onChange: (value: Curve) => void;
|
||||
};
|
||||
|
||||
const curves: 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>
|
||||
);
|
||||
};
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { Direction } from '../EntityRelationsGraph';
|
||||
import { Direction, Curve } from '../EntityRelationsGraph';
|
||||
|
||||
export type CatalogGraphPageValue = {
|
||||
rootEntityNames: CompoundEntityRef[];
|
||||
@@ -46,6 +46,8 @@ export type CatalogGraphPageValue = {
|
||||
setMergeRelations: Dispatch<React.SetStateAction<boolean>>;
|
||||
direction: Direction;
|
||||
setDirection: Dispatch<React.SetStateAction<Direction>>;
|
||||
curve: Curve;
|
||||
setCurve: Dispatch<React.SetStateAction<Curve>>;
|
||||
showFilters: boolean;
|
||||
toggleShowFilters: DispatchWithoutAction;
|
||||
};
|
||||
@@ -62,6 +64,7 @@ export function useCatalogGraphPage({
|
||||
mergeRelations?: boolean;
|
||||
direction?: Direction;
|
||||
showFilters?: boolean;
|
||||
curve?: Curve;
|
||||
};
|
||||
}): CatalogGraphPageValue {
|
||||
const location = useLocation();
|
||||
@@ -77,6 +80,7 @@ export function useCatalogGraphPage({
|
||||
mergeRelations?: string[] | string;
|
||||
direction?: string[] | Direction;
|
||||
showFilters?: string[] | string;
|
||||
curve?: string[] | Curve;
|
||||
},
|
||||
[location.search],
|
||||
);
|
||||
@@ -122,6 +126,11 @@ export function useCatalogGraphPage({
|
||||
? query.direction
|
||||
: initialState?.direction ?? Direction.LEFT_RIGHT,
|
||||
);
|
||||
const [curve, setCurve] = useState<Curve>(() =>
|
||||
typeof query.curve === 'string'
|
||||
? query.curve
|
||||
: initialState?.curve ?? 'curveMonotoneX',
|
||||
);
|
||||
const [showFilters, setShowFilters] = useState<boolean>(() =>
|
||||
typeof query.showFilters === 'string'
|
||||
? query.showFilters === 'true'
|
||||
@@ -249,6 +258,8 @@ export function useCatalogGraphPage({
|
||||
setMergeRelations,
|
||||
direction,
|
||||
setDirection,
|
||||
curve,
|
||||
setCurve,
|
||||
showFilters,
|
||||
toggleShowFilters,
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ import React, { MouseEvent, useEffect, useMemo } from 'react';
|
||||
import { CustomLabel } from './CustomLabel';
|
||||
import { CustomNode } from './CustomNode';
|
||||
import { ALL_RELATION_PAIRS, RelationPairs } from './relations';
|
||||
import { Direction, EntityEdge, EntityNode } from './types';
|
||||
import { Curve, Direction, EntityEdge, EntityNode } from './types';
|
||||
import { useEntityRelationNodesAndEdges } from './useEntityRelationNodesAndEdges';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
@@ -82,6 +82,7 @@ export const EntityRelationsGraph = (props: {
|
||||
zoom?: 'enabled' | 'disabled' | 'enable-on-click';
|
||||
renderNode?: DependencyGraphTypes.RenderNodeFunction<EntityNode>;
|
||||
renderLabel?: DependencyGraphTypes.RenderLabelFunction<EntityEdge>;
|
||||
curve?: Curve;
|
||||
}) => {
|
||||
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>
|
||||
|
||||
@@ -22,4 +22,5 @@ export type {
|
||||
EntityEdge,
|
||||
EntityNodeData,
|
||||
EntityNode,
|
||||
Curve,
|
||||
} from './types';
|
||||
|
||||
@@ -108,3 +108,9 @@ export enum Direction {
|
||||
*/
|
||||
RIGHT_LEFT = 'RL',
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link @types/d3-shape/index.d.ts#curveMonotoneX}
|
||||
* @see {@link @types/d3-shape/index.d.ts#curveStepBefore}
|
||||
*/
|
||||
export type Curve = 'curveStepBefore' | 'curveMonotoneX';
|
||||
|
||||
Reference in New Issue
Block a user