From 416ad45fddeaf4ef31a42470355b274d64f53a12 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Tue, 17 Mar 2026 01:01:20 -0700 Subject: [PATCH 1/2] 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 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> --- .../catalog-graph-use-presentation-api.md | 5 +++ plugins/catalog-graph/src/alpha.test.tsx | 14 +++++++ .../CatalogGraphCard.test.tsx | 12 +++++- .../CatalogGraphCard/CatalogGraphCard.tsx | 9 +++-- .../CatalogGraphPage/CatalogGraphPage.tsx | 38 ++++++++++++------- 5 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 .changeset/catalog-graph-use-presentation-api.md diff --git a/.changeset/catalog-graph-use-presentation-api.md b/.changeset/catalog-graph-use-presentation-api.md new file mode 100644 index 0000000000..b0d73629b4 --- /dev/null +++ b/.changeset/catalog-graph-use-presentation-api.md @@ -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. diff --git a/plugins/catalog-graph/src/alpha.test.tsx b/plugins/catalog-graph/src/alpha.test.tsx index e0241ec7b0..873ebaa476 100644 --- a/plugins/catalog-graph/src/alpha.test.tsx +++ b/plugins/catalog-graph/src/alpha.test.tsx @@ -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[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], ], }); diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx index 3ae8310280..e65d40b955 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx @@ -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[0]) { + const snapshot = defaultEntityPresentation(entityOrRef); + return { snapshot, promise: Promise.resolve(snapshot) }; + }, +}; describe('', () => { let entity: Entity; @@ -58,6 +67,7 @@ describe('', () => { [catalogApiRef, catalog], [translationApiRef, mockApis.translation()], [catalogGraphApiRef, new DefaultCatalogGraphApi()], + [entityPresentationApiRef, mockEntityPresentationApi], ); wrapper = ( @@ -241,7 +251,7 @@ describe('', () => { expect(analyticsApi.captureEvent).toHaveBeenCalledWith( expect.objectContaining({ action: 'click', - subject: 'b:d/c', + subject: 'd/c', attributes: { to: '/entity/{kind}/{namespace}/{name}', }, diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx index 10ba73e2b6..6032f0a3d2 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx @@ -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) => { @@ -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( diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index 93bdc94860..1df2ec1961 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -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) => { 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 (
humanizeEntityRef(e)).join(', ')} + subtitle={rootEntityNames + .map( + e => + entityPresentationApi.forEntity(stringifyEntityRef(e)).snapshot + .primaryTitle, + ) + .join(', ')} /> Date: Tue, 7 Apr 2026 07:47:27 -0700 Subject: [PATCH 2/2] refactor(catalog-graph): migrate to entityPresentationSnapshot helper Replace direct entityPresentationApiRef usage with the new entityPresentationSnapshot helper from #33576. This fixes the type safety concern where CompoundEntityRef was passed to forEntity() which only accepts Entity | string. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> --- plugins/catalog-graph/src/alpha.test.tsx | 14 ----------- .../CatalogGraphCard.test.tsx | 10 -------- .../CatalogGraphCard/CatalogGraphCard.tsx | 9 ++++--- .../CatalogGraphPage/CatalogGraphPage.tsx | 24 +++++-------------- 4 files changed, 10 insertions(+), 47 deletions(-) diff --git a/plugins/catalog-graph/src/alpha.test.tsx b/plugins/catalog-graph/src/alpha.test.tsx index 873ebaa476..e0241ec7b0 100644 --- a/plugins/catalog-graph/src/alpha.test.tsx +++ b/plugins/catalog-graph/src/alpha.test.tsx @@ -21,21 +21,10 @@ 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[0]) { - const snapshot = defaultEntityPresentation(entityOrRef); - return { snapshot, promise: Promise.resolve(snapshot) }; - }, -}; - const CatalogGraphEntityCard = catalogGraphPlugin.getExtension( 'entity-card:catalog-graph/relations', ); @@ -70,7 +59,6 @@ describe('catalog-graph alpha plugin', () => { apis: [ catalogApiMock({ entities: [entity] }), [catalogGraphApiRef, new DefaultCatalogGraphApi()], - [entityPresentationApiRef, mockEntityPresentationApi], ], }); @@ -108,7 +96,6 @@ describe('catalog-graph alpha plugin', () => { apis: [ catalogApiMock({ entities: [entity] }), [catalogGraphApiRef, new DefaultCatalogGraphApi()], - [entityPresentationApiRef, mockEntityPresentationApi], ], }); @@ -145,7 +132,6 @@ describe('catalog-graph alpha plugin', () => { apis: [ catalogApiMock({ entities: [entity] }), [catalogGraphApiRef, new DefaultCatalogGraphApi()], - [entityPresentationApiRef, mockEntityPresentationApi], ], }); diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx index e65d40b955..706fafa177 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx @@ -20,7 +20,6 @@ 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'; @@ -37,14 +36,6 @@ 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[0]) { - const snapshot = defaultEntityPresentation(entityOrRef); - return { snapshot, promise: Promise.resolve(snapshot) }; - }, -}; describe('', () => { let entity: Entity; @@ -67,7 +58,6 @@ describe('', () => { [catalogApiRef, catalog], [translationApiRef, mockApis.translation()], [catalogGraphApiRef, new DefaultCatalogGraphApi()], - [entityPresentationApiRef, mockEntityPresentationApi], ); wrapper = ( diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx index 6032f0a3d2..0510adb53a 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx @@ -19,10 +19,10 @@ import { parseEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; -import { useAnalytics, useRouteRef, useApi } from '@backstage/core-plugin-api'; +import { useAnalytics, useRouteRef } from '@backstage/core-plugin-api'; import { EntityInfoCard, - entityPresentationApiRef, + entityPresentationSnapshot, useEntity, entityRouteRef, } from '@backstage/plugin-catalog-react'; @@ -89,7 +89,6 @@ export const CatalogGraphCard = ( const navigate = useNavigate(); const classes = useStyles({ height }); const analytics = useAnalytics(); - const entityPresentationApi = useApi(entityPresentationApiRef); const defaultOnNodeClick = useCallback( (node: EntityNode, _: MouseEvent) => { @@ -101,12 +100,12 @@ export const CatalogGraphCard = ( }); analytics.captureEvent( 'click', - entityPresentationApi.forEntity(node.entity).snapshot.primaryTitle, + entityPresentationSnapshot(node.entity).primaryTitle, { attributes: { to: path } }, ); navigate(path); }, - [catalogEntityRoute, navigate, analytics, entityPresentationApi], + [catalogEntityRoute, navigate, analytics], ); const catalogGraphParams = qs.stringify( diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index 1df2ec1961..16b54e2717 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; +import { parseEntityRef } from '@backstage/catalog-model'; import { Content, ContentHeader, @@ -22,9 +22,9 @@ import { Page, SupportButton, } from '@backstage/core-components'; -import { useAnalytics, useRouteRef, useApi } from '@backstage/core-plugin-api'; +import { useAnalytics, useRouteRef } from '@backstage/core-plugin-api'; import { - entityPresentationApiRef, + entityPresentationSnapshot, entityRouteRef, } from '@backstage/plugin-catalog-react'; import Grid from '@material-ui/core/Grid'; @@ -157,12 +157,10 @@ export const CatalogGraphPage = ( toggleShowFilters, } = useCatalogGraphPage({ initialState }); const analytics = useAnalytics(); - const entityPresentationApi = useApi(entityPresentationApiRef); const onNodeClick = useCallback( (node: EntityNode, event: MouseEvent) => { const nodeEntityName = parseEntityRef(node.id); - const nodeTitle = entityPresentationApi.forEntity(node.entity).snapshot - .primaryTitle; + const nodeTitle = entityPresentationSnapshot(node.entity).primaryTitle; if (event.shiftKey) { const path = catalogEntityRoute({ @@ -180,13 +178,7 @@ export const CatalogGraphPage = ( setRootEntityNames([nodeEntityName]); } }, - [ - catalogEntityRoute, - navigate, - setRootEntityNames, - analytics, - entityPresentationApi, - ], + [catalogEntityRoute, navigate, setRootEntityNames, analytics], ); return ( @@ -194,11 +186,7 @@ export const CatalogGraphPage = (
- entityPresentationApi.forEntity(stringifyEntityRef(e)).snapshot - .primaryTitle, - ) + .map(e => entityPresentationSnapshot(e).primaryTitle) .join(', ')} />