feat(catalog-graph): use Catalog Presentation API instead of humanizeEntityRef

Replace humanizeEntityRef with entityPresentationApiRef in CatalogGraphCard
and CatalogGraphPage for consistent entity display via the Catalog
Presentation API. Contributes to #20955.

Signed-off-by: Matt Van Horn <matt@osc.dev>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
This commit is contained in:
Matt Van Horn
2026-03-17 01:01:20 -07:00
parent 8632502abe
commit 416ad45fdd
5 changed files with 59 additions and 19 deletions
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-graph': patch
---
Replaced `humanizeEntityRef` with the Catalog Presentation API in `CatalogGraphCard` and `CatalogGraphPage` components for consistent entity display.
+14
View File
@@ -21,10 +21,21 @@ import {
createTestEntityPage,
catalogApiMock,
} from '@backstage/plugin-catalog-react/testUtils';
import {
defaultEntityPresentation,
entityPresentationApiRef,
} from '@backstage/plugin-catalog-react';
import catalogGraphPlugin from './alpha';
import { catalogGraphRouteRef } from './routes';
import { catalogGraphApiRef, DefaultCatalogGraphApi } from './api';
const mockEntityPresentationApi = {
forEntity(entityOrRef: Parameters<typeof defaultEntityPresentation>[0]) {
const snapshot = defaultEntityPresentation(entityOrRef);
return { snapshot, promise: Promise.resolve(snapshot) };
},
};
const CatalogGraphEntityCard = catalogGraphPlugin.getExtension(
'entity-card:catalog-graph/relations',
);
@@ -59,6 +70,7 @@ describe('catalog-graph alpha plugin', () => {
apis: [
catalogApiMock({ entities: [entity] }),
[catalogGraphApiRef, new DefaultCatalogGraphApi()],
[entityPresentationApiRef, mockEntityPresentationApi],
],
});
@@ -96,6 +108,7 @@ describe('catalog-graph alpha plugin', () => {
apis: [
catalogApiMock({ entities: [entity] }),
[catalogGraphApiRef, new DefaultCatalogGraphApi()],
[entityPresentationApiRef, mockEntityPresentationApi],
],
});
@@ -132,6 +145,7 @@ describe('catalog-graph alpha plugin', () => {
apis: [
catalogApiMock({ entities: [entity] }),
[catalogGraphApiRef, new DefaultCatalogGraphApi()],
[entityPresentationApiRef, mockEntityPresentationApi],
],
});
@@ -20,6 +20,7 @@ import { analyticsApiRef } from '@backstage/core-plugin-api';
import {
catalogApiRef,
EntityProvider,
entityPresentationApiRef,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
@@ -36,6 +37,14 @@ import { CatalogGraphCard } from './CatalogGraphCard';
import Button from '@material-ui/core/Button';
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
import { catalogGraphApiRef, DefaultCatalogGraphApi } from '../../api';
import { defaultEntityPresentation } from '@backstage/plugin-catalog-react';
const mockEntityPresentationApi = {
forEntity(entityOrRef: Parameters<typeof defaultEntityPresentation>[0]) {
const snapshot = defaultEntityPresentation(entityOrRef);
return { snapshot, promise: Promise.resolve(snapshot) };
},
};
describe('<CatalogGraphCard/>', () => {
let entity: Entity;
@@ -58,6 +67,7 @@ describe('<CatalogGraphCard/>', () => {
[catalogApiRef, catalog],
[translationApiRef, mockApis.translation()],
[catalogGraphApiRef, new DefaultCatalogGraphApi()],
[entityPresentationApiRef, mockEntityPresentationApi],
);
wrapper = (
@@ -241,7 +251,7 @@ describe('<CatalogGraphCard/>', () => {
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
expect.objectContaining({
action: 'click',
subject: 'b:d/c',
subject: 'd/c',
attributes: {
to: '/entity/{kind}/{namespace}/{name}',
},
@@ -19,10 +19,10 @@ import {
parseEntityRef,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { useAnalytics, useRouteRef } from '@backstage/core-plugin-api';
import { useAnalytics, useRouteRef, useApi } from '@backstage/core-plugin-api';
import {
EntityInfoCard,
humanizeEntityRef,
entityPresentationApiRef,
useEntity,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
@@ -89,6 +89,7 @@ export const CatalogGraphCard = (
const navigate = useNavigate();
const classes = useStyles({ height });
const analytics = useAnalytics();
const entityPresentationApi = useApi(entityPresentationApiRef);
const defaultOnNodeClick = useCallback(
(node: EntityNode, _: MouseEvent<unknown>) => {
@@ -100,12 +101,12 @@ export const CatalogGraphCard = (
});
analytics.captureEvent(
'click',
node.entity.metadata.title ?? humanizeEntityRef(nodeEntityName),
entityPresentationApi.forEntity(node.entity).snapshot.primaryTitle,
{ attributes: { to: path } },
);
navigate(path);
},
[catalogEntityRoute, navigate, analytics],
[catalogEntityRoute, navigate, analytics, entityPresentationApi],
);
const catalogGraphParams = qs.stringify(
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { parseEntityRef } from '@backstage/catalog-model';
import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
import {
Content,
ContentHeader,
@@ -22,10 +22,10 @@ import {
Page,
SupportButton,
} from '@backstage/core-components';
import { useAnalytics, useRouteRef } from '@backstage/core-plugin-api';
import { useAnalytics, useRouteRef, useApi } from '@backstage/core-plugin-api';
import {
entityPresentationApiRef,
entityRouteRef,
humanizeEntityRef,
} from '@backstage/plugin-catalog-react';
import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';
@@ -157,9 +157,12 @@ export const CatalogGraphPage = (
toggleShowFilters,
} = useCatalogGraphPage({ initialState });
const analytics = useAnalytics();
const entityPresentationApi = useApi(entityPresentationApiRef);
const onNodeClick = useCallback(
(node: EntityNode, event: MouseEvent<unknown>) => {
const nodeEntityName = parseEntityRef(node.id);
const nodeTitle = entityPresentationApi.forEntity(node.entity).snapshot
.primaryTitle;
if (event.shiftKey) {
const path = catalogEntityRoute({
@@ -168,28 +171,35 @@ export const CatalogGraphPage = (
name: nodeEntityName.name,
});
analytics.captureEvent(
'click',
node.entity.metadata.title ?? humanizeEntityRef(nodeEntityName),
{ attributes: { to: path } },
);
analytics.captureEvent('click', nodeTitle, {
attributes: { to: path },
});
navigate(path);
} else {
analytics.captureEvent(
'click',
node.entity.metadata.title ?? humanizeEntityRef(nodeEntityName),
);
analytics.captureEvent('click', nodeTitle);
setRootEntityNames([nodeEntityName]);
}
},
[catalogEntityRoute, navigate, setRootEntityNames, analytics],
[
catalogEntityRoute,
navigate,
setRootEntityNames,
analytics,
entityPresentationApi,
],
);
return (
<Page themeId="home">
<Header
title={t('catalogGraphPage.title')}
subtitle={rootEntityNames.map(e => humanizeEntityRef(e)).join(', ')}
subtitle={rootEntityNames
.map(
e =>
entityPresentationApi.forEntity(stringifyEntityRef(e)).snapshot
.primaryTitle,
)
.join(', ')}
/>
<Content stretch className={classes.content}>
<ContentHeader