From 0fd0385615cb9d852511bcc90a6c94076a05f6a8 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Wed, 10 Aug 2022 19:47:46 +0100 Subject: [PATCH] pass layouts throught to MultiJsonForm Signed-off-by: Paul Cowan --- packages/app/src/App.tsx | 13 ++++-- .../scaffolder/customScaffolderLayouts.tsx | 43 +++++++++++++++++++ .../scaffolder/defaultPreviewTemplate.ts | 1 + .../MultistepJsonForm/MultistepJsonForm.tsx | 21 +++++++-- plugins/scaffolder/src/components/Router.tsx | 12 ++++-- .../TemplateEditorPage/TemplateEditor.tsx | 6 +-- .../TemplateEditorPage/TemplateEditorForm.tsx | 9 ++-- .../TemplateEditorPage/TemplateEditorPage.tsx | 8 ++-- .../TemplateFormPreviewer.tsx | 6 +-- .../components/TemplatePage/TemplatePage.tsx | 6 +-- .../DefaultStepFormLayout.tsx | 1 - plugins/scaffolder/src/index.ts | 2 + plugins/scaffolder/src/layouts/index.tsx | 13 +++--- plugins/scaffolder/src/layouts/types.ts | 13 +++--- .../src/next/Router/Router.test.tsx | 10 ++--- plugins/scaffolder/src/next/Router/Router.tsx | 10 ++++- .../TemplateWizardPage/Stepper/Stepper.tsx | 1 - .../TemplateWizardPage/TemplateWizardPage.tsx | 4 +- 18 files changed, 131 insertions(+), 48 deletions(-) create mode 100644 packages/app/src/components/scaffolder/customScaffolderLayouts.tsx diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index dcc233a195..9969a0fcdc 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -64,6 +64,7 @@ import { ScaffolderPage, NextScaffolderPage, scaffolderPlugin, + ScaffolderLayouts, } from '@backstage/plugin-scaffolder'; import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; @@ -104,6 +105,7 @@ 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'; const app = createApp({ apis, @@ -215,9 +217,14 @@ const routes = ( /> } > - - - + <> + + + + + + + { + // eslint-disable-next-line no-console + return ( +
+

CUSTOM LAYOUT!!!!!

+
+ {properties.map(prop => ( +
{prop.content}
+ ))} +
+ {description} +
+ ); +}; + +export const Customlayout = scaffolderPlugin.provide( + createScaffolderLayout({ + name: 'CustomLayout', + component: ALayout, + }), +); diff --git a/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts b/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts index 2e23b08e53..db0725591c 100644 --- a/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts +++ b/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts @@ -17,6 +17,7 @@ export const defaultPreviewTemplate = `# Edit the template parameters below to see how they will render in the scaffolder form UI parameters: - title: Fill in some steps + ui:ObjectFieldTemplate: 'CustomLayout' required: - name properties: diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 97c10c34ec..c4384c2d04 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -36,6 +36,7 @@ import { transformSchemaToProps } from './schema'; import { Content, StructuredMetadataTable } from '@backstage/core-components'; import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; +import { DEFAULT_SCAFFOLDER_LAYOUT, LayoutOptions } from '../../layouts'; const Form = withTheme(MuiTheme); type Step = { @@ -55,7 +56,7 @@ type Props = { widgets?: FormProps['widgets']; fields?: FormProps['fields']; finishButtonLabel?: string; - layout?: FormProps['ObjectFieldTemplate']; + layouts: LayoutOptions[]; }; export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { @@ -120,7 +121,7 @@ export const MultistepJsonForm = (props: Props) => { fields, widgets, finishButtonLabel, - layout, + layouts, } = props; const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); @@ -153,6 +154,21 @@ 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; }; @@ -205,7 +221,6 @@ export const MultistepJsonForm = (props: Props) => {
{ }), ); - const layout = customLayouts?.[0] ?? DEFAULT_SCAFFOLDER_LAYOUT; + if ( + !customLayouts.find( + layout => layout.name === DEFAULT_SCAFFOLDER_LAYOUT.name, + ) + ) { + customLayouts.push(DEFAULT_SCAFFOLDER_LAYOUT); + } /** * This component can be deleted once the older routes have been deprecated. @@ -163,7 +169,7 @@ export const Router = (props: RouterProps) => { } @@ -181,7 +187,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 68ad355b61..7055e42c84 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx @@ -16,7 +16,7 @@ import { makeStyles } from '@material-ui/core'; import React, { useState } from 'react'; import { FieldExtensionOptions } from '../../extensions'; -import { LayoutOptions } from '../../layouts'; +import type { LayoutOptions } from '../../layouts'; import { TemplateDirectoryAccess } from '../../lib/filesystem'; import { DirectoryEditorProvider } from './DirectoryEditorContext'; import { DryRunProvider } from './DryRunContext'; @@ -58,7 +58,7 @@ const useStyles = makeStyles({ export const TemplateEditor = (props: { directory: TemplateDirectoryAccess; fieldExtensions?: FieldExtensionOptions[]; - layout?: LayoutOptions; + layouts?: LayoutOptions[]; onClose?: () => void; }) => { const classes = useStyles(); @@ -79,7 +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 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) => { )}