allow ReviewState component to be replaced as a prop

Signed-off-by: Paul Cowan <paul.cowan@cutting.scot>
This commit is contained in:
Paul Cowan
2022-12-09 16:02:03 +00:00
parent 5fb19bf12a
commit fa79ce2b59
9 changed files with 126 additions and 59 deletions
@@ -20,61 +20,86 @@ import {
TemplateContent,
useGetCustomFields,
} from '@backstage/plugin-scaffolder';
import { Button } from '@material-ui/core';
import type { FormProps } from '@backstage/plugin-scaffolder-react';
import type { TemplateContentProps } from '@backstage/plugin-scaffolder';
import { Box, Button } from '@material-ui/core';
import type { JsonValue } from '@backstage/types';
import { FormProps } from '@backstage/plugin-scaffolder-react';
interface WorkflowProps {
frontPage: ReactNode;
namespace: string;
templateName: string;
type WorkflowProps = Omit<TemplateContentProps, 'customFieldExtensions'> & {
customExtensionsElement?: React.ReactNode;
initialFormState?: Record<string, JsonValue>;
onComplete: (values: Record<string, JsonValue>) => Promise<void>;
onError(error: Error | undefined): JSX.Element | null;
FormProps: FormProps
}
frontPage: ReactNode;
finishPage: ReactNode;
};
type Display = 'front' | 'workflow' | 'finish';
type DisplayComponents = Record<Display, JSX.Element>;
type OnCompleteArgs = Parameters<TemplateContentProps['onComplete']>[0];
export function EmbeddedScaffolderWorkflow({
namespace,
templateName,
customExtensionsElement = <></>,
frontPage,
onComplete,
finishPage,
onComplete = async (_values: OnCompleteArgs) => void 0,
onError,
title,
description,
ReviewStateWrapper,
}: WorkflowProps): JSX.Element {
const [showTemplateContent, setShowTemplateContent] = useState(false);
const [display, setDisplay] = useState<Display>('front');
const fieldExtensions = useGetCustomFields(customExtensionsElement);
const showContent = !showTemplateContent;
const startTemplate = useCallback(() => setDisplay('workflow'), []);
const startTemplate = useCallback(() => setShowTemplateContent(true), []);
const onWorkFlowComplete = useCallback(
async (values: OnCompleteArgs) => {
setDisplay('finish');
return (
<>
{showContent && (
<>
{frontPage}
<Button variant="contained" onClick={startTemplate}>
SETUP
</Button>
</>
)}
{showTemplateContent && (
<TemplateContent
namespace={namespace}
templateName={templateName}
onComplete={onComplete}
onError={onError}
customFieldExtensions={fieldExtensions}
initialFormState={{
name: 'prefilled-name',
description: 'prefilled description',
owner: 'acme-corp',
repoUrl: 'github.com?owner=component&repo=component',
}}
/>
)}
</>
await onComplete(values);
},
[onComplete],
);
const DisplayElements: DisplayComponents = {
front: (
<Box display="flex" alignItems="center" flexDirection="column">
{frontPage}
<Button variant="contained" onClick={startTemplate}>
SETUP
</Button>
</Box>
),
workflow: (
<TemplateContent
title={title}
description={description}
namespace={namespace}
templateName={templateName}
onComplete={onWorkFlowComplete}
onError={onError}
customFieldExtensions={fieldExtensions}
initialFormState={{
name: 'prefilled-name',
description: 'prefilled description',
owner: 'acme-corp',
repoUrl: 'github.com?owner=component&repo=component',
}}
ReviewStateWrapper={ReviewStateWrapper}
/>
),
finish: (
<Box display="flex" alignItems="center" flexDirection="column">
{finishPage}
</Box>
),
};
return <>{DisplayElements[display]}</>;
}
@@ -16,12 +16,19 @@
import React from 'react';
import { EmbeddedScaffolderWorkflow } from '../EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow';
import { Box } from '@material-ui/core';
interface SecurityTabProps {}
const ReviewWrapper = () => {
return (
<Box display="flex" alignItems="center" flexDirection="column">
<h1>This is a different wrapper for the review page</h1>
</Box>
);
};
export function SecurityTab({}: SecurityTabProps): JSX.Element | null {
// eslint-disable-next-line no-alert
const onComplete = async () => alert('success!!!!');
export function SecurityTab(): JSX.Element | null {
// eslint-disable-next-line no-console
const onComplete = async () => console.log('onComplete called from ');
const onError = (error: Error | undefined) => (
<h2>{error?.message ?? 'Houston we have a problem.'}</h2>
@@ -29,6 +36,11 @@ export function SecurityTab({}: SecurityTabProps): JSX.Element | null {
return (
<EmbeddedScaffolderWorkflow
title="Altered title"
description={`
## This is markdown
- overriding the template description
`}
onComplete={onComplete}
onError={onError}
namespace="default"
@@ -46,6 +58,13 @@ export function SecurityTab({}: SecurityTabProps): JSX.Element | null {
</p>
</>
}
finishPage={
<>
<h1>Security Insights</h1>
<p>Congratulations, this application is complete!</p>
</>
}
ReviewStateWrapper={ReviewWrapper}
/>
);
}
@@ -23,7 +23,7 @@ import { ParsedTemplateSchema } from '../../hooks/useTemplateSchema';
* The props for the {@link ReviewState} component.
* @alpha
*/
export type ReviewStateProps = {
export interface ReviewStateProps {
schemas: ParsedTemplateSchema[];
formState: JsonObject;
};
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useAnalytics, useApiHolder } from '@backstage/core-plugin-api';
import { useAnalytics, useApiHolder, useRouteRefParams } from '@backstage/core-plugin-api';
import { JsonValue } from '@backstage/types';
import {
Stepper as MuiStepper,
@@ -28,12 +28,12 @@ import React, { useCallback, useMemo, useState } from 'react';
import { NextFieldExtensionOptions } from '../../extensions';
import { TemplateParameterSchema } from '../../../types';
import { createAsyncValidators } from './createAsyncValidators';
import type { FormProps } from '../../types';
import { ReviewState, type ReviewStateProps } from '../ReviewState';
import { useTemplateSchema } from '../../hooks/useTemplateSchema';
import { ReviewState } from '../ReviewState';
import { useFormDataFromQuery } from '../../hooks/useFormDataFromQuery';
import validator from '@rjsf/validator-ajv6';
import { useFormDataFromQuery } from '../../hooks';
import { FormProps } from '../../types';
const useStyles = makeStyles(theme => ({
backButton: {
@@ -62,7 +62,9 @@ export type StepperProps = {
initialState?: Record<string, JsonValue>;
onComplete: (values: Record<string, JsonValue>) => Promise<void>;
};
initialFormState?: Record<string, JsonValue>;
ReviewStateWrapper?: (props: ReviewStateProps) => JSX.Element;
}
// TODO(blam): We require here, as the types in this package depend on @rjsf/core explicitly
// which is what we're using here as the default types, it needs to depend on @rjsf/core-v5 because
@@ -73,7 +75,11 @@ const Form = withTheme(require('@rjsf/material-ui-v5').Theme);
* The `Stepper` component is the Wizard that is rendered when a user selects a template
* @alpha
*/
export const Stepper = (props: StepperProps) => {
export const Stepper = ({
ReviewStateWrapper = ReviewState,
...props
}: StepperProps) => {
const analytics = useAnalytics();
const { steps } = useTemplateSchema(props.manifest);
const apiHolder = useApiHolder();
@@ -183,7 +189,7 @@ export const Stepper = (props: StepperProps) => {
</Form>
) : (
<>
<ReviewState formState={formState} schemas={steps} />
<ReviewStateWrapper formState={formState} schemas={steps} />
<div className={styles.footer}>
<Button
onClick={handleBack}
+7
View File
@@ -45,6 +45,13 @@ export * from './deprecated';
/** next exports */
export { NextScaffolderPage } from './plugin';
export {
TemplateContent,
useGetCustomFields,
type TemplateContentProps,
} from './next';
export type { NextRouterProps } from './next';
export type { TemplateGroupFilter } from './next';
export {
nextRouteRef,
nextScaffolderTaskRouteRef,
@@ -26,7 +26,7 @@ import {
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import { TemplateGroupFilter } from '../TemplateListPage/TemplateGroups';
import { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from '../../extensions/default';
import { FormProps } from '../types';
import type { FormProps } from '@backstage/plugin-scaffolder-react';
import { nextSelectedTemplateRouteRef } from '../routes';
/**
@@ -28,6 +28,8 @@ import { makeStyles } from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
import {
ReviewState,
type ReviewStateProps,
SecretsContextProvider,
Stepper,
type NextFieldExtensionOptions,
@@ -46,7 +48,9 @@ const useStyles = makeStyles<BackstageTheme>(() => ({
},
}));
export interface TemplateWizardContentProps {
export interface TemplateContentProps {
title?: string;
description?: string;
namespace: string;
templateName: string;
customFieldExtensions: NextFieldExtensionOptions<any, any>[];
@@ -55,11 +59,13 @@ export interface TemplateWizardContentProps {
onError(error: Error | undefined): JSX.Element | null;
initialFormState?: Record<string, JsonValue>;
FormProps?: FormProps;
ReviewStateWrapper?: (props: ReviewStateProps) => JSX.Element;
}
export const TemplateWizardContent = (
props: TemplateWizardContentProps,
): JSX.Element | null => {
export const TemplateWizardContent = ({
ReviewStateWrapper = ReviewState,
...props
}: TemplateContentProps): JSX.Element | null => {
const styles = useStyles();
const templateRef = stringifyEntityRef({
kind: 'Template',
@@ -86,11 +92,13 @@ export const TemplateWizardContent = (
{loading && <Progress />}
{manifest && (
<InfoCard
title={manifest.title}
title={props.title ?? manifest.title}
subheader={
<MarkdownContent
className={styles.markdown}
content={manifest.description ?? 'No description'}
content={
props.description ?? manifest.description ?? 'No description'
}
/>
}
noPadding
@@ -101,6 +109,8 @@ export const TemplateWizardContent = (
extensions={props.customFieldExtensions}
onComplete={props.onComplete}
FormProps={props.FormProps}
initialFormState={props.initialFormState}
ReviewStateWrapper={ReviewStateWrapper}
/>
</InfoCard>
)}
@@ -108,7 +118,7 @@ export const TemplateWizardContent = (
);
};
export const TemplateContent = (props: TemplateWizardContentProps) => (
export const TemplateContent = (props: TemplateContentProps) => (
<SecretsContextProvider>
<TemplateWizardContent {...props} />
</SecretsContextProvider>
@@ -14,6 +14,7 @@
* limitations under the License.
*/
export {
type TemplateContentProps,
TemplateWizardContent,
TemplateContent,
} from './TemplateWizardContent';
@@ -29,11 +29,10 @@ import {
} from '@backstage/plugin-scaffolder-react';
import useAsync from 'react-use/lib/useAsync';
import { JsonValue } from '@backstage/types';
import { FormProps } from '../types';
import { FormProps } from '@backstage/plugin-scaffolder-react';
import { nextRouteRef } from '../routes';
import { scaffolderTaskRouteRef, selectedTemplateRouteRef } from '../../routes';
import { TemplateWizardContent } from '../TemplateWizardContent/TemplateWizardContent';
import { Header, Page } from '@backstage/core-components';
type TemplateWizardPageProps = {
customFieldExtensions: NextFieldExtensionOptions<any, any>[];