From 929cb2611873cee3d562530e0313b2ca9c727aa3 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Sat, 20 Apr 2024 12:09:43 -0300 Subject: [PATCH] feat: MultiEntityPicker uses entityPresentationApi to display entity instead of humanizeEntityRef Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../MultiEntityPicker.test.tsx | 17 ++++- .../MultiEntityPicker/MultiEntityPicker.tsx | 76 +++++++++++-------- 2 files changed, 60 insertions(+), 33 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx index bc0a5ed32d..4be07b139d 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx @@ -16,7 +16,11 @@ 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'; @@ -24,6 +28,7 @@ import React from 'react'; import { MultiEntityPicker } from './MultiEntityPicker'; import { MultiEntityPickerProps } 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', @@ -59,7 +64,15 @@ describe('', () => { ]; Wrapper = ({ children }: { children?: React.ReactNode }) => ( - + {children} ); diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index d9804a8cba..0a0fcfdc2b 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -25,7 +25,9 @@ import { import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef, - humanizeEntityRef, + entityPresentationApiRef, + EntityRefPresentationSnapshot, + EntityDisplayName, } from '@backstage/plugin-catalog-react'; import TextField from '@material-ui/core/TextField'; import FormControl from '@material-ui/core/FormControl'; @@ -64,40 +66,35 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { uiSchema['ui:options']?.defaultNamespace || undefined; const catalogApi = useApi(catalogApiRef); - + const entityPresentationApi = useApi(entityPresentationApiRef); const { value: entities, loading } = useAsync(async () => { const { items } = await catalogApi.getEntities( catalogFilter ? { filter: catalogFilter } : undefined, ); - return items; + const primaryTitles: string[] = []; + for (const item of items) { + const entityPresentation = (await entityPresentationApi.forEntity(item) + ?.promise) as EntityRefPresentationSnapshot[]; + entityPresentation.map(e => primaryTitles.push(e.primaryTitle)); + } + + return { items, primaryTitles }; }); const allowArbitraryValues = uiSchema['ui:options']?.allowArbitraryValues ?? true; - const getLabel = useCallback( - (ref: string) => { - try { - return humanizeEntityRef( - parseEntityRef(ref, { defaultKind, defaultNamespace }), - { - defaultKind, - defaultNamespace, - }, - ); - } catch (err) { - return ref; - } - }, - [defaultKind, defaultNamespace], - ); - const onSelect = useCallback( (_: any, refs: (string | Entity)[], reason: AutocompleteChangeReason) => { const values = refs .map(ref => { if (typeof ref !== 'string') { // if ref does not exist: pass 'undefined' to trigger validation for required value - return ref ? stringifyEntityRef(ref as Entity) : undefined; + return ref + ? entityPresentationApi.forEntity(ref, { + defaultKind, + defaultNamespace, + }).snapshot.entityRef + : undefined; } if (reason === 'blur' || reason === 'create-option') { // Add in default namespace, etc. @@ -126,14 +123,21 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { onChange(values); }, - [onChange, formData, defaultKind, defaultNamespace, allowArbitraryValues], + [ + onChange, + formData, + defaultKind, + defaultNamespace, + allowArbitraryValues, + entityPresentationApi, + ], ); useEffect(() => { - if (entities?.length === 1) { - onChange([stringifyEntityRef(entities[0])]); + if (entities?.items?.length === 1) { + onChange([stringifyEntityRef(entities.items[0])]); } - }, [entities, onChange]); + }, [entities?.items, onChange]); return ( { formData && formData.includes(stringifyEntityRef(e)), - ) ?? (allowArbitraryValues && formData ? formData.map(getLabel) : []) + ) ?? + (allowArbitraryValues && formData + ? entities?.primaryTitles || [] + : []) } loading={loading} onChange={onSelect} - options={entities || []} + options={entities?.items || []} + renderOption={option => } getOptionLabel={option => // option can be a string due to freeSolo. typeof option === 'string' ? option - : humanizeEntityRef(option, { defaultKind, defaultNamespace })! + : entityPresentationApi.forEntity(option, { + defaultKind, + defaultNamespace, + }).snapshot.entityRef! } autoSelect freeSolo={allowArbitraryValues} @@ -170,7 +181,10 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { label={title} margin="dense" helperText={description} - FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} + FormHelperTextProps={{ + margin: 'dense', + style: { marginLeft: 0 }, + }} variant="outlined" required={required} InputProps={params.InputProps}