From 032d21caa357c2ba21c5fd9a50b8be28a4325dc8 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 11 Apr 2023 14:46:07 +0200 Subject: [PATCH] feat: added progress bar and disable buttons when async validation is running Signed-off-by: blam --- .../next/components/Stepper/Stepper.test.tsx | 45 +++++++++++++++++++ .../src/next/components/Stepper/Stepper.tsx | 17 +++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx index 0b17c550a0..d743078d11 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx @@ -22,6 +22,7 @@ import type { RJSFValidationError } from '@rjsf/utils'; import { JsonValue } from '@backstage/types'; import { NextFieldExtensionComponentProps } from '../../extensions'; import { LayoutTemplate } from '../../../layouts'; +import { AsyncDependenciesBlock } from 'webpack'; describe('Stepper', () => { it('should render the step titles for each step of the manifest', async () => { @@ -237,6 +238,50 @@ describe('Stepper', () => { expect(getByText('im a custom field extension')).toBeInTheDocument(); }); + it('should disable the form with progress when async validators are running', async () => { + const MockComponent = () => { + return

im a custom field extension

; + }; + + const manifest: TemplateParameterSchema = { + title: 'Custom Fields', + steps: [ + { + title: 'Test', + schema: { + properties: { + name: { + type: 'string', + 'ui:field': 'Mock', + }, + }, + }, + }, + ], + }; + + const { getByRole } = await renderInTestApp( + new Promise(r => setTimeout(r, 1000)), + }, + ]} + onCreate={jest.fn()} + />, + ); + + await act(async () => { + await fireEvent.click(getByRole('button', { name: 'Review' })); + + expect(getByRole('progressbar')).toBeInTheDocument(); + expect(getByRole('button', { name: 'Review' })).toBeDisabled(); + }); + }); + it('should transform default error message', async () => { const manifest: TemplateParameterSchema = { steps: [ diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 51a00dfa64..37d0b0baef 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -21,6 +21,7 @@ import { StepLabel as MuiStepLabel, Button, makeStyles, + LinearProgress, } from '@material-ui/core'; import { type IChangeEvent } from '@rjsf/core-v5'; import { ErrorSchema } from '@rjsf/utils'; @@ -93,6 +94,7 @@ export const Stepper = (stepperProps: StepperProps) => { const { steps } = useTemplateSchema(props.manifest); const apiHolder = useApiHolder(); const [activeStep, setActiveStep] = useState(0); + const [isValidating, setIsValidating] = useState(false); const [formState, setFormState] = useFormDataFromQuery(props.initialState); const [errors, setErrors] = useState(); @@ -133,12 +135,13 @@ export const Stepper = (stepperProps: StepperProps) => { }: { formData?: Record; }) => { - // TODO(blam): What do we do about loading states, does each field extension get a chance - // to display it's own loading? Or should we grey out the entire form. setErrors(undefined); + setIsValidating(true); const returnedValidation = await validation(formData); + setIsValidating(false); + if (hasErrors(returnedValidation)) { setErrors(returnedValidation); } else { @@ -154,6 +157,7 @@ export const Stepper = (stepperProps: StepperProps) => { return ( <> + {isValidating && } {steps.map((step, index) => ( @@ -183,11 +187,16 @@ export const Stepper = (stepperProps: StepperProps) => { -