Merge pull request #17316 from backstage/blam/scaffolder/next/fixes
scaffolder/next: Added loading indicator for `AsyncValidation`
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
---
|
||||
|
||||
Add indication that the validators are running
|
||||
@@ -237,6 +237,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>
|
||||
|
||||
Reference in New Issue
Block a user