feat: use entityPresentationApi for the node title and the icon

Signed-off-by: David Weber <david.weber@w3tec.ch>
Signed-off-by: David Weber <david.weber.schenker@gmail.com>
This commit is contained in:
David Weber
2024-07-31 01:32:54 +02:00
parent 5083c8024d
commit 4a529c266e
5 changed files with 33 additions and 32 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-graph': patch
---
Use `entityPresentationApi` for the node title and the icon.
@@ -47,6 +47,7 @@ describe('<CustomNode />', () => {
);
expect(screen.getByText('kind:namespace/name')).toBeInTheDocument();
expect(screen.getByText('namespace/name')).toBeInTheDocument();
});
test('renders node, skips default namespace', async () => {
@@ -73,7 +74,7 @@ describe('<CustomNode />', () => {
</svg>,
);
expect(screen.getByText('kind:name')).toBeInTheDocument();
expect(screen.getByText('name')).toBeInTheDocument();
});
test('renders node with onClick', async () => {
@@ -102,8 +103,8 @@ describe('<CustomNode />', () => {
</svg>,
);
expect(screen.getByText('kind:namespace/name')).toBeInTheDocument();
await userEvent.click(screen.getByText('kind:namespace/name'));
expect(screen.getByText('namespace/name')).toBeInTheDocument();
await userEvent.click(screen.getByText('namespace/name'));
expect(onClick).toHaveBeenCalledTimes(1);
});
@@ -134,5 +135,6 @@ describe('<CustomNode />', () => {
);
expect(screen.getByText('Custom Title')).toBeInTheDocument();
expect(screen.getByText('kind:namespace/name')).toBeInTheDocument();
});
});
@@ -14,12 +14,12 @@
* limitations under the License.
*/
import { DependencyGraphTypes } from '@backstage/core-components';
import { useApp } from '@backstage/core-plugin-api';
import { humanizeEntityRef } from '@backstage/plugin-catalog-react';
import { IconComponent } from '@backstage/core-plugin-api';
import { useEntityPresentation } from '@backstage/plugin-catalog-react';
import { makeStyles } from '@material-ui/core/styles';
import classNames from 'classnames';
import React, { useLayoutEffect, useRef, useState } from 'react';
import { EntityKindIcon } from './EntityKindIcon';
import { EntityIcon } from './EntityIcon';
import { EntityNodeData } from './types';
import { DEFAULT_NAMESPACE } from '@backstage/catalog-model';
@@ -64,8 +64,10 @@ export function DefaultRenderNode({
const classes = useStyles();
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const app = useApp();
const idRef = useRef<SVGTextElement | null>(null);
const entityRefPresentationSnapshot = useEntityPresentation(entity, {
defaultNamespace: DEFAULT_NAMESPACE,
});
useLayoutEffect(() => {
// set the width to the length of the ID
@@ -82,25 +84,14 @@ export function DefaultRenderNode({
}
}, [width, height]);
const {
kind,
metadata: { name, namespace = DEFAULT_NAMESPACE, title },
} = entity;
const hasKindIcon = app.getSystemIcon(
`kind:${kind.toLocaleLowerCase('en-US')}`,
);
const hasKindIcon = !!entityRefPresentationSnapshot.Icon;
const padding = 10;
const iconSize = height;
const paddedIconWidth = hasKindIcon ? iconSize + padding : 0;
const paddedWidth = paddedIconWidth + width + padding * 2;
const paddedHeight = height + padding * 2;
const displayTitle =
title ??
(kind && name && namespace
? humanizeEntityRef({ kind, name, namespace })
: id);
const displayTitle = entityRefPresentationSnapshot.primaryTitle ?? id;
return (
<g onClick={onClick} className={classNames(onClick && classes.clickable)}>
@@ -115,8 +106,8 @@ export function DefaultRenderNode({
rx={10}
/>
{hasKindIcon && (
<EntityKindIcon
kind={kind}
<EntityIcon
icon={entityRefPresentationSnapshot.Icon as IconComponent}
y={padding}
x={padding}
width={iconSize}
@@ -144,6 +135,7 @@ export function DefaultRenderNode({
>
{displayTitle}
</text>
<title>{entityRefPresentationSnapshot.entityRef}</title>
</g>
);
}
@@ -16,12 +16,14 @@
import { renderInTestApp } from '@backstage/test-utils';
import React from 'react';
import { EntityKindIcon } from './EntityKindIcon';
import { EntityIcon } from './EntityIcon';
import MuiMemoryIcon from '@material-ui/icons/Memory';
import { IconComponent } from '@backstage/core-plugin-api';
describe('<EntityKindIcon />', () => {
it('renders without exploding', async () => {
const { baseElement } = await renderInTestApp(
<EntityKindIcon kind="Component" />,
<EntityIcon icon={MuiMemoryIcon as IconComponent} />,
);
expect(baseElement.querySelector('svg')).toBeInTheDocument();
@@ -29,7 +31,7 @@ describe('<EntityKindIcon />', () => {
it('renders without exploding for unknown kind', async () => {
const { baseElement } = await renderInTestApp(
<EntityKindIcon kind="unknown" />,
<EntityIcon icon={undefined} />,
);
expect(baseElement.querySelector('svg')).toBeInTheDocument();
@@ -13,23 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useApp } from '@backstage/core-plugin-api';
import React from 'react';
import SvgIcon from '@material-ui/core/SvgIcon';
import { OverridableComponent } from '@material-ui/core/OverridableComponent';
import { SvgIconTypeMap } from '@material-ui/core/SvgIcon/SvgIcon';
import { IconComponent } from '@backstage/core-plugin-api';
export function EntityKindIcon({
kind,
export function EntityIcon({
icon,
...props
}: {
kind: string;
icon: IconComponent | undefined;
x?: number;
y?: number;
width?: number;
height?: number;
className?: string;
}) {
const app = useApp();
const Icon =
app.getSystemIcon(`kind:${kind.toLocaleLowerCase('en-US')}`) ?? SvgIcon;
const Icon = (icon as OverridableComponent<SvgIconTypeMap>) ?? SvgIcon;
return <Icon {...props} />;
}