From d362737143a2a9be3c17a2b79d67a9b29a408b7a Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Sep 2022 14:24:52 +0200 Subject: [PATCH] chore: reset some things so we can run it in paralell Signed-off-by: blam --- .../FieldOverrides/DescriptionField.tsx | 2 +- .../MultistepJsonForm/MultistepJsonForm.tsx | 9 ++- .../TemplatePage/createValidator.test.ts | 4 +- .../TemplatePage/createValidator.ts | 4 +- .../fields/EntityPicker/EntityPicker.test.tsx | 8 +-- .../fields/EntityPicker/EntityPicker.tsx | 13 ++-- .../fields/OwnerPicker/OwnerPicker.test.tsx | 4 +- .../fields/OwnerPicker/OwnerPicker.tsx | 8 +-- .../RepoUrlPicker/RepoUrlPicker.test.tsx | 15 ++--- .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 5 +- .../fields/RepoUrlPicker/validation.test.ts | 2 +- .../fields/RepoUrlPicker/validation.ts | 4 +- plugins/scaffolder/src/extensions/index.tsx | 34 ++++++++++- plugins/scaffolder/src/extensions/types.ts | 61 +++++++++++++++++-- .../TemplateWizardPage/Stepper/Stepper.tsx | 6 +- .../Stepper/createAsyncValidators.test.ts | 13 ++-- .../Stepper/createAsyncValidators.ts | 4 +- 17 files changed, 134 insertions(+), 62 deletions(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx index eded38beee..3c592860ef 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { MarkdownContent } from '@backstage/core-components'; -import { FieldProps } from '@rjsf/utils'; +import { FieldProps } from '@rjsf/core'; export const DescriptionField = ({ description }: FieldProps) => description && ; diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 7ab3007e99..8c71df67b6 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -29,9 +29,8 @@ import { useApi, featureFlagsApiRef, } from '@backstage/core-plugin-api'; -import { UiSchema } from '@rjsf/utils'; -import { FormProps, IChangeEvent, withTheme } from '@rjsf/core'; -import { Theme } from '@rjsf/material-ui'; +import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core'; +import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useState } from 'react'; import { transformSchemaToProps } from './schema'; import { Content, StructuredMetadataTable } from '@backstage/core-components'; @@ -39,6 +38,7 @@ import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; +const Form = withTheme(MuiTheme); type Step = { schema: JsonObject; title: string; @@ -123,7 +123,6 @@ export const MultistepJsonForm = (props: Props) => { finishButtonLabel, layouts, } = props; - const Form = withTheme(Theme); const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); const errorApi = useApi(errorApiRef); @@ -214,7 +213,7 @@ export const MultistepJsonForm = (props: Props) => { formData={formData} formContext={{ formData }} onChange={onChange} - onSubmit={(e: any) => { + onSubmit={e => { if (e.errors.length === 0) handleNext(); }} {...formProps} diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts b/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts index eea5efc296..f549c9e7f2 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts +++ b/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts @@ -17,7 +17,7 @@ import { createValidator } from './createValidator'; import { CustomFieldValidator } from '../../extensions'; import { ApiHolder } from '@backstage/core-plugin-api'; -import { FormValidation } from '@rjsf/utils'; +import { FormValidation } from '@rjsf/core'; describe('createValidator', () => { const validators: Record> = @@ -68,6 +68,6 @@ describe('createValidator', () => { /* THEN */ expect(result).not.toBeNull(); - expect(result.p1?.addError).toHaveBeenCalledTimes(1); + expect(result.p1.addError).toHaveBeenCalledTimes(1); }); }); diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts b/plugins/scaffolder/src/components/TemplatePage/createValidator.ts index d1c55009a9..9cd1489a02 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts +++ b/plugins/scaffolder/src/components/TemplatePage/createValidator.ts @@ -15,7 +15,7 @@ */ import { CustomFieldValidator } from '../../extensions'; -import { FormValidation } from '@rjsf/utils'; +import { FormValidation } from '@rjsf/core'; import { JsonObject, JsonValue } from '@backstage/types'; import { ApiHolder } from '@backstage/core-plugin-api'; @@ -62,7 +62,7 @@ export const createValidator = ( if (fieldName && typeof validators[fieldName] === 'function') { validators[fieldName]!( propData as JsonValue, - propValidation as FormValidation, + propValidation, context, ); } diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index ffc447577a..942eef9d82 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { FieldProps } from '@rjsf/utils'; +import { FieldProps } from '@rjsf/core'; import { fireEvent } from '@testing-library/react'; import React from 'react'; import { EntityPicker } from './EntityPicker'; @@ -39,7 +39,7 @@ describe('', () => { const rawErrors: string[] = []; const formData = undefined; - let props: FieldProps; + let props: FieldProps; const catalogApi: jest.Mocked = { getLocationById: jest.fn(), @@ -76,7 +76,7 @@ describe('', () => { uiSchema, rawErrors, formData, - } as any; + } as unknown as FieldProps; catalogApi.getEntities.mockResolvedValue({ items: entities }); }); @@ -117,7 +117,7 @@ describe('', () => { uiSchema, rawErrors, formData, - } as any; + } as unknown as FieldProps; catalogApi.getEntities.mockResolvedValue({ items: entities }); }); diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index a040320d79..e7883a4721 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -45,10 +45,7 @@ export interface EntityPickerUiOptions { * @public */ export const EntityPicker = ( - props: FieldExtensionComponentProps< - string | undefined, - EntityPickerUiOptions - >, + props: FieldExtensionComponentProps, ) => { const { onChange, @@ -59,9 +56,9 @@ export const EntityPicker = ( formData, idSchema, } = props; - const allowedKinds = uiSchema?.['ui:options']?.allowedKinds; - const defaultKind = uiSchema?.['ui:options']?.defaultKind; - const defaultNamespace = uiSchema?.['ui:options']?.defaultNamespace; + const allowedKinds = uiSchema['ui:options']?.allowedKinds; + const defaultKind = uiSchema['ui:options']?.defaultKind; + const defaultNamespace = uiSchema['ui:options']?.defaultNamespace; const catalogApi = useApi(catalogApiRef); @@ -102,7 +99,7 @@ export const EntityPicker = ( onChange={onSelect} options={entityRefs || []} autoSelect - freeSolo={uiSchema?.['ui:options']?.allowArbitraryValues ?? true} + freeSolo={uiSchema['ui:options']?.allowArbitraryValues ?? true} renderInput={params => ( ', () => { const rawErrors: string[] = []; const formData = undefined; - let props: FieldProps; + let props: FieldProps; const catalogApi: jest.Mocked = { getLocationById: jest.fn(), diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx index 8ce01fc3ef..3de2b12bd0 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx @@ -36,7 +36,7 @@ export interface OwnerPickerUiOptions { * @public */ export const OwnerPicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps, ) => { const { schema: { title = 'Owner', description = 'The owner of the component' }, @@ -44,18 +44,18 @@ export const OwnerPicker = ( ...restProps } = props; - const defaultNamespace = uiSchema?.['ui:options']?.defaultNamespace; + const defaultNamespace = uiSchema['ui:options']?.defaultNamespace; const ownerUiSchema = { ...uiSchema, 'ui:options': { - allowedKinds: (uiSchema?.['ui:options']?.allowedKinds || [ + allowedKinds: (uiSchema['ui:options']?.allowedKinds || [ 'Group', 'User', ]) as string[], defaultKind: 'Group', allowArbitraryValues: - uiSchema?.['ui:options']?.allowArbitraryValues ?? true, + uiSchema['ui:options']?.allowArbitraryValues ?? true, ...(defaultNamespace !== undefined ? { defaultNamespace } : {}), }, }; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index f28b4bc816..7fac9f7840 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -15,8 +15,7 @@ */ import React, { useContext } from 'react'; import { RepoUrlPicker } from './RepoUrlPicker'; -import { withTheme } from '@rjsf/core'; -import { Theme } from '@rjsf/material-ui'; +import Form from '@rjsf/core'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { scmIntegrationsApiRef, @@ -26,12 +25,12 @@ import { } from '@backstage/integration-react'; import { scaffolderApiRef } from '../../../api'; import { ScaffolderApi } from '../../../types'; + import { SecretsContextProvider, SecretsContext, } from '../../secrets/SecretsContext'; import { act, fireEvent } from '@testing-library/react'; -import { Field } from '@rjsf/utils'; describe('RepoUrlPicker', () => { const mockScaffolderApi: Partial = { @@ -51,8 +50,6 @@ describe('RepoUrlPicker', () => { getCredentials: jest.fn().mockResolvedValue({ token: 'abc123' }), }; - const Form = withTheme(Theme); - describe('happy path rendering', () => { it('should render the repo url picker with minimal props', async () => { const onSubmit = jest.fn(); @@ -68,7 +65,7 @@ describe('RepoUrlPicker', () => {
}} + fields={{ RepoUrlPicker: RepoUrlPicker }} onSubmit={onSubmit} /> @@ -102,13 +99,12 @@ describe('RepoUrlPicker', () => { > }} + fields={{ RepoUrlPicker: RepoUrlPicker }} /> , @@ -136,7 +132,6 @@ describe('RepoUrlPicker', () => { > { }, }, }} - fields={{ RepoUrlPicker: RepoUrlPicker as Field }} + fields={{ RepoUrlPicker: RepoUrlPicker }} /> diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 870ec9dd53..451df857e5 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -62,10 +62,7 @@ export interface RepoUrlPickerUiOptions { * @public */ export const RepoUrlPicker = ( - props: FieldExtensionComponentProps< - string | undefined, - RepoUrlPickerUiOptions - >, + props: FieldExtensionComponentProps, ) => { const { uiSchema, onChange, rawErrors, formData } = props; const [state, setState] = useState( diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts index b9c481957c..45e44b4072 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts @@ -15,7 +15,7 @@ */ import { repoPickerValidation } from './validation'; -import { FieldValidation } from '@rjsf/utils'; +import { FieldValidation } from '@rjsf/core'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/core-app-api'; import { ApiHolder } from '@backstage/core-plugin-api'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts index ae4f2ec1b9..99847f9e3b 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/utils'; +import { FieldValidation } from '@rjsf/core'; import { ApiHolder } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; @@ -26,7 +26,7 @@ import { scmIntegrationsApiRef } from '@backstage/integration-react'; * @public */ export const repoPickerValidation = ( - value: string | undefined, + value: string, validation: FieldValidation, context: { apiHolder: ApiHolder }, ) => { diff --git a/plugins/scaffolder/src/extensions/index.tsx b/plugins/scaffolder/src/extensions/index.tsx index 21a565bf46..38cabd5511 100644 --- a/plugins/scaffolder/src/extensions/index.tsx +++ b/plugins/scaffolder/src/extensions/index.tsx @@ -19,6 +19,9 @@ import { CustomFieldValidator, FieldExtensionOptions, FieldExtensionComponentProps, + NextCustomFieldValidator, + NextFieldExtensionOptions, + NextFieldExtensionComponentProps, } from './types'; import { Extension, attachComponentData } from '@backstage/core-plugin-api'; import { UIOptionsType } from '@rjsf/utils'; @@ -40,7 +43,7 @@ export type FieldExtensionComponent<_TReturnValue, _TInputProps> = () => null; */ export function createScaffolderFieldExtension< TReturnValue = unknown, - TInputProps extends UIOptionsType = {}, + TInputProps = unknown, >( options: FieldExtensionOptions, ): Extension> { @@ -59,6 +62,32 @@ export function createScaffolderFieldExtension< }; } +/** + * Method for creating field extensions that can be used in the scaffolder + * frontend form. + * @alpha + */ +export function createNextScaffolderFieldExtension< + TReturnValue = unknown, + TInputProps extends UIOptionsType = {}, +>( + options: FieldExtensionOptions, +): Extension> { + return { + expose() { + const FieldExtensionDataHolder: any = () => null; + + attachComponentData( + FieldExtensionDataHolder, + FIELD_EXTENSION_KEY, + options, + ); + + return FieldExtensionDataHolder; + }, + }; +} + /** * The Wrapping component for defining fields extensions inside * @@ -77,6 +106,9 @@ export type { CustomFieldValidator, FieldExtensionOptions, FieldExtensionComponentProps, + NextCustomFieldValidator, + NextFieldExtensionOptions, + NextFieldExtensionComponentProps, }; export { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from './default'; diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index 4758d96f66..627587bba6 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -14,9 +14,16 @@ * limitations under the License. */ import { ApiHolder } from '@backstage/core-plugin-api'; -import { FieldValidation, FieldProps, UiSchema } from '@rjsf/core'; +import { FieldValidation, FieldProps } from '@rjsf/core'; import { PropsWithChildren } from 'react'; +import { + UIOptionsType, + FieldProps as FieldPropsV5, + UiSchema as UiSchemaV5, + FieldValidation as FieldValidationV5, +} from '@rjsf/utils'; + /** * Field validation type for Custom Field Extensions. * @@ -36,7 +43,7 @@ export type CustomFieldValidator = ( */ export type FieldExtensionOptions< TFieldReturnValue = unknown, - TInputProps extends {} = {}, + TInputProps = unknown, > = { name: string; component: ( @@ -53,9 +60,51 @@ export type FieldExtensionOptions< */ export interface FieldExtensionComponentProps< TFieldReturnValue, - TUiOptions = {}, -> extends PropsWithChildren> { - uiSchema: UiSchema & { - 'ui:options': TUiOptions; + TUiOptions extends {} = {}, +> extends FieldProps { + uiSchema: FieldProps['uiSchema'] & { + 'ui:options'?: TUiOptions; }; } + +/** + * Type for Field Extension Props for RJSF v5 + * + * @alpha + */ +export interface NextFieldExtensionComponentProps< + TFieldReturnValue, + TUiOptions = {}, +> extends PropsWithChildren> { + uiSchema?: UiSchemaV5 & { + 'ui:options'?: TUiOptions & UIOptionsType; + }; +} + +/** + * Field validation type for Custom Field Extensions. + * + * @alpha + */ +export type NextCustomFieldValidator = ( + data: TFieldReturnValue, + field: FieldValidationV5, + context: { apiHolder: ApiHolder }, +) => void | Promise; + +/** + * Type for the Custom Field Extension with the + * name and components and validation function. + * + * @alpha + */ +export type NextFieldExtensionOptions< + TFieldReturnValue = unknown, + TInputProps = unknown, +> = { + name: string; + component: ( + props: FieldExtensionComponentProps, + ) => JSX.Element | null; + validation?: NextCustomFieldValidator; +}; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index ade7c982ff..aa3a384738 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -26,12 +26,12 @@ import { withTheme } from '@rjsf/core'; import { ErrorSchema, FieldValidation } from '@rjsf/utils'; import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useMemo, useState } from 'react'; -import { FieldExtensionOptions } from '../../../extensions'; +import { NextFieldExtensionOptions } from '../../../extensions'; import { TemplateParameterSchema } from '../../../types'; import { createAsyncValidators } from './createAsyncValidators'; import { useTemplateSchema } from './useTemplateSchema'; import { ReviewState } from './ReviewState'; -import validator from '@rjsf/validator-ajv6'; +import validator from '@rjsf/validator-ajv8'; const useStyles = makeStyles(theme => ({ backButton: { @@ -50,7 +50,7 @@ const useStyles = makeStyles(theme => ({ export interface StepperProps { manifest: TemplateParameterSchema; - extensions: FieldExtensionOptions[]; + extensions: NextFieldExtensionOptions[]; } const Form = withTheme(MuiTheme); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts index ed887fb56d..a4a937e6fe 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { JsonObject } from '@backstage/types'; -import { CustomFieldValidator } from '../../../extensions'; +import { NextCustomFieldValidator } from '../../../extensions'; import { createAsyncValidators } from './createAsyncValidators'; describe('createAsyncValidators', () => { @@ -79,13 +79,16 @@ describe('createAsyncValidators', () => { }, }; - const NameField: CustomFieldValidator = (value, { addError }) => { + const NameField: NextCustomFieldValidator = ( + value, + { addError }, + ) => { if (!value) { addError('something is broken here!'); } }; - const AddressField: CustomFieldValidator<{ + const AddressField: NextCustomFieldValidator<{ street?: string; postcode?: string; }> = (value, { addError }) => { @@ -101,8 +104,8 @@ describe('createAsyncValidators', () => { const validate = createAsyncValidators( schema, { - NameField: NameField as CustomFieldValidator, - AddressField: AddressField as CustomFieldValidator, + NameField: NameField as NextCustomFieldValidator, + AddressField: AddressField as NextCustomFieldValidator, }, { apiHolder: { get: jest.fn() }, diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts index 60eed0f364..1355387eda 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts @@ -17,13 +17,13 @@ import { FieldValidation } from '@rjsf/utils'; import { JsonObject } from '@backstage/types'; import { ApiHolder } from '@backstage/core-plugin-api'; -import { CustomFieldValidator } from '../../../extensions'; +import { NextCustomFieldValidator } from '../../../extensions'; import { Draft07 as JSONSchema } from 'json-schema-library'; import { createFieldValidation } from './schema'; export const createAsyncValidators = ( rootSchema: JsonObject, - validators: Record>, + validators: Record>, context: { apiHolder: ApiHolder; },