chore: starting to add async validation to the next scaffolder
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -25,7 +25,7 @@ export type CustomFieldValidator<TFieldReturnValue> = (
|
||||
data: TFieldReturnValue,
|
||||
field: FieldValidation,
|
||||
context: { apiHolder: ApiHolder },
|
||||
) => void;
|
||||
) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Type for the Custom Field Extension with the
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useApiHolder } from '@backstage/core-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import {
|
||||
Stepper as MuiStepper,
|
||||
@@ -21,11 +22,12 @@ import {
|
||||
Button,
|
||||
makeStyles,
|
||||
} from '@material-ui/core';
|
||||
import { withTheme } from '@rjsf/core';
|
||||
import { FieldValidation, FormValidation, withTheme } from '@rjsf/core';
|
||||
import { Theme as MuiTheme } from '@rjsf/material-ui';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { FieldExtensionOptions } from '../../../extensions';
|
||||
import { TemplateParameterSchema } from '../../../types';
|
||||
import { createAsyncValidator } from './createAsyncValidators';
|
||||
import { useTemplateSchema } from './useTemplateSchema';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
@@ -51,6 +53,7 @@ const Form = withTheme(MuiTheme);
|
||||
|
||||
export const Stepper = (props: StepperProps) => {
|
||||
const { steps } = useTemplateSchema(props.manifest);
|
||||
const apiHolder = useApiHolder();
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [formState, setFormState] = useState({});
|
||||
const styles = useStyles();
|
||||
@@ -61,11 +64,31 @@ export const Stepper = (props: StepperProps) => {
|
||||
);
|
||||
}, [props.extensions]);
|
||||
|
||||
const validators = useMemo(() => {
|
||||
return Object.fromEntries(
|
||||
props.extensions.map(({ name, validation }) => [name, validation]),
|
||||
);
|
||||
}, [props.extensions]);
|
||||
|
||||
const validator = useMemo(() => {
|
||||
return createAsyncValidator(steps[activeStep].originalSchema, validators, {
|
||||
apiHolder,
|
||||
});
|
||||
}, [steps, activeStep, validators, apiHolder]);
|
||||
|
||||
const handleBack = () => {
|
||||
setActiveStep(prevActiveStep => prevActiveStep - 1);
|
||||
};
|
||||
|
||||
const handleNext = ({ formData }: { formData: JsonObject }) => {
|
||||
const handleNext = async ({ formData }: { formData: JsonObject }) => {
|
||||
console.log('running validation');
|
||||
|
||||
const errors = await validator(formData, {
|
||||
addError: () => {},
|
||||
__errors: [],
|
||||
} as any);
|
||||
|
||||
console.log('errors');
|
||||
setActiveStep(prevActiveStep => prevActiveStep + 1);
|
||||
setFormState(current => ({ ...current, ...formData }));
|
||||
};
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormValidation } from '@rjsf/core';
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { ApiHolder } from '@backstage/core-plugin-api';
|
||||
import { CustomFieldValidator } from '../../../extensions';
|
||||
|
||||
function isObject(obj: unknown): obj is JsonObject {
|
||||
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
|
||||
}
|
||||
|
||||
export const createAsyncValidator = (
|
||||
rootSchema: JsonObject,
|
||||
validators: Record<string, undefined | CustomFieldValidator<unknown>>,
|
||||
context: {
|
||||
apiHolder: ApiHolder;
|
||||
},
|
||||
) => {
|
||||
async function validate(
|
||||
schema: JsonObject,
|
||||
formData: JsonObject,
|
||||
errors: FormValidation,
|
||||
) {
|
||||
const schemaProps = schema.properties;
|
||||
const customObject = schema.type === 'object' && schemaProps === undefined;
|
||||
|
||||
if (!isObject(schemaProps) && !customObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (schemaProps) {
|
||||
for (const [key, propData] of Object.entries(formData)) {
|
||||
const propValidation = errors[key];
|
||||
|
||||
if (isObject(propData)) {
|
||||
const propSchemaProps = schemaProps[key];
|
||||
if (isObject(propSchemaProps)) {
|
||||
await validate(
|
||||
propSchemaProps,
|
||||
propData as JsonObject,
|
||||
propValidation as FormValidation,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const propSchema = schemaProps[key];
|
||||
const fieldName =
|
||||
isObject(propSchema) && (propSchema['ui:field'] as string);
|
||||
if (fieldName && typeof validators[fieldName] === 'function') {
|
||||
await validators[fieldName]!(
|
||||
propData as JsonValue,
|
||||
propValidation,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (customObject) {
|
||||
const fieldName = schema['ui:field'] as string;
|
||||
if (fieldName && typeof validators[fieldName] === 'function') {
|
||||
await validators[fieldName]!(formData, errors, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return async (formData: JsonObject, errors: FormValidation) => {
|
||||
await validate(rootSchema, formData, errors);
|
||||
return errors;
|
||||
};
|
||||
};
|
||||
@@ -24,6 +24,7 @@ export const useTemplateSchema = (
|
||||
): {
|
||||
steps: {
|
||||
uiSchema: UiSchema;
|
||||
originalSchema: JsonObject;
|
||||
schema: JsonObject;
|
||||
title: string;
|
||||
description?: string;
|
||||
@@ -33,6 +34,7 @@ export const useTemplateSchema = (
|
||||
const steps = manifest.steps.map(({ title, description, schema }) => ({
|
||||
title,
|
||||
description,
|
||||
originalSchema: schema,
|
||||
...extractSchemaFromStep(schema),
|
||||
}));
|
||||
|
||||
@@ -45,6 +47,7 @@ export const useTemplateSchema = (
|
||||
// Then filter out the properties that are not enabled with feature flag
|
||||
.map(step => ({
|
||||
...step,
|
||||
|
||||
schema: {
|
||||
...step.schema,
|
||||
// Title is rendered at the top of the page, so let's ignore this from jsonschemaform
|
||||
|
||||
Reference in New Issue
Block a user