chore: added some more refactor to validate now TODO
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -24,7 +24,12 @@ import {
|
||||
useRouteRef,
|
||||
} from '@backstage/core';
|
||||
import { LinearProgress } from '@material-ui/core';
|
||||
import { FormValidation, IChangeEvent } from '@rjsf/core';
|
||||
import {
|
||||
Field,
|
||||
FieldValidation,
|
||||
FormValidation,
|
||||
IChangeEvent,
|
||||
} from '@rjsf/core';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import React, { useCallback, useContext, useState } from 'react';
|
||||
import { generatePath, useNavigate, Navigate } from 'react-router';
|
||||
@@ -34,7 +39,7 @@ import { scaffolderApiRef } from '../../api';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { MultistepJsonForm } from '../MultistepJsonForm';
|
||||
import { RepoUrlPicker, OwnerPicker } from '../fields';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import { JsonObject, JsonValue } from '@backstage/config';
|
||||
import { ExtensionContext } from '../../extensions';
|
||||
|
||||
const useTemplateParameterSchema = (templateName: string) => {
|
||||
@@ -55,7 +60,10 @@ function isObject(obj: unknown): obj is JsonObject {
|
||||
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
|
||||
}
|
||||
|
||||
export const createValidator = (rootSchema: JsonObject) => {
|
||||
export const createValidator = (
|
||||
rootSchema: JsonObject,
|
||||
validators: Record<string, (value: any, validation: FieldValidation) => void>,
|
||||
) => {
|
||||
function validate(
|
||||
schema: JsonObject,
|
||||
formData: JsonObject,
|
||||
@@ -67,7 +75,7 @@ export const createValidator = (rootSchema: JsonObject) => {
|
||||
}
|
||||
|
||||
for (const [key, propData] of Object.entries(formData)) {
|
||||
const propErrors = errors[key];
|
||||
const propValidation = errors[key];
|
||||
|
||||
if (isObject(propData)) {
|
||||
const propSchemaProps = schemaProps[key];
|
||||
@@ -75,27 +83,16 @@ export const createValidator = (rootSchema: JsonObject) => {
|
||||
validate(
|
||||
propSchemaProps,
|
||||
propData as JsonObject,
|
||||
propErrors as FormValidation,
|
||||
propValidation as FormValidation,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const propSchema = schemaProps[key];
|
||||
if (
|
||||
isObject(propSchema) &&
|
||||
propSchema['ui:field'] === 'RepoUrlPicker'
|
||||
) {
|
||||
try {
|
||||
const { host, searchParams } = new URL(`https://${propData}`);
|
||||
if (
|
||||
!host ||
|
||||
!searchParams.get('owner') ||
|
||||
!searchParams.get('repo')
|
||||
) {
|
||||
propErrors.addError('Incomplete repository location provided');
|
||||
}
|
||||
} catch {
|
||||
propErrors.addError('Unable to parse the Repository URL');
|
||||
}
|
||||
const fieldName =
|
||||
isObject(propSchema) && (propSchema['ui:field'] as string);
|
||||
const validator = fieldName && validators[fieldName];
|
||||
if (validator) {
|
||||
validator(propData, propValidation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,12 +172,18 @@ export const TemplatePage = () => {
|
||||
return <Navigate to={rootLink()} />;
|
||||
}
|
||||
|
||||
const customFields = state!.fields.reduce((acc, next) => {
|
||||
acc[next.name] = next.component;
|
||||
return acc;
|
||||
}, {} as Record<string, any>);
|
||||
const { components, validation } = state!.fields.reduce(
|
||||
(acc, next) => {
|
||||
acc.components[next.name] = next.component;
|
||||
acc.validation[next.name] = next.validation;
|
||||
return acc;
|
||||
},
|
||||
{ components: {}, validation: {} } as {
|
||||
components: Record<string, Field>;
|
||||
validation: Record<string, (data: JsonValue, f: FieldValidation) => void>;
|
||||
},
|
||||
);
|
||||
|
||||
console.log(customFields);
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
@@ -202,7 +205,7 @@ export const TemplatePage = () => {
|
||||
>
|
||||
<MultistepJsonForm
|
||||
formData={formState}
|
||||
fields={customFields}
|
||||
fields={components}
|
||||
onChange={handleChange}
|
||||
onReset={handleFormReset}
|
||||
onFinish={handleCreate}
|
||||
@@ -218,7 +221,7 @@ export const TemplatePage = () => {
|
||||
|
||||
return {
|
||||
...step,
|
||||
validate: createValidator(step.schema),
|
||||
validate: createValidator(step.schema, validation),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -19,8 +19,9 @@ import {
|
||||
ComponentLoader,
|
||||
createReactExtension,
|
||||
getComponentData,
|
||||
attachComponentData,
|
||||
} from '@backstage/core';
|
||||
import { FieldValidation } from '@rjsf/core';
|
||||
import { FieldValidation, Field } from '@rjsf/core';
|
||||
import React from 'react';
|
||||
import { useMount } from 'react-use';
|
||||
|
||||
@@ -31,39 +32,37 @@ import {
|
||||
createCollector,
|
||||
} from '@backstage/core-api/src/extensions/traversal';
|
||||
|
||||
export type FieldExtensionOptions<T> = {
|
||||
export type FieldExtensionOptions = {
|
||||
name: string;
|
||||
component: ComponentLoader<T>;
|
||||
component: Field;
|
||||
validation: (data: JsonValue, field: FieldValidation) => void;
|
||||
};
|
||||
|
||||
export function createScaffolderFieldExtension<
|
||||
T extends (props: any) => JSX.Element | null
|
||||
>(options: FieldExtensionOptions<T>): Extension<T> {
|
||||
const componentInData =
|
||||
'lazy' in options.component
|
||||
? React.lazy(() =>
|
||||
options.component.lazy().then(component => ({ default: component })),
|
||||
)
|
||||
: options.component.sync;
|
||||
export function createScaffolderFieldExtension(
|
||||
options: FieldExtensionOptions,
|
||||
): Extension<Field> {
|
||||
const extensionData = {
|
||||
'scaffolder.extensions.field.v1': options,
|
||||
};
|
||||
|
||||
return createReactExtension({
|
||||
data: {
|
||||
'scaffolder.extensions.field.v1': {
|
||||
...options,
|
||||
component: componentInData,
|
||||
},
|
||||
const Result = (props: any) => <options.component {...props} />;
|
||||
|
||||
for (const [key, value] of Object.entries(extensionData)) {
|
||||
attachComponentData(Result, key, value);
|
||||
}
|
||||
return {
|
||||
expose() {
|
||||
return Result;
|
||||
},
|
||||
component: options.component,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export type ExtensionState = {
|
||||
fields: FieldExtensionOptions<unknown>[];
|
||||
fields: FieldExtensionOptions[];
|
||||
};
|
||||
export type RegisterFieldExtensionAction = {
|
||||
type: 'fields';
|
||||
data: FieldExtensionOptions<unknown>[];
|
||||
data: FieldExtensionOptions[];
|
||||
};
|
||||
|
||||
export type ExtensionAction = RegisterFieldExtensionAction;
|
||||
@@ -101,9 +100,9 @@ export const ExtensionCollector = ({
|
||||
discoverers: [childDiscoverer, routeElementDiscoverer],
|
||||
collectors: {
|
||||
fields: createCollector(
|
||||
() => [] as FieldExtensionOptions<unknown>[],
|
||||
() => [] as FieldExtensionOptions[],
|
||||
(acc, node) => {
|
||||
const data = getComponentData<FieldExtensionOptions<unknown>>(
|
||||
const data = getComponentData<FieldExtensionOptions>(
|
||||
node,
|
||||
'scaffolder.extensions.field.v1',
|
||||
);
|
||||
|
||||
@@ -54,9 +54,7 @@ export const scaffolderPlugin = createPlugin({
|
||||
export const RepoUrlPicker = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
// TODO: work out how to type this component part so we can enforce FieldComponent from RJSF
|
||||
component: {
|
||||
sync: RepoUrlPickerComponent,
|
||||
},
|
||||
component: RepoUrlPickerComponent,
|
||||
name: 'RepoUrlPicker',
|
||||
validation: (value: JsonValue, validation) => {
|
||||
try {
|
||||
@@ -73,9 +71,7 @@ export const RepoUrlPicker = scaffolderPlugin.provide(
|
||||
|
||||
export const OwnerPicker = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
component: {
|
||||
sync: OwnerPickerComponent,
|
||||
},
|
||||
component: OwnerPickerComponent,
|
||||
name: 'OwnerPicker',
|
||||
validation: () => {},
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user