diff --git a/docs/features/software-catalog/entity-presentation.md b/docs/features/software-catalog/entity-presentation.md
index c5ca8edada..a883f93339 100644
--- a/docs/features/software-catalog/entity-presentation.md
+++ b/docs/features/software-catalog/entity-presentation.md
@@ -52,24 +52,12 @@ function MyComponent({ entityRef }: { entityRef: string }) {
The hook subscribes to the `EntityPresentationApi` and returns a snapshot
that may update over time as additional data is fetched in the background.
-### Using the API directly
+### Using the API directly (async, preferred for non-React)
-In contexts where hooks are not available, you can use the
-`entityPresentationApiRef` API directly. The API provides two access
-patterns:
-
-- **`.snapshot`** for synchronous access (for example in sort comparators or
- filter callbacks):
-
-```ts
-import { entityPresentationApiRef } from '@backstage/plugin-catalog-react';
-
-const title = entityPresentationApi.forEntity(entity, {
- defaultKind: 'Component',
-}).snapshot.primaryTitle;
-```
-
-- **`.promise`** for async contexts (for example inside data loaders):
+In non-React **async** contexts where you can `await` -- such as data
+loaders, `useAsync` callbacks, or event handlers -- use the
+`entityPresentationApiRef` API directly with `.promise` for the richest
+possible presentation:
```ts
const presentation = await entityPresentationApi.forEntity(entity, {
@@ -78,9 +66,39 @@ const presentation = await entityPresentationApi.forEntity(entity, {
const title = presentation.primaryTitle;
```
-The `.snapshot` path uses cached data when available, so it performs well
-even in tight loops like sorting. The `.promise` path resolves to a richer
-presentation that may include data fetched from the catalog.
+The `.promise` path resolves to a full presentation that may include data
+fetched from the catalog. **This is the preferred approach whenever an
+async context is available.**
+
+### `entityPresentationSnapshot` helper (synchronous fallback)
+
+When a synchronous return value is required and `await` is not possible --
+such as in sort comparators, column factories, or filter callbacks -- use
+`entityPresentationSnapshot` as a fallback. It accepts `Entity`,
+`CompoundEntityRef`, or string ref inputs and uses the presentation API
+when available, falling back to `defaultEntityPresentation` otherwise:
+
+```ts
+import {
+ entityPresentationSnapshot,
+ entityPresentationApiRef,
+} from '@backstage/plugin-catalog-react';
+
+// In a column factory or sort comparator where you have
+// the API instance (or undefined if not registered):
+const title = entityPresentationSnapshot(
+ entity,
+ {
+ defaultKind: 'Component',
+ },
+ entityPresentationApi,
+).primaryTitle;
+```
+
+Because this function is synchronous, it uses cached data from the
+presentation API. If the entity has been seen before, the snapshot will
+contain the full resolved title; otherwise it falls back to what can be
+extracted from the ref alone.
## Customizing entity presentation
@@ -124,6 +142,6 @@ Replace them as follows:
| :---------------------------------------------------- | :--------------------------------------------------------------------- |
| `humanizeEntityRef(entity)` in JSX | `` |
| `humanizeEntityRef(entity)` in a React component | `useEntityPresentation(entity).primaryTitle` |
-| `humanizeEntityRef(entity)` in a sort/filter callback | `entityPresentationApi.forEntity(entity).snapshot.primaryTitle` |
| `humanizeEntityRef(entity)` in an async loader | `(await entityPresentationApi.forEntity(entity).promise).primaryTitle` |
+| `humanizeEntityRef(entity)` in a sort/filter callback | `entityPresentationSnapshot(entity, ctx, api).primaryTitle` |
| `humanizeEntity(entity, fallback)` | `useEntityPresentation(entity).primaryTitle` |
diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md
index 251a0e853a..419a3c3f94 100644
--- a/plugins/catalog-react/report.api.md
+++ b/plugins/catalog-react/report.api.md
@@ -593,6 +593,16 @@ export interface EntityPresentationApi {
// @public
export const entityPresentationApiRef: ApiRef_2;
+// @public
+export function entityPresentationSnapshot(
+ entityOrRef: Entity | CompoundEntityRef | string,
+ context?: {
+ defaultKind?: string;
+ defaultNamespace?: string;
+ },
+ entityPresentationApi?: EntityPresentationApi,
+): EntityRefPresentationSnapshot;
+
// @public (undocumented)
export const EntityProcessingStatusPicker: () => JSX_2.Element;
diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts
index 316bc9aff5..99ae0ceacc 100644
--- a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts
+++ b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts
@@ -37,8 +37,8 @@ import { Observable } from '@backstage/types';
* and tooltip.
*
* - In non-React contexts such as sort comparators or data mappers, use the
- * API directly via `forEntity().snapshot` for synchronous access, or
- * `forEntity().promise` in async loaders.
+ * {@link entityPresentationSnapshot} helper for synchronous access, or
+ * the API directly via `forEntity().promise` in async loaders.
*
* @public
*/
@@ -143,8 +143,8 @@ export interface EntityRefPresentation {
* - {@link EntityDisplayName} — React component that renders an entity name
* with optional icon and tooltip.
*
- * For non-React contexts, you can use the API directly via
- * `forEntity().snapshot` for synchronous access, or `forEntity().promise`
+ * For non-React contexts, use the {@link entityPresentationSnapshot} helper
+ * for synchronous access, or the API directly via `forEntity().promise`
* for async contexts.
*
* Implement this interface to customize how entities are displayed throughout
diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts
index 1984593cfa..98f200c67a 100644
--- a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts
+++ b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts
@@ -35,10 +35,10 @@ import { EntityRefPresentationSnapshot } from './EntityPresentationApi';
*
* This function is primarily used as the internal fallback within the
* {@link EntityPresentationApi} when no custom implementation is registered.
- * Prefer using the API directly via `forEntity().snapshot` or
- * `forEntity().promise`, which respects custom presentation overrides.
* In React components, use the {@link useEntityPresentation} hook or the
- * {@link EntityDisplayName} component.
+ * {@link EntityDisplayName} component. In non-React contexts, use
+ * {@link entityPresentationSnapshot} which respects custom presentation
+ * overrides and falls back to this function when no API is registered.
*
* @public
* @param entityOrRef - Either an entity, or a ref to it.
diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/entityPresentationSnapshot.test.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/entityPresentationSnapshot.test.ts
new file mode 100644
index 0000000000..8ff27c3282
--- /dev/null
+++ b/plugins/catalog-react/src/apis/EntityPresentationApi/entityPresentationSnapshot.test.ts
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2026 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 { CompoundEntityRef, Entity } from '@backstage/catalog-model';
+import { entityPresentationSnapshot } from './entityPresentationSnapshot';
+import {
+ EntityPresentationApi,
+ EntityRefPresentation,
+ EntityRefPresentationSnapshot,
+} from './EntityPresentationApi';
+
+function createMockApi(): EntityPresentationApi & {
+ calls: Array<{ entityOrRef: Entity | string; context?: object }>;
+} {
+ const calls: Array<{ entityOrRef: Entity | string; context?: object }> = [];
+ return {
+ calls,
+ forEntity(entityOrRef, context) {
+ calls.push({ entityOrRef, context });
+ const snapshot: EntityRefPresentationSnapshot = {
+ entityRef: typeof entityOrRef === 'string' ? entityOrRef : 'mock-ref',
+ primaryTitle: 'from-api',
+ secondaryTitle: undefined,
+ Icon: undefined,
+ };
+ return {
+ snapshot,
+ promise: Promise.resolve(snapshot),
+ } as EntityRefPresentation;
+ },
+ };
+}
+
+describe('entityPresentationSnapshot', () => {
+ describe('with EntityPresentationApi', () => {
+ it('passes Entity directly to forEntity', () => {
+ const api = createMockApi();
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: { name: 'test', namespace: 'default' },
+ };
+
+ const result = entityPresentationSnapshot(entity, undefined, api);
+
+ expect(result.primaryTitle).toBe('from-api');
+ expect(api.calls).toHaveLength(1);
+ expect(api.calls[0].entityOrRef).toBe(entity);
+ });
+
+ it('stringifies CompoundEntityRef before passing to forEntity', () => {
+ const api = createMockApi();
+ const ref: CompoundEntityRef = {
+ kind: 'group',
+ namespace: 'default',
+ name: 'my-team',
+ };
+
+ const result = entityPresentationSnapshot(ref, undefined, api);
+
+ expect(result.primaryTitle).toBe('from-api');
+ expect(api.calls).toHaveLength(1);
+ expect(api.calls[0].entityOrRef).toBe('group:default/my-team');
+ expect(typeof api.calls[0].entityOrRef).toBe('string');
+ });
+
+ it('passes string ref directly to forEntity', () => {
+ const api = createMockApi();
+
+ const result = entityPresentationSnapshot(
+ 'component:default/test',
+ undefined,
+ api,
+ );
+
+ expect(result.primaryTitle).toBe('from-api');
+ expect(api.calls).toHaveLength(1);
+ expect(api.calls[0].entityOrRef).toBe('component:default/test');
+ });
+
+ it('forwards context to forEntity', () => {
+ const api = createMockApi();
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: { name: 'test', namespace: 'default' },
+ };
+
+ entityPresentationSnapshot(
+ entity,
+ { defaultKind: 'component', defaultNamespace: 'custom' },
+ api,
+ );
+
+ expect(api.calls[0].context).toEqual({
+ defaultKind: 'component',
+ defaultNamespace: 'custom',
+ });
+ });
+ });
+
+ describe('without EntityPresentationApi', () => {
+ it('falls back to defaultEntityPresentation for Entity', () => {
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'test',
+ namespace: 'default',
+ title: 'My Component',
+ },
+ };
+
+ const result = entityPresentationSnapshot(entity);
+
+ expect(result).toEqual({
+ entityRef: 'component:default/test',
+ primaryTitle: 'My Component',
+ secondaryTitle: 'component:default/test',
+ Icon: undefined,
+ });
+ });
+
+ it('falls back to defaultEntityPresentation for CompoundEntityRef', () => {
+ const ref: CompoundEntityRef = {
+ kind: 'group',
+ namespace: 'default',
+ name: 'my-team',
+ };
+
+ const result = entityPresentationSnapshot(ref);
+
+ expect(result).toEqual({
+ entityRef: 'group:default/my-team',
+ primaryTitle: 'my-team',
+ secondaryTitle: 'group:default/my-team',
+ Icon: undefined,
+ });
+ });
+
+ it('falls back to defaultEntityPresentation for string ref', () => {
+ const result = entityPresentationSnapshot('component:default/test');
+
+ expect(result).toEqual({
+ entityRef: 'component:default/test',
+ primaryTitle: 'test',
+ secondaryTitle: 'component:default/test',
+ Icon: undefined,
+ });
+ });
+
+ it('forwards context to defaultEntityPresentation', () => {
+ const result = entityPresentationSnapshot('component:default/test', {
+ defaultKind: 'component',
+ });
+
+ expect(result.primaryTitle).toBe('test');
+ });
+ });
+});
diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/entityPresentationSnapshot.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/entityPresentationSnapshot.ts
new file mode 100644
index 0000000000..a5ee876bdf
--- /dev/null
+++ b/plugins/catalog-react/src/apis/EntityPresentationApi/entityPresentationSnapshot.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2026 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 {
+ CompoundEntityRef,
+ Entity,
+ stringifyEntityRef,
+} from '@backstage/catalog-model';
+import {
+ EntityPresentationApi,
+ EntityRefPresentationSnapshot,
+} from './EntityPresentationApi';
+import { defaultEntityPresentation } from './defaultEntityPresentation';
+
+/**
+ * Returns a synchronous presentation snapshot for an entity in non-React
+ * contexts.
+ *
+ * @remarks
+ *
+ * This is the synchronous, non-React counterpart to
+ * {@link useEntityPresentation}. It handles `Entity`, `CompoundEntityRef`,
+ * and string ref inputs uniformly, using the provided
+ * {@link EntityPresentationApi} when available and falling back to
+ * {@link defaultEntityPresentation} otherwise.
+ *
+ * Because this function is synchronous, it uses cached data from the
+ * presentation API (via `.snapshot`). If the entity has been seen before,
+ * the snapshot will contain the full resolved title; otherwise it falls
+ * back to what can be extracted from the ref alone. This is the correct
+ * trade-off for sort comparators, column factories, filter callbacks, and
+ * data mappers where a synchronous return value is required.
+ *
+ * In async contexts such as data loaders where you can `await`, prefer
+ * using the {@link EntityPresentationApi} directly via
+ * `forEntity().promise` for the richest possible presentation.
+ *
+ * @public
+ * @param entityOrRef - An entity, a compound entity ref, or a string entity ref.
+ * @param context - Optional context that may affect the presentation.
+ * @param entityPresentationApi - Optional presentation API instance. When not
+ * provided, falls back to {@link defaultEntityPresentation}.
+ */
+export function entityPresentationSnapshot(
+ entityOrRef: Entity | CompoundEntityRef | string,
+ context?: { defaultKind?: string; defaultNamespace?: string },
+ entityPresentationApi?: EntityPresentationApi,
+): EntityRefPresentationSnapshot {
+ if (entityPresentationApi) {
+ const ref =
+ typeof entityOrRef === 'string' || 'metadata' in entityOrRef
+ ? entityOrRef
+ : stringifyEntityRef(entityOrRef);
+ return entityPresentationApi.forEntity(ref, context).snapshot;
+ }
+ return defaultEntityPresentation(entityOrRef, context);
+}
diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/index.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/index.ts
index 405afe23f6..8369a69167 100644
--- a/plugins/catalog-react/src/apis/EntityPresentationApi/index.ts
+++ b/plugins/catalog-react/src/apis/EntityPresentationApi/index.ts
@@ -21,4 +21,5 @@ export {
type EntityRefPresentationSnapshot,
} from './EntityPresentationApi';
export { defaultEntityPresentation } from './defaultEntityPresentation';
+export { entityPresentationSnapshot } from './entityPresentationSnapshot';
export { useEntityPresentation } from './useEntityPresentation';
diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts
index 125557ceb7..68694f167a 100644
--- a/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts
+++ b/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts
@@ -43,7 +43,7 @@ import { useUpdatingObservable } from './useUpdatingObservable';
* component instead, which wraps this hook with icon and tooltip support.
*
* For non-React contexts such as sort comparators or data mappers, use
- * the {@link EntityPresentationApi} directly via `forEntity().snapshot`.
+ * {@link entityPresentationSnapshot}.
*
* @public
* @param entityOrRef - The entity to represent, or an entity ref to it. If you
diff --git a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx
index b0ff221e9d..80a4cc8070 100644
--- a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx
+++ b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx
@@ -22,7 +22,7 @@ import {
import { Cell, CellText, Column, ColumnConfig, TableItem } from '@backstage/ui';
import { EntityRefLink, EntityRefLinks } from '../EntityRefLink';
import {
- defaultEntityPresentation,
+ entityPresentationSnapshot,
EntityPresentationApi,
} from '@backstage/plugin-catalog-react';
import { EntityTableColumnTitle } from '../EntityTable/TitleColumn';
@@ -36,18 +36,6 @@ export interface EntityColumnConfig extends ColumnConfig {
sortValue?: (entity: EntityRow) => string;
}
-function getEntityTitle(
- entityOrRef: Entity | { kind: string; namespace?: string; name: string },
- context: { defaultKind?: string },
- entityPresentationApi?: EntityPresentationApi,
-): string {
- if (entityPresentationApi) {
- return entityPresentationApi.forEntity(entityOrRef as Entity, context)
- .snapshot.primaryTitle;
- }
- return defaultEntityPresentation(entityOrRef as Entity, context).primaryTitle;
-}
-
/** @public */
export const columnFactories = Object.freeze({
createEntityRefColumn(options: {
@@ -76,11 +64,11 @@ export const columnFactories = Object.freeze({
),
sortValue: entity =>
- getEntityTitle(
+ entityPresentationSnapshot(
entity,
{ defaultKind: options.defaultKind },
options.entityPresentationApi,
- ),
+ ).primaryTitle,
};
},
@@ -115,12 +103,13 @@ export const columnFactories = Object.freeze({
),
sortValue: entity =>
getEntityRelations(entity, options.relation, options.filter)
- .map(r =>
- getEntityTitle(
- r,
- { defaultKind: options.defaultKind },
- options.entityPresentationApi,
- ),
+ .map(
+ r =>
+ entityPresentationSnapshot(
+ r,
+ { defaultKind: options.defaultKind },
+ options.entityPresentationApi,
+ ).primaryTitle,
)
.join(', '),
};
diff --git a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx
index a6b4ed94fe..1f60333acd 100644
--- a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx
+++ b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx
@@ -70,7 +70,7 @@ export type EntityDisplayNameProps = {
*
* For more control over the presentation data, use the
* {@link useEntityPresentation} hook directly. For non-React contexts, use
- * the {@link EntityPresentationApi} directly via `forEntity().snapshot`.
+ * {@link entityPresentationSnapshot}.
*
* @public
*/
diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx
index 1375140ebc..291fcf5376 100644
--- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx
+++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx
@@ -34,8 +34,8 @@ import { useDebouncedEffect } from '@react-hookz/web';
import PersonIcon from '@material-ui/icons/Person';
import GroupIcon from '@material-ui/icons/Group';
import {
- defaultEntityPresentation,
entityPresentationApiRef,
+ entityPresentationSnapshot,
} from '../../apis';
import { useFetchEntities } from './useFetchEntities';
import { withStyles } from '@material-ui/core/styles';
@@ -209,11 +209,11 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => {
defaultNamespace: 'default',
})
: o;
- if (entityPresentationApi) {
- return entityPresentationApi.forEntity(entity as Entity).snapshot
- .primaryTitle;
- }
- return defaultEntityPresentation(entity).primaryTitle;
+ return entityPresentationSnapshot(
+ entity,
+ undefined,
+ entityPresentationApi,
+ ).primaryTitle;
}}
onChange={(_: object, owners) => {
setText('');
diff --git a/plugins/catalog-react/src/components/EntityRefLink/humanize.ts b/plugins/catalog-react/src/components/EntityRefLink/humanize.ts
index 80bd290806..fe697365b6 100644
--- a/plugins/catalog-react/src/components/EntityRefLink/humanize.ts
+++ b/plugins/catalog-react/src/components/EntityRefLink/humanize.ts
@@ -26,9 +26,10 @@ import get from 'lodash/get';
* if set to string which matches namespace of entity then omitted
*
* @deprecated Use {@link useEntityPresentation} or {@link EntityDisplayName}
- * in React components, or access the {@link entityPresentationApiRef} directly.
- * These provide richer display names using `metadata.title` and
- * `spec.profile.displayName` in addition to the entity ref.
+ * in React components. In non-React contexts such as sort comparators or
+ * data mappers, use {@link entityPresentationSnapshot}. These provide richer
+ * display names using `metadata.title` and `spec.profile.displayName` in
+ * addition to the entity ref.
*
* @public
**/
@@ -82,7 +83,8 @@ export function humanizeEntityRef(
* If neither of those are found or populated, fallback to `defaultName`.
*
* @deprecated Use {@link useEntityPresentation} or {@link EntityDisplayName}
- * in React components, or access the {@link entityPresentationApiRef} directly.
+ * in React components. In non-React contexts, use
+ * {@link entityPresentationSnapshot}.
*
* @param entity - Entity to convert.
* @param defaultName - If entity readable name is not available, `defaultName` will be returned.
diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx
index 01d1c7e1f3..82dd8e7309 100644
--- a/plugins/catalog-react/src/components/EntityTable/columns.tsx
+++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx
@@ -23,21 +23,9 @@ import {
import { OverflowTooltip, TableColumn } from '@backstage/core-components';
import { getEntityRelations } from '../../utils';
import { EntityRefLink, EntityRefLinks } from '../EntityRefLink';
-import { defaultEntityPresentation, EntityPresentationApi } from '../../apis';
+import { entityPresentationSnapshot, EntityPresentationApi } from '../../apis';
import { EntityTableColumnTitle } from './TitleColumn';
-function getEntityTitle(
- entityOrRef: Entity | CompoundEntityRef,
- context: { defaultKind?: string },
- entityPresentationApi?: EntityPresentationApi,
-): string {
- if (entityPresentationApi) {
- return entityPresentationApi.forEntity(entityOrRef as Entity, context)
- .snapshot.primaryTitle;
- }
- return defaultEntityPresentation(entityOrRef, context).primaryTitle;
-}
-
/** @public */
export const columnFactories = Object.freeze({
createEntityRefColumn(options: {
@@ -46,7 +34,11 @@ export const columnFactories = Object.freeze({
}): TableColumn {
const { defaultKind, entityPresentationApi } = options;
function formatContent(entity: T): string {
- return getEntityTitle(entity, { defaultKind }, entityPresentationApi);
+ return entityPresentationSnapshot(
+ entity,
+ { defaultKind },
+ entityPresentationApi,
+ ).primaryTitle;
}
return {
@@ -96,7 +88,14 @@ export const columnFactories = Object.freeze({
function formatContent(entity: T): string {
return getRelations(entity)
- .map(r => getEntityTitle(r, { defaultKind }, entityPresentationApi))
+ .map(
+ r =>
+ entityPresentationSnapshot(
+ r,
+ { defaultKind },
+ entityPresentationApi,
+ ).primaryTitle,
+ )
.join(', ');
}
diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx
index 6c55ff21a3..b021aae0e1 100644
--- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx
+++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx
@@ -16,7 +16,6 @@
import {
ANNOTATION_EDIT_URL,
ANNOTATION_VIEW_URL,
- CompoundEntityRef,
Entity,
RELATION_OWNED_BY,
RELATION_PART_OF,
@@ -30,8 +29,8 @@ import {
WarningPanel,
} from '@backstage/core-components';
import {
- defaultEntityPresentation,
entityPresentationApiRef,
+ entityPresentationSnapshot,
getEntityRelations,
useEntityList,
useStarredEntities,
@@ -73,21 +72,13 @@ export interface CatalogTableProps {
subtitle?: string;
}
-function getTitle(
- entityOrRef: Entity | CompoundEntityRef,
- context: { defaultKind?: string },
- api?: EntityPresentationApi,
-): string {
- if (api) {
- const ref =
- 'metadata' in entityOrRef ? entityOrRef : stringifyEntityRef(entityOrRef);
- return api.forEntity(ref, context).snapshot.primaryTitle;
- }
- return defaultEntityPresentation(entityOrRef, context).primaryTitle;
-}
-
const sortEntities = (entities: Entity[], api?: EntityPresentationApi) => {
- return sortBy(entities, e => getTitle(e, { defaultKind: 'Component' }, api));
+ return sortBy(
+ entities,
+ e =>
+ entityPresentationSnapshot(e, { defaultKind: 'Component' }, api)
+ .primaryTitle,
+ );
};
/**
@@ -307,14 +298,26 @@ function toEntityRow(entity: Entity, api?: EntityPresentationApi) {
// This name is here for backwards compatibility mostly; the
// presentation of refs in the table should in general be handled with
// EntityRefLink / EntityName components
- name: getTitle(entity, { defaultKind: 'Component' }, api),
+ name: entityPresentationSnapshot(
+ entity,
+ { defaultKind: 'Component' },
+ api,
+ ).primaryTitle,
entityRef: stringifyEntityRef(entity),
ownedByRelationsTitle: ownedByRelations
- .map(r => getTitle(r, { defaultKind: 'group' }, api))
+ .map(
+ r =>
+ entityPresentationSnapshot(r, { defaultKind: 'group' }, api)
+ .primaryTitle,
+ )
.join(', '),
ownedByRelations,
partOfSystemRelationTitle: partOfSystemRelations
- .map(r => getTitle(r, { defaultKind: 'system' }, api))
+ .map(
+ r =>
+ entityPresentationSnapshot(r, { defaultKind: 'system' }, api)
+ .primaryTitle,
+ )
.join(', '),
partOfSystemRelations,
},
diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx
index b436f56427..efb1a06822 100644
--- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx
+++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx
@@ -186,7 +186,7 @@ export const TemplateFormPreviewer = ({
severity: 'error',
}),
),
- [catalogApi],
+ [catalogApi, entityPresentationApi, alertApi],
);
const handleSelectChange = useCallback(
diff --git a/plugins/techdocs/src/home/components/Tables/helpers.ts b/plugins/techdocs/src/home/components/Tables/helpers.ts
index 4f2424340e..b0b5c41fcd 100644
--- a/plugins/techdocs/src/home/components/Tables/helpers.ts
+++ b/plugins/techdocs/src/home/components/Tables/helpers.ts
@@ -14,13 +14,9 @@
* limitations under the License.
*/
+import { RELATION_OWNED_BY, Entity } from '@backstage/catalog-model';
import {
- RELATION_OWNED_BY,
- Entity,
- stringifyEntityRef,
-} from '@backstage/catalog-model';
-import {
- defaultEntityPresentation,
+ entityPresentationSnapshot,
getEntityRelations,
type EntityPresentationApi,
} from '@backstage/plugin-catalog-react';
@@ -54,15 +50,14 @@ export function entitiesToDocsMapper(
}),
ownedByRelations,
ownedByRelationsTitle: ownedByRelations
- .map(r => {
- if (entityPresentationApi) {
- return entityPresentationApi.forEntity(stringifyEntityRef(r), {
- defaultKind: 'group',
- }).snapshot.primaryTitle;
- }
- return defaultEntityPresentation(r, { defaultKind: 'group' })
- .primaryTitle;
- })
+ .map(
+ r =>
+ entityPresentationSnapshot(
+ r,
+ { defaultKind: 'group' },
+ entityPresentationApi,
+ ).primaryTitle,
+ )
.join(', '),
},
};