allow a custom ObjectFieldTemplate to be supplied through the ui:ObjectFieldTemplate field
Signed-off-by: Paul Cowan <paul.cowan@cutting.scot>
This commit is contained in:
@@ -55,6 +55,7 @@ type Props = {
|
||||
widgets?: FormProps<any>['widgets'];
|
||||
fields?: FormProps<any>['fields'];
|
||||
finishButtonLabel?: string;
|
||||
layout?: FormProps<any>['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) => {
|
||||
</StepLabel>
|
||||
<StepContent key={title}>
|
||||
<Form
|
||||
ObjectFieldTemplate={layout}
|
||||
showErrorList={false}
|
||||
fields={{ ...fieldOverrides, ...fields }}
|
||||
widgets={widgets}
|
||||
|
||||
@@ -45,6 +45,12 @@ import {
|
||||
selectedTemplateRouteRef,
|
||||
} from '../routes';
|
||||
import { ListTasksPage } from './ListTasksPage';
|
||||
import {
|
||||
DEFAULT_SCAFFOLDER_LAYOUT,
|
||||
LayoutOptions,
|
||||
LAYOUTS_KEY,
|
||||
LAYOUTS_WRAPPER_KEY,
|
||||
} from '../layouts';
|
||||
|
||||
/**
|
||||
* The props for the entrypoint `ScaffolderPage` component the plugin.
|
||||
@@ -105,6 +111,19 @@ export const Router = (props: RouterProps) => {
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
const customLayouts = useElementFilter(outlet, elements =>
|
||||
elements
|
||||
.selectByComponentData({
|
||||
key: LAYOUTS_WRAPPER_KEY,
|
||||
})
|
||||
.findComponentData<LayoutOptions>({
|
||||
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={
|
||||
<SecretsContextProvider>
|
||||
<TemplatePage customFieldExtensions={fieldExtensions} />
|
||||
<TemplatePage
|
||||
customFieldExtensions={fieldExtensions}
|
||||
layout={layout}
|
||||
/>
|
||||
</SecretsContextProvider>
|
||||
}
|
||||
/>
|
||||
@@ -159,6 +181,7 @@ export const Router = (props: RouterProps) => {
|
||||
<TemplateEditorPage
|
||||
defaultPreviewTemplate={defaultPreviewTemplate}
|
||||
customFieldExtensions={fieldExtensions}
|
||||
layout={layout}
|
||||
/>
|
||||
</SecretsContextProvider>
|
||||
}
|
||||
|
||||
@@ -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<any, any>[];
|
||||
layout?: LayoutOptions;
|
||||
onClose?: () => void;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
@@ -77,6 +79,7 @@ export const TemplateEditor = (props: {
|
||||
<TemplateEditorForm.DirectoryEditorDryRun
|
||||
setErrorText={setErrorText}
|
||||
fieldExtensions={props.fieldExtensions}
|
||||
layout={props.layout}
|
||||
/>
|
||||
</section>
|
||||
<section className={classes.results}>
|
||||
|
||||
@@ -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<void>;
|
||||
fieldExtensions?: FieldExtensionOptions<any, any>[];
|
||||
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}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
@@ -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<TemplateEditorFormProps, 'setErrorText' | 'fieldExtensions'>,
|
||||
props: Pick<
|
||||
TemplateEditorFormProps,
|
||||
'setErrorText' | 'fieldExtensions' | 'layout'
|
||||
>,
|
||||
) {
|
||||
const { setErrorText, fieldExtensions = [] } = props;
|
||||
const dryRun = useDryRun();
|
||||
|
||||
@@ -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<any, any>[];
|
||||
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 {
|
||||
|
||||
@@ -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<any, any>[];
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -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<any, any>[];
|
||||
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,
|
||||
|
||||
+79
@@ -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 (
|
||||
<>
|
||||
<h1>THIS IS OUR OBJECTFIELDTEMPLATE!!!!!!</h1>
|
||||
{(uiSchema['ui:title'] || title) && (
|
||||
<TitleField
|
||||
id={`${idSchema.$id}-title`}
|
||||
title={title}
|
||||
required={required}
|
||||
/>
|
||||
)}
|
||||
{description && (
|
||||
<DescriptionField
|
||||
id={`${idSchema.$id}-description`}
|
||||
description={description}
|
||||
/>
|
||||
)}
|
||||
<Grid container spacing={2} style={{ marginTop: '10px' }}>
|
||||
{properties.map((element, index) =>
|
||||
// Remove the <Grid> if the inner element is hidden as the <Grid>
|
||||
// itself would otherwise still take up space.
|
||||
element.hidden ? (
|
||||
element.content
|
||||
) : (
|
||||
<Grid item xs={12} key={index} style={{ marginBottom: '10px' }}>
|
||||
{element.content}
|
||||
</Grid>
|
||||
),
|
||||
)}
|
||||
{canExpand(schema, uiSchema, formData) && (
|
||||
<Grid container justifyContent="flex-end">
|
||||
<Grid item>
|
||||
<Button
|
||||
className="object-property-expand"
|
||||
onClick={onAddClick(schema)}
|
||||
disabled={disabled || readonly}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export { DefaultStepFormLayout } from './DefaultStepFormLayout';
|
||||
@@ -0,0 +1,21 @@
|
||||
import { DefaultStepFormLayout } from '../components/layouts/DefaultStepFormLayout';
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export const DEFAULT_SCAFFOLDER_LAYOUT = {
|
||||
component: DefaultStepFormLayout,
|
||||
name: 'DefaultStepFormLayout',
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 { attachComponentData, Extension } from '@backstage/core-plugin-api';
|
||||
import type { ObjectFieldTemplateProps } from '@rjsf/core';
|
||||
import type { LayoutOptions } from './types';
|
||||
import type { FunctionComponent } from 'react';
|
||||
|
||||
export const LAYOUTS_KEY = 'scaffolder.layout.v1';
|
||||
export const LAYOUTS_WRAPPER_KEY = 'scaffolder.layouts.wrapper.v1';
|
||||
|
||||
export function createScaffolderLayout(
|
||||
options: LayoutOptions,
|
||||
): Extension<FunctionComponent<ObjectFieldTemplateProps>> {
|
||||
return {
|
||||
expose() {
|
||||
const LayoutDataHolder: any = () => null;
|
||||
|
||||
attachComponentData(LayoutDataHolder, LAYOUTS_KEY, options);
|
||||
|
||||
return LayoutDataHolder;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const ScaffolderLayouts: React.ComponentType = (): JSX.Element | null =>
|
||||
null;
|
||||
|
||||
attachComponentData(ScaffolderLayouts, LAYOUTS_WRAPPER_KEY, true);
|
||||
|
||||
export type { LayoutOptions } from './types';
|
||||
|
||||
export { DEFAULT_SCAFFOLDER_LAYOUT } from './default';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { ObjectFieldTemplateProps } from '@rjsf/core';
|
||||
import type { FunctionComponent } from 'react';
|
||||
|
||||
export type LayoutOptions = {
|
||||
name: string;
|
||||
component: FunctionComponent<ObjectFieldTemplateProps>;
|
||||
};
|
||||
@@ -22,7 +22,13 @@ import {
|
||||
createScaffolderFieldExtension,
|
||||
ScaffolderFieldExtensions,
|
||||
} from '../../extensions';
|
||||
import {
|
||||
createScaffolderLayout,
|
||||
DEFAULT_SCAFFOLDER_LAYOUT,
|
||||
ScaffolderLayouts,
|
||||
} from '../../layouts';
|
||||
import { scaffolderPlugin } from '../../plugin';
|
||||
import { ObjectFieldTemplateProps } from '@rjsf/core';
|
||||
|
||||
jest.mock('../TemplateListPage', () => ({
|
||||
TemplateListPage: jest.fn(() => null),
|
||||
@@ -81,5 +87,44 @@ describe('Router', () => {
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use the default layout', async () => {
|
||||
await renderInTestApp(<Router />, {
|
||||
routeEntries: ['/templates/default/foo'],
|
||||
});
|
||||
|
||||
const mock = TemplateWizardPage as jest.Mock;
|
||||
|
||||
const [{ layout }] = mock.mock.calls[0];
|
||||
|
||||
expect(layout).toEqual(DEFAULT_SCAFFOLDER_LAYOUT);
|
||||
});
|
||||
|
||||
it('should extract the custom layout and pass it through', async () => {
|
||||
const mockLayout = () => null;
|
||||
const CustomLayout = scaffolderPlugin.provide(
|
||||
createScaffolderLayout({
|
||||
name: 'customLayout',
|
||||
component: mockLayout,
|
||||
}),
|
||||
);
|
||||
|
||||
const props = {} as ObjectFieldTemplateProps;
|
||||
|
||||
await renderInTestApp(
|
||||
<Router>
|
||||
<ScaffolderLayouts>
|
||||
<CustomLayout {...props} />
|
||||
</ScaffolderLayouts>
|
||||
</Router>,
|
||||
{ routeEntries: ['/templates/default/foo'] },
|
||||
);
|
||||
|
||||
const mock = TemplateWizardPage as jest.Mock;
|
||||
// eslint-disable-next-line no-console
|
||||
const [{ layout }] = mock.mock.calls[0];
|
||||
|
||||
expect(layout).toEqual({ name: 'customLayout', component: mockLayout });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,6 +29,12 @@ import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateGroupFilter } from '../TemplateListPage/TemplateGroups';
|
||||
import { nextSelectedTemplateRouteRef } from '../../routes';
|
||||
import { SecretsContextProvider } from '../../components/secrets/SecretsContext';
|
||||
import {
|
||||
DEFAULT_SCAFFOLDER_LAYOUT,
|
||||
LayoutOptions,
|
||||
LAYOUTS_KEY,
|
||||
LAYOUTS_WRAPPER_KEY,
|
||||
} from '../../layouts';
|
||||
|
||||
/**
|
||||
* The Props for the Scaffolder Router
|
||||
@@ -75,6 +81,18 @@ export const Router = (props: PropsWithChildren<NextRouterProps>) => {
|
||||
),
|
||||
];
|
||||
|
||||
const customLayouts = useElementFilter(outlet, elements =>
|
||||
elements
|
||||
.selectByComponentData({
|
||||
key: LAYOUTS_WRAPPER_KEY,
|
||||
})
|
||||
.findComponentData<LayoutOptions>({
|
||||
key: LAYOUTS_KEY,
|
||||
}),
|
||||
);
|
||||
|
||||
const layout = customLayouts?.[0] ?? DEFAULT_SCAFFOLDER_LAYOUT;
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
@@ -91,7 +109,10 @@ export const Router = (props: PropsWithChildren<NextRouterProps>) => {
|
||||
path={nextSelectedTemplateRouteRef.path}
|
||||
element={
|
||||
<SecretsContextProvider>
|
||||
<TemplateWizardPage customFieldExtensions={fieldExtensions} />
|
||||
<TemplateWizardPage
|
||||
customFieldExtensions={fieldExtensions}
|
||||
layout={layout}
|
||||
/>
|
||||
</SecretsContextProvider>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { TemplateParameterSchema } from '../../../types';
|
||||
import { Stepper } from './Stepper';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import { DEFAULT_SCAFFOLDER_LAYOUT } from '../../../layouts';
|
||||
|
||||
describe('Stepper', () => {
|
||||
it('should render the step titles for each step of the manifest', async () => {
|
||||
@@ -30,7 +31,11 @@ describe('Stepper', () => {
|
||||
};
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Stepper manifest={manifest} extensions={[]} />,
|
||||
<Stepper
|
||||
manifest={manifest}
|
||||
extensions={[]}
|
||||
layout={DEFAULT_SCAFFOLDER_LAYOUT}
|
||||
/>,
|
||||
);
|
||||
|
||||
for (const step of manifest.steps) {
|
||||
@@ -134,6 +139,7 @@ describe('Stepper', () => {
|
||||
<Stepper
|
||||
manifest={manifest}
|
||||
extensions={[{ name: 'Mock', component: MockComponent }]}
|
||||
layout={DEFAULT_SCAFFOLDER_LAYOUT}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ 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';
|
||||
@@ -49,6 +50,7 @@ const useStyles = makeStyles(theme => ({
|
||||
export interface StepperProps {
|
||||
manifest: TemplateParameterSchema;
|
||||
extensions: FieldExtensionOptions<any, any>[];
|
||||
layout?: LayoutOptions;
|
||||
}
|
||||
|
||||
const Form = withTheme(MuiTheme);
|
||||
|
||||
@@ -41,6 +41,7 @@ describe('useTemplateSchema', () => {
|
||||
description: 'Step 2 Description',
|
||||
schema: {
|
||||
type: 'object',
|
||||
'ui:ObjectFieldTemplate': 'MyLayout',
|
||||
properties: {
|
||||
field2: { type: 'string', 'ui:field': 'MyCoolerComponent' },
|
||||
},
|
||||
@@ -74,6 +75,7 @@ describe('useTemplateSchema', () => {
|
||||
|
||||
expect(second.uiSchema).toEqual({
|
||||
field2: { 'ui:field': 'MyCoolerComponent' },
|
||||
'ui:ObjectFieldTemplate': 'MyLayout',
|
||||
});
|
||||
|
||||
expect(second.schema).toEqual({
|
||||
|
||||
@@ -37,9 +37,11 @@ import { makeStyles } from '@material-ui/core';
|
||||
import { Stepper } from './Stepper';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { nextRouteRef, selectedTemplateRouteRef } from '../../routes';
|
||||
import type { LayoutOptions } from '../../layouts';
|
||||
|
||||
export interface TemplateWizardPageProps {
|
||||
customFieldExtensions: FieldExtensionOptions<any, any>[];
|
||||
layout?: LayoutOptions;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(() => ({
|
||||
@@ -113,6 +115,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
|
||||
<Stepper
|
||||
manifest={manifest}
|
||||
extensions={props.customFieldExtensions}
|
||||
layout={props.layout}
|
||||
/>
|
||||
</InfoCard>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user