diff --git a/.changeset/sour-flowers-care.md b/.changeset/sour-flowers-care.md new file mode 100644 index 0000000000..8524c4981c --- /dev/null +++ b/.changeset/sour-flowers-care.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Adds the ability to supply a `transformErrors` function to the `Stepper` for `/next` diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index bc6643cffe..33a5f8c2fe 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -11,6 +11,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import type { ErrorTransformer } from '@rjsf/utils'; import { Extension } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; @@ -262,6 +263,7 @@ export type NextRouterProps = { TaskPageComponent?: React_2.ComponentType<{}>; }; groups?: TemplateGroupFilter[]; + transformErrors?: ErrorTransformer; }; // @alpha diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index ebe00fdfb8..0fc1c9eceb 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -30,6 +30,7 @@ import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '../TemplateListPage/TemplateGroups'; import { nextSelectedTemplateRouteRef } from '../../routes'; import { SecretsContextProvider } from '../../components/secrets/SecretsContext'; +import type { ErrorTransformer } from '@rjsf/utils'; /** * The Props for the Scaffolder Router @@ -44,6 +45,7 @@ export type NextRouterProps = { TaskPageComponent?: React.ComponentType<{}>; }; groups?: TemplateGroupFilter[]; + transformErrors?: ErrorTransformer; }; /** diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx index 12ad05ab75..bf82fecf49 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx @@ -18,6 +18,7 @@ import { TemplateParameterSchema } from '../../../types'; import { Stepper } from './Stepper'; import { renderInTestApp } from '@backstage/test-utils'; import { act, fireEvent } from '@testing-library/react'; +import type { RJSFValidationError } from '@rjsf/utils'; describe('Stepper', () => { it('should render the step titles for each step of the manifest', async () => { @@ -140,4 +141,50 @@ describe('Stepper', () => { expect(getByText('im a custom field extension')).toBeInTheDocument(); }); + + it('should transform default error message', async () => { + const manifest: TemplateParameterSchema = { + steps: [ + { + title: 'Step 1', + schema: { + properties: { + postcode: { + type: 'string', + pattern: '[A-Z][0-9][A-Z] [0-9][A-Z][0-9]', + }, + }, + }, + }, + ], + title: 'transformErrors Form Test', + }; + + const transformErrors = (errors: RJSFValidationError[]) => { + return errors.map(err => + err.property === '.postcode' + ? { ...err, message: 'invalid postcode' } + : err, + ); + }; + + const { getByText, getByRole } = await renderInTestApp( + , + ); + + await fireEvent.change(getByRole('textbox', { name: 'postcode' }), { + target: { value: 'invalid' }, + }); + + await act(async () => { + await fireEvent.click(getByRole('button', { name: 'Review' })); + }); + + expect(getByText('invalid postcode')).toBeInTheDocument(); + }); }); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index 75f92916df..de06655aa1 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -36,6 +36,7 @@ import { useTemplateSchema } from './useTemplateSchema'; import { ReviewState } from './ReviewState'; import validator from '@rjsf/validator-ajv8'; import { selectedTemplateRouteRef } from '../../../routes'; +import type { ErrorTransformer } from '@rjsf/utils'; const useStyles = makeStyles(theme => ({ backButton: { @@ -56,6 +57,7 @@ export interface StepperProps { manifest: TemplateParameterSchema; extensions: NextFieldExtensionOptions[]; onComplete: (values: Record) => Promise; + transformErrors?: ErrorTransformer; } // TODO(blam): We require here, as the types in this package depend on @rjsf/core explicitly @@ -149,6 +151,7 @@ export const Stepper = (props: StepperProps) => { onSubmit={handleNext} fields={extensions} showErrorList={false} + transformErrors={props.transformErrors} >