From ff9d717c71a3725acbbe308ece447f58c8dad311 Mon Sep 17 00:00:00 2001 From: Toby Harradine Date: Wed, 16 Apr 2025 11:47:41 +1000 Subject: [PATCH 1/4] fix: enable markdown rendering for EntityPicker description Signed-off-by: Toby Harradine --- .../fields/EntityPicker/EntityPicker.tsx | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index 8a1b0854cf..a471e69457 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -29,8 +29,10 @@ import { catalogApiRef, entityPresentationApiRef, } from '@backstage/plugin-catalog-react'; +import { MarkdownContent } from '@backstage/core-components'; import TextField from '@material-ui/core/TextField'; import FormControl from '@material-ui/core/FormControl'; +import { makeStyles } from '@material-ui/core/styles'; import Autocomplete, { AutocompleteChangeReason, createFilterOptions, @@ -49,6 +51,18 @@ import { scaffolderTranslationRef } from '../../../translation'; export { EntityPickerSchema } from './schema'; +const useStyles = makeStyles(theme => ({ + markdownDescription: { + fontSize: theme.typography.caption.fontSize, + margin: 0, + color: theme.palette.text.secondary, + '& :first-child': { + margin: 0, + marginTop: '3px', // to keep the standard browser padding + }, + }, +})); + /** * The underlying component that is rendered in the form for the `EntityPicker` * field extension. @@ -57,6 +71,7 @@ export { EntityPickerSchema } from './schema'; */ export const EntityPicker = (props: EntityPickerProps) => { const { t } = useTranslationRef(scaffolderTranslationRef); + const classes = useStyles(); const { onChange, schema: { @@ -210,8 +225,6 @@ export const EntityPicker = (props: EntityPickerProps) => { {...params} label={title} margin="dense" - helperText={description} - FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} variant="outlined" required={required} disabled={isDisabled} @@ -226,6 +239,13 @@ export const EntityPicker = (props: EntityPickerProps) => { })} ListboxComponent={VirtualizedListbox} /> + {description && ( + + )} ); }; From 205df8bd61aa239d956ac5e75bf729c446d3a2b6 Mon Sep 17 00:00:00 2001 From: Toby Harradine Date: Wed, 16 Apr 2025 13:00:54 +1000 Subject: [PATCH 2/4] test: add test for markdown description rendering in EntityPicker Signed-off-by: Toby Harradine --- .../fields/EntityPicker/EntityPicker.test.tsx | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index ab3092e3ed..2522283230 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -28,6 +28,13 @@ import { EntityPickerProps } from './schema'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import * as backstageCore from '@backstage/core-components'; + +// Mock MarkdownContent to test that it's called with the right props +jest.mock('@backstage/core-components', () => ({ + ...jest.requireActual('@backstage/core-components'), + MarkdownContent: jest.fn(() => null), +})); const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', @@ -812,4 +819,39 @@ describe('', () => { expect(onChange).toHaveBeenCalledWith(undefined); }); }); + + describe('with markdown description', () => { + beforeEach(() => { + jest.clearAllMocks(); + uiSchema = { 'ui:options': {} }; + props = { + onChange, + schema: { + description: '**Bold text** and *italic text*', + }, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('renders description as markdown', async () => { + await renderInTestApp( + + + , + ); + + expect(backstageCore.MarkdownContent).toHaveBeenCalledWith( + expect.objectContaining({ + content: '**Bold text** and *italic text*', + linkTarget: '_blank', + }), + expect.anything(), + ); + }); + }); }); From d7da01d98b6496fbe046d3d99f1593cc448389e5 Mon Sep 17 00:00:00 2001 From: Toby Harradine Date: Wed, 16 Apr 2025 14:01:04 +1000 Subject: [PATCH 3/4] add changeset for EntityPicker markdown fix Signed-off-by: Toby Harradine --- .changeset/green-trainers-float.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/green-trainers-float.md diff --git a/.changeset/green-trainers-float.md b/.changeset/green-trainers-float.md new file mode 100644 index 0000000000..537fe01736 --- /dev/null +++ b/.changeset/green-trainers-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Fix EntityPicker field to render description as markdown, matching other form components in the system. From 97f7592ea945c76f7305dfe315a69f6ebaa2c260 Mon Sep 17 00:00:00 2001 From: Toby Harradine Date: Wed, 16 Apr 2025 20:38:42 +1000 Subject: [PATCH 4/4] refactor: convert EntityPicker to use ScaffolderField component Signed-off-by: Toby Harradine --- .../fields/EntityPicker/EntityPicker.test.tsx | 42 ------------------- .../fields/EntityPicker/EntityPicker.tsx | 35 ++++------------ 2 files changed, 8 insertions(+), 69 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index 2522283230..ab3092e3ed 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -28,13 +28,6 @@ import { EntityPickerProps } from './schema'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; -import * as backstageCore from '@backstage/core-components'; - -// Mock MarkdownContent to test that it's called with the right props -jest.mock('@backstage/core-components', () => ({ - ...jest.requireActual('@backstage/core-components'), - MarkdownContent: jest.fn(() => null), -})); const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', @@ -819,39 +812,4 @@ describe('', () => { expect(onChange).toHaveBeenCalledWith(undefined); }); }); - - describe('with markdown description', () => { - beforeEach(() => { - jest.clearAllMocks(); - uiSchema = { 'ui:options': {} }; - props = { - onChange, - schema: { - description: '**Bold text** and *italic text*', - }, - required, - uiSchema, - rawErrors, - formData, - } as unknown as FieldProps; - - catalogApi.getEntities.mockResolvedValue({ items: entities }); - }); - - it('renders description as markdown', async () => { - await renderInTestApp( - - - , - ); - - expect(backstageCore.MarkdownContent).toHaveBeenCalledWith( - expect.objectContaining({ - content: '**Bold text** and *italic text*', - linkTarget: '_blank', - }), - expect.anything(), - ); - }); - }); }); diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index a471e69457..b15c197945 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -29,10 +29,7 @@ import { catalogApiRef, entityPresentationApiRef, } from '@backstage/plugin-catalog-react'; -import { MarkdownContent } from '@backstage/core-components'; import TextField from '@material-ui/core/TextField'; -import FormControl from '@material-ui/core/FormControl'; -import { makeStyles } from '@material-ui/core/styles'; import Autocomplete, { AutocompleteChangeReason, createFilterOptions, @@ -48,21 +45,10 @@ import { import { VirtualizedListbox } from '../VirtualizedListbox'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../../translation'; +import { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha'; export { EntityPickerSchema } from './schema'; -const useStyles = makeStyles(theme => ({ - markdownDescription: { - fontSize: theme.typography.caption.fontSize, - margin: 0, - color: theme.palette.text.secondary, - '& :first-child': { - margin: 0, - marginTop: '3px', // to keep the standard browser padding - }, - }, -})); - /** * The underlying component that is rendered in the form for the `EntityPicker` * field extension. @@ -71,7 +57,6 @@ const useStyles = makeStyles(theme => ({ */ export const EntityPicker = (props: EntityPickerProps) => { const { t } = useTranslationRef(scaffolderTranslationRef); - const classes = useStyles(); const { onChange, schema: { @@ -83,6 +68,7 @@ export const EntityPicker = (props: EntityPickerProps) => { rawErrors, formData, idSchema, + errors, } = props; const catalogFilter = buildCatalogFilter(uiSchema); const defaultKind = uiSchema['ui:options']?.defaultKind; @@ -194,10 +180,12 @@ export const EntityPicker = (props: EntityPickerProps) => { }, [entities, onChange, selectedEntity, required, allowArbitraryValues]); return ( - 0 && !formData} + disabled={isDisabled} + errors={errors} > { })} ListboxComponent={VirtualizedListbox} /> - {description && ( - - )} - + ); };