consolidate props in Stepper and Workflow
Signed-off-by: Paul Cowan <paul.cowan@cutting.scot>
This commit is contained in:
@@ -355,7 +355,7 @@ export type StepperProps = {
|
||||
templateName?: string;
|
||||
FormProps?: FormProps;
|
||||
initialState?: Record<string, JsonValue>;
|
||||
onComplete: (values: Record<string, JsonValue>) => Promise<void>;
|
||||
onCreate: (values: Record<string, JsonValue>) => Promise<void>;
|
||||
ReviewStateComponent?: (props: ReviewStateProps) => JSX.Element;
|
||||
createButtonText?: string;
|
||||
reviewButtonText?: string;
|
||||
@@ -456,7 +456,7 @@ export interface WorkflowProps {
|
||||
// (undocumented)
|
||||
namespace: string;
|
||||
// (undocumented)
|
||||
onComplete: (values: Record<string, JsonValue>) => Promise<void>;
|
||||
onCreate: (values: Record<string, JsonValue>) => Promise<void>;
|
||||
// (undocumented)
|
||||
onError(error: Error | undefined): JSX.Element | null;
|
||||
// (undocumented)
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('Stepper', () => {
|
||||
};
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Stepper manifest={manifest} extensions={[]} onComplete={jest.fn()} />,
|
||||
<Stepper manifest={manifest} extensions={[]} onCreate={jest.fn()} />,
|
||||
);
|
||||
|
||||
for (const step of manifest.steps) {
|
||||
@@ -51,7 +51,7 @@ describe('Stepper', () => {
|
||||
};
|
||||
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<Stepper manifest={manifest} extensions={[]} onComplete={jest.fn()} />,
|
||||
<Stepper manifest={manifest} extensions={[]} onCreate={jest.fn()} />,
|
||||
);
|
||||
|
||||
expect(getByRole('button', { name: 'Next' })).toBeInTheDocument();
|
||||
@@ -91,7 +91,7 @@ describe('Stepper', () => {
|
||||
};
|
||||
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<Stepper manifest={manifest} extensions={[]} onComplete={jest.fn()} />,
|
||||
<Stepper manifest={manifest} extensions={[]} onCreate={jest.fn()} />,
|
||||
);
|
||||
|
||||
await fireEvent.change(getByRole('textbox', { name: 'name' }), {
|
||||
@@ -162,7 +162,7 @@ describe('Stepper', () => {
|
||||
title: 'React JSON Schema Form Test',
|
||||
};
|
||||
|
||||
const onComplete = jest.fn(async (values: Record<string, JsonValue>) => {
|
||||
const onCreate = jest.fn(async (values: Record<string, JsonValue>) => {
|
||||
expect(values).toEqual({
|
||||
first: { repository: 'Repo' },
|
||||
second: { owner: 'Owner' },
|
||||
@@ -172,7 +172,7 @@ describe('Stepper', () => {
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<Stepper
|
||||
manifest={manifest}
|
||||
onComplete={onComplete}
|
||||
onCreate={onCreate}
|
||||
extensions={[
|
||||
{ name: 'Repo', component: Repo },
|
||||
{ name: 'Owner', component: Owner },
|
||||
@@ -200,7 +200,7 @@ describe('Stepper', () => {
|
||||
await fireEvent.click(getByRole('button', { name: 'Create' }));
|
||||
});
|
||||
|
||||
expect(onComplete).toHaveBeenCalled();
|
||||
expect(onCreate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render custom field extensions properly', async () => {
|
||||
@@ -229,7 +229,7 @@ describe('Stepper', () => {
|
||||
<Stepper
|
||||
manifest={manifest}
|
||||
extensions={[{ name: 'Mock', component: MockComponent }]}
|
||||
onComplete={jest.fn()}
|
||||
onCreate={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -266,7 +266,7 @@ describe('Stepper', () => {
|
||||
<Stepper
|
||||
manifest={manifest}
|
||||
extensions={[]}
|
||||
onComplete={jest.fn()}
|
||||
onCreate={jest.fn()}
|
||||
FormProps={{ transformErrors }}
|
||||
/>,
|
||||
);
|
||||
@@ -308,7 +308,7 @@ describe('Stepper', () => {
|
||||
});
|
||||
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<Stepper manifest={manifest} extensions={[]} onComplete={jest.fn()} />,
|
||||
<Stepper manifest={manifest} extensions={[]} onCreate={jest.fn()} />,
|
||||
);
|
||||
|
||||
expect(getByRole('textbox', { name: 'firstName' })).toHaveValue('John');
|
||||
@@ -331,12 +331,12 @@ describe('Stepper', () => {
|
||||
title: 'initialize formData',
|
||||
};
|
||||
|
||||
const onComplete = jest.fn(async (values: Record<string, JsonValue>) => {
|
||||
const onCreate = jest.fn(async (values: Record<string, JsonValue>) => {
|
||||
expect(values).toHaveProperty('firstName');
|
||||
});
|
||||
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<Stepper manifest={manifest} extensions={[]} onComplete={onComplete} />,
|
||||
<Stepper manifest={manifest} extensions={[]} onCreate={onCreate} />,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
@@ -373,10 +373,12 @@ describe('Stepper', () => {
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<Stepper
|
||||
manifest={manifest}
|
||||
onComplete={jest.fn()}
|
||||
onCreate={jest.fn()}
|
||||
extensions={[]}
|
||||
createButtonText="Make"
|
||||
reviewButtonText="Inspect"
|
||||
components={{
|
||||
createButtonText: <b>Make</b>,
|
||||
reviewButtonText: <i>Inspect</i>,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from '@material-ui/core';
|
||||
import { type IChangeEvent, withTheme } from '@rjsf/core-v5';
|
||||
import { ErrorSchema, FieldValidation } from '@rjsf/utils';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState, type ReactNode } from 'react';
|
||||
import { NextFieldExtensionOptions } from '../../extensions';
|
||||
import { TemplateParameterSchema } from '../../../types';
|
||||
import { createAsyncValidators } from './createAsyncValidators';
|
||||
@@ -59,11 +59,12 @@ export type StepperProps = {
|
||||
templateName?: string;
|
||||
FormProps?: FormProps;
|
||||
initialState?: Record<string, JsonValue>;
|
||||
|
||||
onComplete: (values: Record<string, JsonValue>) => Promise<void>;
|
||||
ReviewStateComponent?: (props: ReviewStateProps) => JSX.Element;
|
||||
createButtonText?: string;
|
||||
reviewButtonText?: string;
|
||||
onCreate: (values: Record<string, JsonValue>) => Promise<void>;
|
||||
components?: {
|
||||
ReviewStateComponent?: (props: ReviewStateProps) => JSX.Element;
|
||||
createButtonText?: ReactNode;
|
||||
reviewButtonText?: ReactNode;
|
||||
};
|
||||
};
|
||||
|
||||
// TODO(blam): We require here, as the types in this package depend on @rjsf/core explicitly
|
||||
@@ -77,12 +78,12 @@ const Form = withTheme(require('@rjsf/material-ui-v5').Theme);
|
||||
*/
|
||||
|
||||
export const Stepper = (stepperProps: StepperProps) => {
|
||||
const { components = {}, ...props } = stepperProps;
|
||||
const {
|
||||
ReviewStateComponent = ReviewState,
|
||||
createButtonText = 'Create',
|
||||
reviewButtonText = 'Review',
|
||||
...props
|
||||
} = stepperProps;
|
||||
} = components;
|
||||
|
||||
const analytics = useAnalytics();
|
||||
const { steps } = useTemplateSchema(props.manifest);
|
||||
@@ -205,7 +206,7 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
props.onComplete(formState);
|
||||
props.onCreate(formState);
|
||||
const name =
|
||||
typeof formState.name === 'string'
|
||||
? formState.name
|
||||
|
||||
@@ -45,7 +45,7 @@ const apis = TestApiRegistry.from(
|
||||
|
||||
describe('<Workflow />', () => {
|
||||
it('should complete a workflow', async () => {
|
||||
const onComplete = jest.fn();
|
||||
const onCreate = jest.fn();
|
||||
const onError = jest.fn();
|
||||
scaffolderApiMock.scaffold.mockResolvedValue({ taskId: 'xyz' });
|
||||
|
||||
@@ -61,6 +61,16 @@ describe('<Workflow />', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Step 2',
|
||||
schema: {
|
||||
properties: {
|
||||
age: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
title: 'React JSON Schema Form Test',
|
||||
});
|
||||
@@ -73,17 +83,22 @@ describe('<Workflow />', () => {
|
||||
## This is markdown
|
||||
- overriding the template description
|
||||
`}
|
||||
onComplete={onComplete}
|
||||
onCreate={onCreate}
|
||||
onError={onError}
|
||||
namespace="default"
|
||||
templateName="docs-template"
|
||||
initialFormState={{
|
||||
initialState={{
|
||||
name: 'prefilled-name',
|
||||
age: '53',
|
||||
}}
|
||||
ReviewStateComponent={() => (
|
||||
<h1>This is a different wrapper for the review page</h1>
|
||||
)}
|
||||
customFieldExtensions={[]}
|
||||
components={{
|
||||
ReviewStateComponent: () => (
|
||||
<h1>This is a different wrapper for the review page</h1>
|
||||
),
|
||||
reviewButtonText: <i>Onwards</i>,
|
||||
createButtonText: <b>Make</b>,
|
||||
}}
|
||||
extensions={[]}
|
||||
/>
|
||||
</ApiProvider>,
|
||||
);
|
||||
@@ -93,14 +108,24 @@ describe('<Workflow />', () => {
|
||||
'Different title than template',
|
||||
);
|
||||
|
||||
const input = getByRole('textbox') as HTMLInputElement;
|
||||
const nameInput = getByRole('textbox', {
|
||||
name: 'name',
|
||||
}) as HTMLInputElement;
|
||||
|
||||
expect(input).toBeInTheDocument();
|
||||
expect(nameInput).toBeInTheDocument();
|
||||
|
||||
expect(input.value).toBe('prefilled-name');
|
||||
expect(nameInput.value).toBe('prefilled-name');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByRole('button', { name: 'Review' }));
|
||||
fireEvent.click(getByRole('button', { name: 'Next' }));
|
||||
});
|
||||
|
||||
const ageInput = getByRole('textbox', { name: 'age' }) as HTMLInputElement;
|
||||
|
||||
expect(ageInput.value).toBe('53');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getByRole('button', { name: 'Onwards' }));
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -111,6 +136,9 @@ describe('<Workflow />', () => {
|
||||
fireEvent.click(getAllByRole('button')[1] as HTMLButtonElement);
|
||||
});
|
||||
|
||||
expect(onComplete).toHaveBeenCalledWith({ name: 'prefilled-name' });
|
||||
expect(onCreate).toHaveBeenCalledWith({
|
||||
name: 'prefilled-name',
|
||||
age: '53',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,16 +21,11 @@ import {
|
||||
Progress,
|
||||
} from '@backstage/core-components';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import type { ErrorTransformer } from '@rjsf/utils';
|
||||
import type { JsonValue } from '@backstage/types';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { type NextFieldExtensionOptions } from '../../extensions/types';
|
||||
import { type FormProps } from '../../types';
|
||||
import { ReviewState, ReviewStateProps } from '../ReviewState/ReviewState';
|
||||
import { useTemplateParameterSchema } from '../../hooks/useTemplateParameterSchema';
|
||||
import { Stepper } from '../Stepper/Stepper';
|
||||
import { Stepper, type StepperProps } from '../Stepper/Stepper';
|
||||
import { SecretsContextProvider } from '../../../secrets/SecretsContext';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(() => ({
|
||||
@@ -48,35 +43,29 @@ const useStyles = makeStyles<BackstageTheme>(() => ({
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface WorkflowProps {
|
||||
export type WorkflowProps = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
namespace: string;
|
||||
templateName: string;
|
||||
customFieldExtensions: NextFieldExtensionOptions<any, any>[];
|
||||
transformErrors?: ErrorTransformer;
|
||||
onComplete: (values: Record<string, JsonValue>) => Promise<void>;
|
||||
onError(error: Error | undefined): JSX.Element | null;
|
||||
initialFormState?: Record<string, JsonValue>;
|
||||
FormProps?: FormProps;
|
||||
ReviewStateComponent?: (props: ReviewStateProps) => JSX.Element;
|
||||
}
|
||||
} & Pick<
|
||||
StepperProps,
|
||||
'extensions' | 'FormProps' | 'components' | 'onCreate' | 'initialState'
|
||||
>;
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
|
||||
const {
|
||||
ReviewStateComponent = ReviewState,
|
||||
FormProps = {},
|
||||
...props
|
||||
} = workflowProps;
|
||||
const { title, description, namespace, templateName, ...props } =
|
||||
workflowProps;
|
||||
|
||||
const styles = useStyles();
|
||||
const templateRef = stringifyEntityRef({
|
||||
kind: 'Template',
|
||||
namespace: props.namespace,
|
||||
name: props.templateName,
|
||||
namespace: namespace,
|
||||
name: templateName,
|
||||
});
|
||||
|
||||
const errorApi = useApi(errorApiRef);
|
||||
@@ -98,26 +87,17 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
|
||||
{loading && <Progress />}
|
||||
{manifest && (
|
||||
<InfoCard
|
||||
title={props.title ?? manifest.title}
|
||||
title={title ?? manifest.title}
|
||||
subheader={
|
||||
<MarkdownContent
|
||||
className={styles.markdown}
|
||||
content={
|
||||
props.description ?? manifest.description ?? 'No description'
|
||||
}
|
||||
content={description ?? manifest.description ?? 'No description'}
|
||||
/>
|
||||
}
|
||||
noPadding
|
||||
titleTypographyProps={{ component: 'h2' }}
|
||||
>
|
||||
<Stepper
|
||||
manifest={manifest}
|
||||
extensions={props.customFieldExtensions}
|
||||
onComplete={props.onComplete}
|
||||
FormProps={FormProps}
|
||||
initialState={props.initialFormState}
|
||||
ReviewStateComponent={ReviewStateComponent}
|
||||
/>
|
||||
<Stepper manifest={manifest} {...props} />
|
||||
</InfoCard>
|
||||
)}
|
||||
</Content>
|
||||
|
||||
@@ -55,7 +55,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
|
||||
name: templateName,
|
||||
});
|
||||
|
||||
const onComplete = async (values: Record<string, JsonValue>) => {
|
||||
const onCreate = async (values: Record<string, JsonValue>) => {
|
||||
const { taskId } = await scaffolderApi.scaffold({
|
||||
templateRef,
|
||||
values,
|
||||
@@ -78,9 +78,9 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
|
||||
<Workflow
|
||||
namespace={namespace}
|
||||
templateName={templateName}
|
||||
onComplete={onComplete}
|
||||
onCreate={onCreate}
|
||||
onError={onError}
|
||||
customFieldExtensions={props.customFieldExtensions}
|
||||
extensions={props.customFieldExtensions}
|
||||
FormProps={props.FormProps}
|
||||
/>
|
||||
</Page>
|
||||
|
||||
Reference in New Issue
Block a user