From 6bba66c0d65e879c33d4357a96006813df50179d Mon Sep 17 00:00:00 2001 From: blam Date: Sat, 15 May 2021 22:10:26 +0200 Subject: [PATCH] chore: added some more refactor to validate now TODO Signed-off-by: blam --- .../components/TemplatePage/TemplatePage.tsx | 59 ++++++++++--------- plugins/scaffolder/src/extensions/index.tsx | 47 ++++++++------- plugins/scaffolder/src/plugin.ts | 8 +-- 3 files changed, 56 insertions(+), 58 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index fb9ae1fb3d..18e6962800 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -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 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 ; } - const customFields = state!.fields.reduce((acc, next) => { - acc[next.name] = next.component; - return acc; - }, {} as Record); + 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; + validation: Record void>; + }, + ); - console.log(customFields); return (
{ > { return { ...step, - validate: createValidator(step.schema), + validate: createValidator(step.schema, validation), }; })} /> diff --git a/plugins/scaffolder/src/extensions/index.tsx b/plugins/scaffolder/src/extensions/index.tsx index 32fa536507..ac83985411 100644 --- a/plugins/scaffolder/src/extensions/index.tsx +++ b/plugins/scaffolder/src/extensions/index.tsx @@ -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 = { +export type FieldExtensionOptions = { name: string; - component: ComponentLoader; + component: Field; validation: (data: JsonValue, field: FieldValidation) => void; }; -export function createScaffolderFieldExtension< - T extends (props: any) => JSX.Element | null ->(options: FieldExtensionOptions): Extension { - const componentInData = - 'lazy' in options.component - ? React.lazy(() => - options.component.lazy().then(component => ({ default: component })), - ) - : options.component.sync; +export function createScaffolderFieldExtension( + options: FieldExtensionOptions, +): Extension { + const extensionData = { + 'scaffolder.extensions.field.v1': options, + }; - return createReactExtension({ - data: { - 'scaffolder.extensions.field.v1': { - ...options, - component: componentInData, - }, + const Result = (props: any) => ; + + for (const [key, value] of Object.entries(extensionData)) { + attachComponentData(Result, key, value); + } + return { + expose() { + return Result; }, - component: options.component, - }); + }; } export type ExtensionState = { - fields: FieldExtensionOptions[]; + fields: FieldExtensionOptions[]; }; export type RegisterFieldExtensionAction = { type: 'fields'; - data: FieldExtensionOptions[]; + data: FieldExtensionOptions[]; }; export type ExtensionAction = RegisterFieldExtensionAction; @@ -101,9 +100,9 @@ export const ExtensionCollector = ({ discoverers: [childDiscoverer, routeElementDiscoverer], collectors: { fields: createCollector( - () => [] as FieldExtensionOptions[], + () => [] as FieldExtensionOptions[], (acc, node) => { - const data = getComponentData>( + const data = getComponentData( node, 'scaffolder.extensions.field.v1', ); diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index 460f3f0e60..85456b7e2e 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -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: () => {}, }),