feat: added progress bar and disable buttons when async validation is running

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2023-04-11 14:46:07 +02:00
parent b457e89233
commit 032d21caa3
2 changed files with 58 additions and 4 deletions
@@ -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 <h1>im a custom field extension</h1>;
};
const manifest: TemplateParameterSchema = {
title: 'Custom Fields',
steps: [
{
title: 'Test',
schema: {
properties: {
name: {
type: 'string',
'ui:field': 'Mock',
},
},
},
},
],
};
const { getByRole } = await renderInTestApp(
<Stepper
manifest={manifest}
extensions={[
{
name: 'Mock',
component: MockComponent,
validation: async () => 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: [
@@ -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<undefined | FormValidation>();
@@ -133,12 +135,13 @@ export const Stepper = (stepperProps: StepperProps) => {
}: {
formData?: Record<string, JsonValue>;
}) => {
// 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 && <LinearProgress variant="indeterminate" />}
<MuiStepper activeStep={activeStep} alternativeLabel variant="elevation">
{steps.map((step, index) => (
<MuiStep key={index}>
@@ -183,11 +187,16 @@ export const Stepper = (stepperProps: StepperProps) => {
<Button
onClick={handleBack}
className={styles.backButton}
disabled={activeStep < 1}
disabled={activeStep < 1 || isValidating}
>
Back
</Button>
<Button variant="contained" color="primary" type="submit">
<Button
variant="contained"
color="primary"
type="submit"
disabled={isValidating}
>
{activeStep === steps.length - 1 ? reviewButtonText : 'Next'}
</Button>
</div>