From 401c10eba0eab2b72e2863771fac0acac7d1f4b4 Mon Sep 17 00:00:00 2001 From: andrmaz <60042277+andrmaz@users.noreply.github.com> Date: Tue, 13 Sep 2022 22:28:17 +0200 Subject: [PATCH 1/9] feat(edge): attach groups hierarchy arrows to the right side of their parent boxes Signed-off-by: andrmaz <60042277+andrmaz@users.noreply.github.com> --- .../src/components/DependencyGraph/Edge.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/core-components/src/components/DependencyGraph/Edge.tsx b/packages/core-components/src/components/DependencyGraph/Edge.tsx index cc6a044beb..72a7547cda 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.tsx @@ -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`, @@ -80,7 +80,7 @@ const createPath = d3Shape .line() .x(d => d.x) .y(d => d.y) - .curve(d3Shape.curveMonotoneX); + .curve(d3Shape.curveStepBefore); export function Edge({ render = renderDefault, @@ -124,12 +124,7 @@ export function Edge({ return ( <> {path && ( - + )} {labelProps.label ? ( Date: Tue, 13 Sep 2022 22:43:44 +0200 Subject: [PATCH 2/9] feat(changelog): add changeset describing the change Signed-off-by: andrmaz <60042277+andrmaz@users.noreply.github.com> --- .changeset/pink-pumas-double.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pink-pumas-double.md diff --git a/.changeset/pink-pumas-double.md b/.changeset/pink-pumas-double.md new file mode 100644 index 0000000000..ca4d40967f --- /dev/null +++ b/.changeset/pink-pumas-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Attach groups hierarchy arrows to the right side of their parent boxes From bde1e8c8e2dd3de13aa48f36f7dc7f57cd13e43c Mon Sep 17 00:00:00 2001 From: andrmaz <60042277+andrmaz@users.noreply.github.com> Date: Thu, 6 Oct 2022 00:00:46 +0200 Subject: [PATCH 3/9] feat(curve): add curve filter to graph page Signed-off-by: andrmaz <60042277+andrmaz@users.noreply.github.com> --- .changeset/clever-clocks-drive.md | 6 +++ .../DependencyGraph/DependencyGraph.tsx | 11 +++++ .../src/components/DependencyGraph/Edge.tsx | 19 ++++--- .../src/components/DependencyGraph/types.ts | 6 +++ .../CatalogGraphPage/CatalogGraphPage.tsx | 7 +++ .../CatalogGraphPage/CurveFilter.test.tsx | 47 ++++++++++++++++++ .../CatalogGraphPage/CurveFilter.tsx | 49 +++++++++++++++++++ .../CatalogGraphPage/useCatalogGraphPage.ts | 13 ++++- .../EntityRelationsGraph.tsx | 5 +- .../components/EntityRelationsGraph/index.ts | 1 + .../components/EntityRelationsGraph/types.ts | 6 +++ 11 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 .changeset/clever-clocks-drive.md create mode 100644 plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx create mode 100644 plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx diff --git a/.changeset/clever-clocks-drive.md b/.changeset/clever-clocks-drive.md new file mode 100644 index 0000000000..d91a8ee53f --- /dev/null +++ b/.changeset/clever-clocks-drive.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-catalog-graph': patch +--- + +Added select to toggle the curve factory of the dependency graph diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx index 9f447f80cc..3457a83d7d 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx @@ -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 * 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( renderLabel, defs, zoom = 'enabled', + curve = 'curveMonotoneX', ...svgProps } = props; const theme: BackstageTheme = useTheme(); @@ -424,6 +434,7 @@ export function DependencyGraph( setEdge={setEdge} render={renderLabel} edge={edge} + curve={curve} /> ); })} diff --git a/packages/core-components/src/components/DependencyGraph/Edge.tsx b/packages/core-components/src/components/DependencyGraph/Edge.tsx index 72a7547cda..ea526827bf 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.tsx @@ -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 = { id: dagre.Edge, edge: DependencyEdge, ) => dagre.graphlib.Graph<{}>; + curve: Curve; }; const renderDefault = (props: RenderLabelProps) => ( ); -const createPath = d3Shape - .line() - .x(d => d.x) - .y(d => d.y) - .curve(d3Shape.curveStepBefore); - export function Edge({ render = renderDefault, setEdge, id, edge, + curve, }: EdgeComponentProps) { const { x = 0, y = 0, width, height, points } = edge; const labelProps: DependencyEdge = edge; @@ -114,6 +111,16 @@ export function Edge({ let path: string = ''; + const createPath = React.useMemo( + () => + d3Shape + .line() + .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), diff --git a/packages/core-components/src/components/DependencyGraph/types.ts b/packages/core-components/src/components/DependencyGraph/types.ts index 4ae6057bd7..f74374957b 100644 --- a/packages/core-components/src/components/DependencyGraph/types.ts +++ b/packages/core-components/src/components/DependencyGraph/types.ts @@ -75,6 +75,12 @@ export type RenderNodeFunction = ( props: RenderNodeProps, ) => 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 * diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index 8e59c127d9..38fcdad918 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -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} /> + diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx new file mode 100644 index 0000000000..0d1ed6e9a7 --- /dev/null +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx @@ -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('', () => { + test('should display current curve label', () => { + const onChange = jest.fn(); + const { getByText } = render( + , + ); + + expect(getByText('Monotone X')).toBeInTheDocument(); + }); + + test('should select an alternative curve factory', async () => { + const onChange = jest.fn(); + const { getByText, getByTestId } = render( + , + ); + + 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'); + }); + }); +}); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx new file mode 100644 index 0000000000..b508d48172 --- /dev/null +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx @@ -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 = { + 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 ( + +