allow ReviewState component to be replaced as a prop
Signed-off-by: Paul Cowan <paul.cowan@cutting.scot>
This commit is contained in:
+62
-37
@@ -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}
|
||||
|
||||
@@ -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>[];
|
||||
|
||||
Reference in New Issue
Block a user