From fa79ce2b594b2b9d2e6d7f9cbf0fb55807b9aa28 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Fri, 9 Dec 2022 16:02:03 +0000 Subject: [PATCH] allow ReviewState component to be replaced as a prop Signed-off-by: Paul Cowan --- .../EmbeddedScaffolderWorkflow.tsx | 99 ++++++++++++------- .../components/SecurityTab/SecurityTab.tsx | 27 ++++- .../components/ReviewState/ReviewState.tsx | 2 +- .../src/next/components/Stepper/Stepper.tsx | 20 ++-- plugins/scaffolder/src/index.ts | 7 ++ plugins/scaffolder/src/next/Router/Router.tsx | 2 +- .../TemplateWizardContent.tsx | 24 +++-- .../src/next/TemplateWizardContent/index.ts | 1 + .../TemplateWizardPage/TemplateWizardPage.tsx | 3 +- 9 files changed, 126 insertions(+), 59 deletions(-) diff --git a/packages/app/src/components/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx b/packages/app/src/components/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx index 0c06836bbe..909c8bda12 100644 --- a/packages/app/src/components/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx +++ b/packages/app/src/components/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx @@ -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 & { customExtensionsElement?: React.ReactNode; initialFormState?: Record; onComplete: (values: Record) => Promise; onError(error: Error | undefined): JSX.Element | null; FormProps: FormProps -} + frontPage: ReactNode; + finishPage: ReactNode; +}; + +type Display = 'front' | 'workflow' | 'finish'; + +type DisplayComponents = Record; + +type OnCompleteArgs = Parameters[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('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} - - - )} - {showTemplateContent && ( - - )} - + await onComplete(values); + }, + [onComplete], ); + + const DisplayElements: DisplayComponents = { + front: ( + + {frontPage} + + + ), + workflow: ( + + ), + finish: ( + + {finishPage} + + ), + }; + + return <>{DisplayElements[display]}; } diff --git a/packages/app/src/components/SecurityTab/SecurityTab.tsx b/packages/app/src/components/SecurityTab/SecurityTab.tsx index 6d53e542f6..1841a4357e 100644 --- a/packages/app/src/components/SecurityTab/SecurityTab.tsx +++ b/packages/app/src/components/SecurityTab/SecurityTab.tsx @@ -16,12 +16,19 @@ import React from 'react'; import { EmbeddedScaffolderWorkflow } from '../EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow'; +import { Box } from '@material-ui/core'; -interface SecurityTabProps {} +const ReviewWrapper = () => { + return ( + +

This is a different wrapper for the review page

+
+ ); +}; -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) => (

{error?.message ?? 'Houston we have a problem.'}

@@ -29,6 +36,11 @@ export function SecurityTab({}: SecurityTabProps): JSX.Element | null { return ( } + finishPage={ + <> +

Security Insights

+

Congratulations, this application is complete!

+ + } + ReviewStateWrapper={ReviewWrapper} /> ); } diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx index 52ad80c4c2..11fb28b29c 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx @@ -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; }; diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index cde299da7e..638a1fb41c 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -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; onComplete: (values: Record) => Promise; -}; + initialFormState?: Record; + 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) => { ) : ( <> - +