From adb1b01e327f13950ae11217d2cc35537f9936e3 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Wed, 16 Nov 2022 14:18:34 +0000 Subject: [PATCH 1/4] give the ability to supply a transformErrors function to the Stepper form Signed-off-by: Paul Cowan --- .changeset/sour-flowers-care.md | 5 ++ plugins/scaffolder/src/next/Router/Router.tsx | 2 + .../Stepper/Stepper.test.tsx | 47 +++++++++++++++++++ .../TemplateWizardPage/Stepper/Stepper.tsx | 15 ++++++ .../TemplateWizardPage/TemplateWizardPage.tsx | 3 ++ 5 files changed, 72 insertions(+) create mode 100644 .changeset/sour-flowers-care.md diff --git a/.changeset/sour-flowers-care.md b/.changeset/sour-flowers-care.md new file mode 100644 index 0000000000..8e1c634346 --- /dev/null +++ b/.changeset/sour-flowers-care.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +give the ability to supply a transform errors function to the Stepper form 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..10873b41c7 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,19 @@ export const Stepper = (props: StepperProps) => { onSubmit={handleNext} fields={extensions} showErrorList={false} + transformErrors={props.transformErrors} + // this is needed because handleNext is really triggering the validation + // which only happens onSubmit + onChange={({ formData, schema }) => { + if (props.transformErrors) { + validator.validateFormData( + formData, + schema, + undefined, + props.transformErrors, + ); + } + }} >