diff --git a/.changeset/slow-dolls-type.md b/.changeset/slow-dolls-type.md
new file mode 100644
index 0000000000..d2626f3ab3
--- /dev/null
+++ b/.changeset/slow-dolls-type.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': minor
+---
+
+Changed the way to display entities in EntityPicker to use entityPresentationApi instead of humanizeEntityRef
diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx
index 8016a6ae83..dcb52a3949 100644
--- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx
+++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx
@@ -16,14 +16,18 @@
import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
-import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
+import {
+ CatalogApi,
+ catalogApiRef,
+ entityPresentationApiRef,
+} from '@backstage/plugin-catalog-react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
-
import { fireEvent, screen } from '@testing-library/react';
import React from 'react';
import { EntityPicker } from './EntityPicker';
import { EntityPickerProps } from './schema';
import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react';
+import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog';
const makeEntity = (kind: string, namespace: string, name: string): Entity => ({
apiVersion: 'scaffolder.backstage.io/v1beta3',
@@ -50,6 +54,7 @@ describe('', () => {
getLocationByRef: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
+
let Wrapper: React.ComponentType>;
beforeEach(() => {
@@ -59,7 +64,15 @@ describe('', () => {
];
Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
+
{children}
);
@@ -90,7 +103,12 @@ describe('', () => {
);
expect(catalogApi.getEntities).toHaveBeenCalledWith({
- fields: ['metadata.name', 'metadata.namespace', 'kind'],
+ fields: [
+ 'metadata.name',
+ 'metadata.namespace',
+ 'metadata.title',
+ 'kind',
+ ],
filter: undefined,
});
});
diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx
index 69e1b5efad..176f6e9ce0 100644
--- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx
+++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx
@@ -24,13 +24,16 @@ import {
} from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import {
+ EntityDisplayName,
+ EntityRefPresentationSnapshot,
catalogApiRef,
- humanizeEntityRef,
+ entityPresentationApiRef,
} from '@backstage/plugin-catalog-react';
import TextField from '@material-ui/core/TextField';
import FormControl from '@material-ui/core/FormControl';
import Autocomplete, {
AutocompleteChangeReason,
+ createFilterOptions,
} from '@material-ui/lab/Autocomplete';
import React, { useCallback, useEffect } from 'react';
import useAsync from 'react-use/esm/useAsync';
@@ -65,31 +68,55 @@ export const EntityPicker = (props: EntityPickerProps) => {
uiSchema['ui:options']?.defaultNamespace || undefined;
const catalogApi = useApi(catalogApiRef);
+ const entityPresentationApi = useApi(entityPresentationApiRef);
const { value: entities, loading } = useAsync(async () => {
- const fields = ['metadata.name', 'metadata.namespace', 'kind'];
+ const fields = [
+ 'metadata.name',
+ 'metadata.namespace',
+ 'metadata.title',
+ 'kind',
+ ];
const { items } = await catalogApi.getEntities(
catalogFilter
? { filter: catalogFilter, fields }
: { filter: undefined, fields },
);
- return items;
+
+ const entityRefToPresentation = new Map<
+ string,
+ EntityRefPresentationSnapshot
+ >(
+ await Promise.all(
+ items.map(async item => {
+ const presentation = await entityPresentationApi.forEntity(item)
+ .promise;
+ return [stringifyEntityRef(item), presentation] as [
+ string,
+ EntityRefPresentationSnapshot,
+ ];
+ }),
+ ),
+ );
+
+ return { catalogEntities: items, entityRefToPresentation };
});
+
const allowArbitraryValues =
uiSchema['ui:options']?.allowArbitraryValues ?? true;
const getLabel = useCallback(
- (ref: string) => {
+ (freeSoloValue: string) => {
try {
- return humanizeEntityRef(
- parseEntityRef(ref, { defaultKind, defaultNamespace }),
- {
- defaultKind,
- defaultNamespace,
- },
- );
+ // Will throw if defaultKind or defaultNamespace are not set
+ const parsedRef = parseEntityRef(freeSoloValue, {
+ defaultKind,
+ defaultNamespace,
+ });
+
+ return stringifyEntityRef(parsedRef);
} catch (err) {
- return ref;
+ return freeSoloValue;
}
},
[defaultKind, defaultNamespace],
@@ -129,12 +156,12 @@ export const EntityPicker = (props: EntityPickerProps) => {
// Since free solo can be enabled, attempt to parse as a full entity ref first, then fall
// back to the given value.
const selectedEntity =
- entities?.find(e => stringifyEntityRef(e) === formData) ??
+ entities?.catalogEntities.find(e => stringifyEntityRef(e) === formData) ??
(allowArbitraryValues && formData ? getLabel(formData) : '');
useEffect(() => {
- if (entities?.length === 1 && selectedEntity === '') {
- onChange(stringifyEntityRef(entities[0]));
+ if (entities?.catalogEntities.length === 1 && selectedEntity === '') {
+ onChange(stringifyEntityRef(entities.catalogEntities[0]));
}
}, [entities, onChange, selectedEntity]);
@@ -145,17 +172,18 @@ export const EntityPicker = (props: EntityPickerProps) => {
error={rawErrors?.length > 0 && !formData}
>
// option can be a string due to freeSolo.
typeof option === 'string'
? option
- : humanizeEntityRef(option, { defaultKind, defaultNamespace })!
+ : entities?.entityRefToPresentation.get(stringifyEntityRef(option))
+ ?.entityRef!
}
autoSelect
freeSolo={allowArbitraryValues}
@@ -171,6 +199,12 @@ export const EntityPicker = (props: EntityPickerProps) => {
InputProps={params.InputProps}
/>
)}
+ renderOption={option => }
+ filterOptions={createFilterOptions({
+ stringify: option =>
+ entities?.entityRefToPresentation.get(stringifyEntityRef(option))
+ ?.primaryTitle!,
+ })}
/>
);
diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx
index 57ca8e53ab..79105e05c3 100644
--- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx
+++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx
@@ -16,11 +16,16 @@
import { type EntityFilterQuery } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
-import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
+import {
+ CatalogApi,
+ catalogApiRef,
+ entityPresentationApiRef,
+} from '@backstage/plugin-catalog-react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react';
import React from 'react';
import { OwnerPicker } from './OwnerPicker';
+import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog';
const makeEntity = (kind: string, namespace: string, name: string): Entity => ({
apiVersion: 'backstage.io/v1beta1',
@@ -64,7 +69,15 @@ describe('', () => {
];
Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
+
{children}
);
@@ -99,7 +112,12 @@ describe('', () => {
filter: {
kind: ['Group', 'User'],
},
- fields: ['metadata.name', 'metadata.namespace', 'kind'],
+ fields: [
+ 'metadata.name',
+ 'metadata.namespace',
+ 'metadata.title',
+ 'kind',
+ ],
}),
);
});
@@ -132,7 +150,12 @@ describe('', () => {
filter: {
kind: ['User'],
},
- fields: ['metadata.name', 'metadata.namespace', 'kind'],
+ fields: [
+ 'metadata.name',
+ 'metadata.namespace',
+ 'metadata.title',
+ 'kind',
+ ],
}),
);
});