From ff28494249e2748b1e27acf4020584ad71d6c543 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 9 Aug 2022 19:54:57 +0100 Subject: [PATCH 01/17] allow a custom ObjectFieldTemplate to be supplied through the ui:ObjectFieldTemplate field Signed-off-by: Paul Cowan --- .../MultistepJsonForm/MultistepJsonForm.tsx | 3 + plugins/scaffolder/src/components/Router.tsx | 25 +++++- .../TemplateEditorPage/TemplateEditor.tsx | 3 + .../TemplateEditorPage/TemplateEditorForm.tsx | 9 ++- .../TemplateEditorPage/TemplateEditorPage.tsx | 4 + .../TemplateFormPreviewer.tsx | 4 + .../components/TemplatePage/TemplatePage.tsx | 4 + .../DefaultStepFormLayout.tsx | 79 +++++++++++++++++++ .../layouts/DefaultStepFormLayout/index.tsx | 16 ++++ plugins/scaffolder/src/layouts/default.ts | 21 +++++ plugins/scaffolder/src/layouts/index.tsx | 46 +++++++++++ plugins/scaffolder/src/layouts/types.ts | 22 ++++++ .../src/next/Router/Router.test.tsx | 45 +++++++++++ plugins/scaffolder/src/next/Router/Router.tsx | 23 +++++- .../Stepper/Stepper.test.tsx | 8 +- .../TemplateWizardPage/Stepper/Stepper.tsx | 2 + .../Stepper/useTemplateSchema.test.tsx | 2 + .../TemplateWizardPage/TemplateWizardPage.tsx | 3 + 18 files changed, 315 insertions(+), 4 deletions(-) create mode 100644 plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx create mode 100644 plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/index.tsx create mode 100644 plugins/scaffolder/src/layouts/default.ts create mode 100644 plugins/scaffolder/src/layouts/index.tsx create mode 100644 plugins/scaffolder/src/layouts/types.ts diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 43e12ae85f..97c10c34ec 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -55,6 +55,7 @@ type Props = { widgets?: FormProps['widgets']; fields?: FormProps['fields']; finishButtonLabel?: string; + layout?: FormProps['ObjectFieldTemplate']; }; export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { @@ -119,6 +120,7 @@ export const MultistepJsonForm = (props: Props) => { fields, widgets, finishButtonLabel, + layout, } = props; const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); @@ -203,6 +205,7 @@ export const MultistepJsonForm = (props: Props) => {
{ ), ), ]; + + const customLayouts = useElementFilter(outlet, elements => + elements + .selectByComponentData({ + key: LAYOUTS_WRAPPER_KEY, + }) + .findComponentData({ + key: LAYOUTS_KEY, + }), + ); + + const layout = customLayouts?.[0] ?? DEFAULT_SCAFFOLDER_LAYOUT; + /** * This component can be deleted once the older routes have been deprecated. */ @@ -142,7 +161,10 @@ export const Router = (props: RouterProps) => { path={selectedTemplateRouteRef.path} element={ - + } /> @@ -159,6 +181,7 @@ export const Router = (props: RouterProps) => { } diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx index cc4dd8c9a1..68ad355b61 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx @@ -16,6 +16,7 @@ import { makeStyles } from '@material-ui/core'; import React, { useState } from 'react'; import { FieldExtensionOptions } from '../../extensions'; +import { LayoutOptions } from '../../layouts'; import { TemplateDirectoryAccess } from '../../lib/filesystem'; import { DirectoryEditorProvider } from './DirectoryEditorContext'; import { DryRunProvider } from './DryRunContext'; @@ -57,6 +58,7 @@ const useStyles = makeStyles({ export const TemplateEditor = (props: { directory: TemplateDirectoryAccess; fieldExtensions?: FieldExtensionOptions[]; + layout?: LayoutOptions; onClose?: () => void; }) => { const classes = useStyles(); @@ -77,6 +79,7 @@ export const TemplateEditor = (props: {
diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx index d802ab07dc..8b9fea6b30 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx @@ -20,6 +20,7 @@ import React, { Component, ReactNode, useMemo, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import yaml from 'yaml'; import { FieldExtensionOptions } from '../../extensions'; +import { DEFAULT_SCAFFOLDER_LAYOUT, LayoutOptions } from '../../layouts'; import { TemplateParameterSchema } from '../../types'; import { MultistepJsonForm } from '../MultistepJsonForm'; import { createValidator } from '../TemplatePage'; @@ -83,6 +84,7 @@ interface TemplateEditorFormProps { onDryRun?: (data: JsonObject) => Promise; fieldExtensions?: FieldExtensionOptions[]; + layout?: LayoutOptions; } function isJsonObject(value: JsonValue | undefined): value is JsonObject { @@ -99,6 +101,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { onDryRun, setErrorText, fieldExtensions = [], + layout = DEFAULT_SCAFFOLDER_LAYOUT, } = props; const classes = useStyles(); const apiHolder = useApiHolder(); @@ -188,6 +191,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { onReset={() => onUpdate({})} finishButtonLabel={onDryRun && 'Try It'} onFinish={onDryRun && (() => onDryRun(data))} + layout={layout.component} /> @@ -197,7 +201,10 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { /** A version of the TemplateEditorForm that is connected to the DirectoryEditor and DryRun contexts */ export function TemplateEditorFormDirectoryEditorDryRun( - props: Pick, + props: Pick< + TemplateEditorFormProps, + 'setErrorText' | 'fieldExtensions' | 'layout' + >, ) { const { setErrorText, fieldExtensions = [] } = props; const dryRun = useDryRun(); diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx index fce2881190..2995678248 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx @@ -23,6 +23,7 @@ import { TemplateEditorIntro } from './TemplateEditorIntro'; import { TemplateEditor } from './TemplateEditor'; import { TemplateFormPreviewer } from './TemplateFormPreviewer'; import { FieldExtensionOptions } from '../../extensions'; +import { LayoutOptions } from '../../layouts'; type Selection = | { @@ -36,6 +37,7 @@ type Selection = interface TemplateEditorPageProps { defaultPreviewTemplate?: string; customFieldExtensions?: FieldExtensionOptions[]; + layout?: LayoutOptions; } export function TemplateEditorPage(props: TemplateEditorPageProps) { @@ -48,6 +50,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { directory={selection.directory} fieldExtensions={props.customFieldExtensions} onClose={() => setSelection(undefined)} + layout={props.layout} /> ); } else if (selection?.type === 'form') { @@ -56,6 +59,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { defaultPreviewTemplate={props.defaultPreviewTemplate} customFieldExtensions={props.customFieldExtensions} onClose={() => setSelection(undefined)} + layout={props.layout} /> ); } else { diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx index bfa7071d48..1e3758ebba 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -33,6 +33,7 @@ import React, { useCallback, useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; import yaml from 'yaml'; import { FieldExtensionOptions } from '../../extensions'; +import { DEFAULT_SCAFFOLDER_LAYOUT, LayoutOptions } from '../../layouts'; import { TemplateEditorForm } from './TemplateEditorForm'; import { TemplateEditorTextArea } from './TemplateEditorTextArea'; @@ -110,10 +111,12 @@ export const TemplateFormPreviewer = ({ defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML, customFieldExtensions = [], onClose, + layout = DEFAULT_SCAFFOLDER_LAYOUT, }: { defaultPreviewTemplate?: string; customFieldExtensions?: FieldExtensionOptions[]; onClose?: () => void; + layout?: LayoutOptions; }) => { const classes = useStyles(); const alertApi = useApi(alertApiRef); @@ -208,6 +211,7 @@ export const TemplateFormPreviewer = ({ data={formState} onUpdate={setFormState} setErrorText={setErrorText} + layout={layout} /> diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 03ce57136f..2d2ab3989e 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -39,6 +39,7 @@ import { useRouteRefParams, } from '@backstage/core-plugin-api'; import { stringifyEntityRef } from '@backstage/catalog-model'; +import { DEFAULT_SCAFFOLDER_LAYOUT, LayoutOptions } from '../../layouts'; const useTemplateParameterSchema = (templateRef: string) => { const scaffolderApi = useApi(scaffolderApiRef); @@ -51,8 +52,10 @@ const useTemplateParameterSchema = (templateRef: string) => { export const TemplatePage = ({ customFieldExtensions = [], + layout = DEFAULT_SCAFFOLDER_LAYOUT, }: { customFieldExtensions?: FieldExtensionOptions[]; + layout?: LayoutOptions; }) => { const apiHolder = useApiHolder(); const secretsContext = useContext(SecretsContext); @@ -146,6 +149,7 @@ export const TemplatePage = ({ onChange={handleChange} onReset={handleFormReset} onFinish={handleCreate} + layout={layout.component} steps={schema.steps.map(step => { return { ...step, diff --git a/plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx b/plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx new file mode 100644 index 0000000000..871f568383 --- /dev/null +++ b/plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx @@ -0,0 +1,79 @@ +/* + * 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 React from 'react'; +import { ObjectFieldTemplateProps, utils } from '@rjsf/core'; +import { Button, Grid } from '@material-ui/core'; + +const { canExpand } = utils; + +export const DefaultStepFormLayout = ({ + DescriptionField, + description, + TitleField, + title, + properties, + required, + disabled, + readonly, + uiSchema, + idSchema, + schema, + formData, + onAddClick, +}: ObjectFieldTemplateProps) => { + return ( + <> +

THIS IS OUR OBJECTFIELDTEMPLATE!!!!!!

+ {(uiSchema['ui:title'] || title) && ( + + )} + {description && ( + + )} + + {properties.map((element, index) => + // Remove the if the inner element is hidden as the + // itself would otherwise still take up space. + element.hidden ? ( + element.content + ) : ( + + {element.content} + + ), + )} + {canExpand(schema, uiSchema, formData) && ( + + +
diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx index 8b9fea6b30..4395f06206 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx @@ -84,7 +84,7 @@ interface TemplateEditorFormProps { onDryRun?: (data: JsonObject) => Promise; fieldExtensions?: FieldExtensionOptions[]; - layout?: LayoutOptions; + layouts?: LayoutOptions[]; } function isJsonObject(value: JsonValue | undefined): value is JsonObject { @@ -101,7 +101,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { onDryRun, setErrorText, fieldExtensions = [], - layout = DEFAULT_SCAFFOLDER_LAYOUT, + layouts = [DEFAULT_SCAFFOLDER_LAYOUT], } = props; const classes = useStyles(); const apiHolder = useApiHolder(); @@ -141,6 +141,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { } const { parameters } = rootObj; + if (!Array.isArray(parameters)) { setErrorText('Template parameters must be an array'); setSteps(undefined); @@ -191,7 +192,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { onReset={() => onUpdate({})} finishButtonLabel={onDryRun && 'Try It'} onFinish={onDryRun && (() => onDryRun(data))} - layout={layout.component} + layouts={layouts} /> @@ -203,7 +204,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { export function TemplateEditorFormDirectoryEditorDryRun( props: Pick< TemplateEditorFormProps, - 'setErrorText' | 'fieldExtensions' | 'layout' + 'setErrorText' | 'fieldExtensions' | 'layouts' >, ) { const { setErrorText, fieldExtensions = [] } = props; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx index 2995678248..b539b6403b 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx @@ -23,7 +23,7 @@ import { TemplateEditorIntro } from './TemplateEditorIntro'; import { TemplateEditor } from './TemplateEditor'; import { TemplateFormPreviewer } from './TemplateFormPreviewer'; import { FieldExtensionOptions } from '../../extensions'; -import { LayoutOptions } from '../../layouts'; +import type { LayoutOptions } from '../../layouts'; type Selection = | { @@ -37,7 +37,7 @@ type Selection = interface TemplateEditorPageProps { defaultPreviewTemplate?: string; customFieldExtensions?: FieldExtensionOptions[]; - layout?: LayoutOptions; + layouts?: LayoutOptions[]; } export function TemplateEditorPage(props: TemplateEditorPageProps) { @@ -50,7 +50,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { directory={selection.directory} fieldExtensions={props.customFieldExtensions} onClose={() => setSelection(undefined)} - layout={props.layout} + layouts={props.layouts} /> ); } else if (selection?.type === 'form') { @@ -59,7 +59,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { defaultPreviewTemplate={props.defaultPreviewTemplate} customFieldExtensions={props.customFieldExtensions} onClose={() => setSelection(undefined)} - layout={props.layout} + layouts={props.layouts} /> ); } else { diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx index 1e3758ebba..1cdc211865 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -111,12 +111,12 @@ export const TemplateFormPreviewer = ({ defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML, customFieldExtensions = [], onClose, - layout = DEFAULT_SCAFFOLDER_LAYOUT, + layouts = [DEFAULT_SCAFFOLDER_LAYOUT], }: { defaultPreviewTemplate?: string; customFieldExtensions?: FieldExtensionOptions[]; onClose?: () => void; - layout?: LayoutOptions; + layouts?: LayoutOptions[]; }) => { const classes = useStyles(); const alertApi = useApi(alertApiRef); @@ -211,7 +211,7 @@ export const TemplateFormPreviewer = ({ data={formState} onUpdate={setFormState} setErrorText={setErrorText} - layout={layout} + layouts={layouts} /> diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 2d2ab3989e..4cf3cf1841 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -52,10 +52,10 @@ const useTemplateParameterSchema = (templateRef: string) => { export const TemplatePage = ({ customFieldExtensions = [], - layout = DEFAULT_SCAFFOLDER_LAYOUT, + layouts = [DEFAULT_SCAFFOLDER_LAYOUT], }: { customFieldExtensions?: FieldExtensionOptions[]; - layout?: LayoutOptions; + layouts?: LayoutOptions[]; }) => { const apiHolder = useApiHolder(); const secretsContext = useContext(SecretsContext); @@ -149,7 +149,7 @@ export const TemplatePage = ({ onChange={handleChange} onReset={handleFormReset} onFinish={handleCreate} - layout={layout.component} + layouts={layouts} steps={schema.steps.map(step => { return { ...step, diff --git a/plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx b/plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx index 871f568383..b7711e59fb 100644 --- a/plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx +++ b/plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx @@ -36,7 +36,6 @@ export const DefaultStepFormLayout = ({ }: ObjectFieldTemplateProps) => { return ( <> -

THIS IS OUR OBJECTFIELDTEMPLATE!!!!!!

{(uiSchema['ui:title'] || title) && ( = () => null; + +export function createScaffolderLayout< + TFieldReturnValue = unknown, + TInputProps = unknown, +>( options: LayoutOptions, -): Extension> { +): Extension> { return { expose() { const LayoutDataHolder: any = () => null; @@ -41,6 +44,6 @@ export const ScaffolderLayouts: React.ComponentType = (): JSX.Element | null => attachComponentData(ScaffolderLayouts, LAYOUTS_WRAPPER_KEY, true); -export type { LayoutOptions } from './types'; +export type { LayoutOptions, ObjectFieldTemplate } from './types'; export { DEFAULT_SCAFFOLDER_LAYOUT } from './default'; diff --git a/plugins/scaffolder/src/layouts/types.ts b/plugins/scaffolder/src/layouts/types.ts index 66106bda21..fb0e766a43 100644 --- a/plugins/scaffolder/src/layouts/types.ts +++ b/plugins/scaffolder/src/layouts/types.ts @@ -13,10 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ObjectFieldTemplateProps } from '@rjsf/core'; -import type { FunctionComponent } from 'react'; +import type { FormProps } from '@rjsf/core'; -export type LayoutOptions = { +export type ObjectFieldTemplate = Required< + FormProps +>['ObjectFieldTemplate']; + +export interface LayoutOptions { name: string; - component: FunctionComponent; -}; + component: ObjectFieldTemplate; +} diff --git a/plugins/scaffolder/src/next/Router/Router.test.tsx b/plugins/scaffolder/src/next/Router/Router.test.tsx index 5a47127885..d1c627de51 100644 --- a/plugins/scaffolder/src/next/Router/Router.test.tsx +++ b/plugins/scaffolder/src/next/Router/Router.test.tsx @@ -102,19 +102,17 @@ describe('Router', () => { it('should extract the custom layout and pass it through', async () => { const mockLayout = () => null; - const CustomLayout = scaffolderPlugin.provide( + const Customlayout = scaffolderPlugin.provide( createScaffolderLayout({ - name: 'customLayout', + name: 'CustomLayout', component: mockLayout, }), ); - const props = {} as ObjectFieldTemplateProps; - await renderInTestApp( - + , { routeEntries: ['/templates/default/foo'] }, @@ -124,7 +122,7 @@ describe('Router', () => { // eslint-disable-next-line no-console const [{ layout }] = mock.mock.calls[0]; - expect(layout).toEqual({ name: 'customLayout', component: mockLayout }); + expect(layout).toEqual({ name: 'CustomLayout', component: mockLayout }); }); }); }); diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 8a64c9fde9..5934206564 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -91,7 +91,13 @@ export const Router = (props: PropsWithChildren) => { }), ); - const layout = customLayouts?.[0] ?? DEFAULT_SCAFFOLDER_LAYOUT; + if ( + !customLayouts.find( + layout => layout.name === DEFAULT_SCAFFOLDER_LAYOUT.name, + ) + ) { + customLayouts.push(DEFAULT_SCAFFOLDER_LAYOUT); + } return ( @@ -111,7 +117,7 @@ export const Router = (props: PropsWithChildren) => { } diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index c87bc2ed0a..1802f8bea2 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -26,7 +26,6 @@ import { FieldValidation, withTheme } from '@rjsf/core'; import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useMemo, useState } from 'react'; import { FieldExtensionOptions } from '../../../extensions'; -import type { LayoutOptions } from '../../../layouts'; import { TemplateParameterSchema } from '../../../types'; import { createAsyncValidators } from './createAsyncValidators'; import { useTemplateSchema } from './useTemplateSchema'; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index 2c403f4999..e29c903e25 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -41,7 +41,7 @@ import type { LayoutOptions } from '../../layouts'; export interface TemplateWizardPageProps { customFieldExtensions: FieldExtensionOptions[]; - layout?: LayoutOptions; + layouts: LayoutOptions[]; } const useStyles = makeStyles(() => ({ @@ -115,7 +115,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { )} From 7fdb1fe283fa55db5ca5589d5f4977b42445a268 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Wed, 10 Aug 2022 20:16:50 +0100 Subject: [PATCH 03/17] extract ObjectFieldTemplate from uiSchema Signed-off-by: Paul Cowan --- package.json | 5 ++- packages/app/src/App.tsx | 8 +++-- .../scaffolder/customScaffolderLayouts.tsx | 27 ++++++++++++++- .../scaffolder/defaultPreviewTemplate.ts | 1 + .../MultistepJsonForm/MultistepJsonForm.tsx | 33 ++++++++++--------- 5 files changed, 55 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index a0de99bfad..da8f193d3a 100644 --- a/package.json +++ b/package.json @@ -93,5 +93,8 @@ "node ./scripts/check-docs-quality" ] }, - "packageManager": "yarn@3.2.3" + "packageManager": "yarn@3.2.3", + "volta": { + "node": "14.19.3" + } } diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 9969a0fcdc..7e49ad5087 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -105,7 +105,10 @@ import { ApacheAirflowPage } from '@backstage/plugin-apache-airflow'; import { RequirePermission } from '@backstage/plugin-permission-react'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; import { PlaylistIndexPage } from '@backstage/plugin-playlist'; -import { Customlayout } from './components/scaffolder/customScaffolderLayouts'; +import { + AnotherCustomlayout, + CustomLayout, +} from './components/scaffolder/customScaffolderLayouts'; const app = createApp({ apis, @@ -222,7 +225,8 @@ const routes = ( - + + diff --git a/packages/app/src/components/scaffolder/customScaffolderLayouts.tsx b/packages/app/src/components/scaffolder/customScaffolderLayouts.tsx index 0fd4bed2d0..065a1ca780 100644 --- a/packages/app/src/components/scaffolder/customScaffolderLayouts.tsx +++ b/packages/app/src/components/scaffolder/customScaffolderLayouts.tsx @@ -35,9 +35,34 @@ const ALayout: ObjectFieldTemplate = ({ properties, description }) => { ); }; -export const Customlayout = scaffolderPlugin.provide( +const AnotherCustomLayout: ObjectFieldTemplate = ({ + properties, + description, +}) => { + // eslint-disable-next-line no-console + return ( +
+

ANOTHER CUSTOM LAYOUT!!!!!

+
+ {properties.map(prop => ( +
{prop.content}
+ ))} +
+ {description} +
+ ); +}; + +export const CustomLayout = scaffolderPlugin.provide( createScaffolderLayout({ name: 'CustomLayout', component: ALayout, }), ); + +export const AnotherCustomlayout = scaffolderPlugin.provide( + createScaffolderLayout({ + name: 'AnotherCustomLayout', + component: AnotherCustomLayout, + }), +); diff --git a/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts b/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts index db0725591c..53805265ed 100644 --- a/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts +++ b/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts @@ -34,6 +34,7 @@ parameters: allowedKinds: - Group - title: Choose a location + ui:ObjectFieldTemplate: 'AnotherCustomLayout' required: - repoUrl properties: diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index c4384c2d04..c2050fe703 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -155,20 +155,6 @@ export const MultistepJsonForm = (props: Props) => { : filteredStep.schema.required; } - const layoutName = - filteredStep.schema['ui:ObjectFieldTemplate'] ?? - DEFAULT_SCAFFOLDER_LAYOUT.name; - - const LayoutComponent = layouts.find( - layout => layout.name === layoutName, - )?.component; - - if (!LayoutComponent) { - throw new Error(`no step layout found for ${layoutName}`); - } - - filteredStep.schema['ui:ObjectFieldTemplate'] = LayoutComponent as any; - return filteredStep; }; @@ -208,6 +194,22 @@ export const MultistepJsonForm = (props: Props) => { <> {steps.map(({ title, schema, ...formProps }, index) => { + const schemaProps = transformSchemaToProps(schema); + + const layoutName = + schemaProps.uiSchema?.['ui:ObjectFieldTemplate'] ?? + DEFAULT_SCAFFOLDER_LAYOUT.name; + + delete schemaProps.uiSchema?.['ui:ObjectFieldTemplate']; + + const LayoutComponent = layouts.find( + layout => layout.name === layoutName, + )?.component; + + if (!LayoutComponent) { + throw new Error(`no step layout found for ${layoutName}`); + } + return ( { { if (e.errors.length === 0) handleNext(); }} {...formProps} - {...transformSchemaToProps(schema)} + {...schemaProps} >