From 2ca355e4913ed7b2446b1a5d515a85d9be63f99d Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 8 Dec 2022 12:41:26 +0000 Subject: [PATCH 01/33] Add SecurityTab Signed-off-by: Paul Cowan --- .../components/SecurityTab/SecurityTab.tsx | 25 +++++++++++++++++++ .../app/src/components/SecurityTab/index.ts | 17 +++++++++++++ .../components/SecurityTab/showSecurityTab.ts | 22 ++++++++++++++++ .../app/src/components/catalog/EntityPage.tsx | 10 ++++++++ .../examples/all-components.yaml | 24 +++++++++--------- .../components/wayback-search-component.yaml | 2 ++ 6 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 packages/app/src/components/SecurityTab/SecurityTab.tsx create mode 100644 packages/app/src/components/SecurityTab/index.ts create mode 100644 packages/app/src/components/SecurityTab/showSecurityTab.ts diff --git a/packages/app/src/components/SecurityTab/SecurityTab.tsx b/packages/app/src/components/SecurityTab/SecurityTab.tsx new file mode 100644 index 0000000000..2adca3b0ef --- /dev/null +++ b/packages/app/src/components/SecurityTab/SecurityTab.tsx @@ -0,0 +1,25 @@ +/* eslint-disable no-console */ +/* + * 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 { useEntity } from '@backstage/plugin-catalog-react'; + +export function SecurityTab(): JSX.Element | null { + const entity = useEntity(); + console.log(entity); + return

Security

; +} diff --git a/packages/app/src/components/SecurityTab/index.ts b/packages/app/src/components/SecurityTab/index.ts new file mode 100644 index 0000000000..2c7908e958 --- /dev/null +++ b/packages/app/src/components/SecurityTab/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { SecurityTab } from './SecurityTab'; +export { showSecurityTab } from './showSecurityTab'; diff --git a/packages/app/src/components/SecurityTab/showSecurityTab.ts b/packages/app/src/components/SecurityTab/showSecurityTab.ts new file mode 100644 index 0000000000..364436e44e --- /dev/null +++ b/packages/app/src/components/SecurityTab/showSecurityTab.ts @@ -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 { Entity } from '@backstage/catalog-model'; + +export const SECURITY_ANNOTATION = 'myorg.org/security'; + +/** @public */ +export const showSecurityTab = (entity: Entity) => + Boolean(entity.metadata.annotations?.[SECURITY_ANNOTATION]); diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 02b00f038c..6288dc86ee 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -154,6 +154,8 @@ import { ReportIssue, } from '@backstage/plugin-techdocs-module-addons-contrib'; import { EntityCostInsightsContent } from '@backstage/plugin-cost-insights'; +import { SecurityTab } from '../SecurityTab/SecurityTab'; +import { showSecurityTab } from '../SecurityTab'; const customEntityFilterKind = ['Component', 'API', 'System']; @@ -400,6 +402,12 @@ const overviewContent = ( ); +const securityContent = ( + + + +); + const serviceEntityPage = ( @@ -440,6 +448,8 @@ const serviceEntityPage = ( {techdocsContent} + {securityContent} + Date: Thu, 8 Dec 2022 14:46:43 +0000 Subject: [PATCH 02/33] move TemplateWizardPage content into separate component Signed-off-by: Paul Cowan --- .../components/SecurityTab/SecurityTab.tsx | 31 ++++- plugins/scaffolder/src/next/Router/Router.tsx | 2 + plugins/scaffolder/src/next/Router/index.ts | 2 +- .../TemplateWizardContent.tsx | 112 ++++++++++++++++++ .../src/next/TemplateWizardContent/index.ts | 16 +++ .../TemplateWizardPage/TemplateWizardPage.tsx | 84 ++----------- plugins/scaffolder/src/next/index.ts | 1 + 7 files changed, 169 insertions(+), 79 deletions(-) create mode 100644 plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx create mode 100644 plugins/scaffolder/src/next/TemplateWizardContent/index.ts diff --git a/packages/app/src/components/SecurityTab/SecurityTab.tsx b/packages/app/src/components/SecurityTab/SecurityTab.tsx index 2adca3b0ef..af4d66320d 100644 --- a/packages/app/src/components/SecurityTab/SecurityTab.tsx +++ b/packages/app/src/components/SecurityTab/SecurityTab.tsx @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ /* * Copyright 2022 The Backstage Authors * @@ -16,10 +15,30 @@ */ import React from 'react'; -import { useEntity } from '@backstage/plugin-catalog-react'; +import { + TemplateWizardContent, + useGetCustomFields, +} from '@backstage/plugin-scaffolder'; -export function SecurityTab(): JSX.Element | null { - const entity = useEntity(); - console.log(entity); - return

Security

; +interface SecurityTabProps { + customExtensionsElement: React.ReactNode; +} + +export function SecurityTab(props: SecurityTabProps): JSX.Element | null { + // eslint-disable-next-line no-alert + const onComplete = async () => alert('success!!!!'); + const onError = (error: Error | undefined) => ( +

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

+ ); + const fieldExtensions = useGetCustomFields(props.customExtensionsElement); + + return ( + + ); } diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 784e17dd55..a7bf16e033 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -52,7 +52,9 @@ export type NextRouterProps = { */ export const Router = (props: PropsWithChildren) => { const { components: { TemplateCardComponent } = {} } = props; + const outlet = useOutlet() || props.children; + const customFieldExtensions = useCustomFieldExtensions(outlet); const fieldExtensions = [ diff --git a/plugins/scaffolder/src/next/Router/index.ts b/plugins/scaffolder/src/next/Router/index.ts index dac1db7b3a..b4df375d1d 100644 --- a/plugins/scaffolder/src/next/Router/index.ts +++ b/plugins/scaffolder/src/next/Router/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { Router } from './Router'; +export { Router, useGetCustomFields } from './Router'; export type { NextRouterProps } from './Router'; diff --git a/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx b/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx new file mode 100644 index 0000000000..4dfc9be9e5 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx @@ -0,0 +1,112 @@ +/* + * 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, { useEffect } from 'react'; +import { + Content, + Header, + InfoCard, + MarkdownContent, + Page, + Progress, +} from '@backstage/core-components'; +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { useTemplateParameterSchema } from '../TemplateWizardPage/TemplateWizardPage'; +import { NextFieldExtensionOptions } from '../../extensions'; +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 { Stepper } from '../TemplateWizardPage/Stepper'; + +const useStyles = makeStyles(() => ({ + markdown: { + /** to make the styles for React Markdown not leak into the description */ + '& :first-child': { + marginTop: 0, + }, + '& :last-child': { + marginBottom: 0, + }, + }, +})); + +export interface TemplateWizardContentProps { + namespace: string; + templateName: string; + customFieldExtensions: NextFieldExtensionOptions[]; + transformErrors?: ErrorTransformer; + onComplete: (values: Record) => Promise; + onError(error: Error | undefined): JSX.Element | null; +} + +export const TemplateWizardContent = ( + props: TemplateWizardContentProps, +): JSX.Element | null => { + const styles = useStyles(); + const templateRef = stringifyEntityRef({ + kind: 'Template', + namespace: props.namespace, + name: props.templateName, + }); + + const errorApi = useApi(errorApiRef); + + const { loading, manifest, error } = useTemplateParameterSchema(templateRef); + + useEffect(() => { + if (error) { + errorApi.post(new Error(`Failed to load template, ${error}`)); + } + }, [error, errorApi]); + + if (error) { + return props.onError(error); + } + + return ( + +
+ + {loading && } + {manifest && ( + + } + noPadding + titleTypographyProps={{ component: 'h2' }} + > + + + )} + + + ); +}; diff --git a/plugins/scaffolder/src/next/TemplateWizardContent/index.ts b/plugins/scaffolder/src/next/TemplateWizardContent/index.ts new file mode 100644 index 0000000000..3fda3efb4f --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateWizardContent/index.ts @@ -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 { TemplateWizardContent } from './TemplateWizardContent'; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index c4e8a51e1f..4ff47e41e9 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -13,20 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect } from 'react'; -import { - Page, - Header, - Content, - Progress, - InfoCard, - MarkdownContent, -} from '@backstage/core-components'; +import React from 'react'; import { Navigate, useNavigate } from 'react-router-dom'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { AnalyticsContext, - errorApiRef, useApi, useRouteRef, useRouteRefParams, @@ -34,37 +25,21 @@ import { import { scaffolderApiRef, useTemplateSecrets, -} from '@backstage/plugin-scaffolder-react'; -import useAsync from 'react-use/lib/useAsync'; -import { makeStyles } from '@material-ui/core'; -import { BackstageTheme } from '@backstage/theme'; -import { - Stepper, NextFieldExtensionOptions, } from '@backstage/plugin-scaffolder-react'; +import useAsync from 'react-use/lib/useAsync'; import { JsonValue } from '@backstage/types'; import { FormProps } from '../types'; import { nextRouteRef } from '../routes'; import { scaffolderTaskRouteRef, selectedTemplateRouteRef } from '../../routes'; +import { TemplateWizardContent } from '../TemplateWizardContent/TemplateWizardContent'; type TemplateWizardPageProps = { customFieldExtensions: NextFieldExtensionOptions[]; FormProps?: FormProps; }; -const useStyles = makeStyles(() => ({ - markdown: { - /** to make the styles for React Markdown not leak into the description */ - '& :first-child': { - marginTop: 0, - }, - '& :last-child': { - marginBottom: 0, - }, - }, -})); - -const useTemplateParameterSchema = (templateRef: string) => { +export const useTemplateParameterSchema = (templateRef: string) => { const scaffolderApi = useApi(scaffolderApiRef); const { value, loading, error } = useAsync( () => scaffolderApi.getTemplateParameterSchema(templateRef), @@ -75,7 +50,6 @@ const useTemplateParameterSchema = (templateRef: string) => { }; export const TemplateWizardPage = (props: TemplateWizardPageProps) => { - const styles = useStyles(); const rootRef = useRouteRef(nextRouteRef); const taskRoute = useRouteRef(scaffolderTaskRouteRef); const { secrets } = useTemplateSecrets(); @@ -91,9 +65,6 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { name: templateName, }); - const errorApi = useApi(errorApiRef); - const { loading, manifest, error } = useTemplateParameterSchema(templateRef); - const onComplete = async (values: Record) => { const { taskId } = await scaffolderApi.scaffold({ templateRef, @@ -104,48 +75,17 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { navigate(taskRoute({ taskId })); }; - useEffect(() => { - if (error) { - errorApi.post(new Error(`Failed to load template, ${error}`)); - } - }, [error, errorApi]); - - if (error) { - return ; - } + const onError = () => ; return ( - -
- - {loading && } - {manifest && ( - - } - noPadding - titleTypographyProps={{ component: 'h2' }} - > - - - )} - - + ); }; diff --git a/plugins/scaffolder/src/next/index.ts b/plugins/scaffolder/src/next/index.ts index 1ca9096459..fd88d57551 100644 --- a/plugins/scaffolder/src/next/index.ts +++ b/plugins/scaffolder/src/next/index.ts @@ -18,3 +18,4 @@ export * from './TemplateListPage'; export * from './TemplateWizardPage'; export * from './types'; export * from './routes'; +export * from './TemplateWizardContent'; From a647ff761ee28567caada8027ff8cd378f535507 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 8 Dec 2022 14:53:17 +0000 Subject: [PATCH 03/33] useGetCustomFields Signed-off-by: Paul Cowan --- .../app/src/components/SecurityTab/SecurityTab.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/SecurityTab/SecurityTab.tsx b/packages/app/src/components/SecurityTab/SecurityTab.tsx index af4d66320d..3a40f9c1d7 100644 --- a/packages/app/src/components/SecurityTab/SecurityTab.tsx +++ b/packages/app/src/components/SecurityTab/SecurityTab.tsx @@ -21,16 +21,20 @@ import { } from '@backstage/plugin-scaffolder'; interface SecurityTabProps { - customExtensionsElement: React.ReactNode; + customExtensionsElement?: React.ReactNode; } -export function SecurityTab(props: SecurityTabProps): JSX.Element | null { +export function SecurityTab({ + customExtensionsElement = <>, +}: SecurityTabProps): JSX.Element | null { // eslint-disable-next-line no-alert const onComplete = async () => alert('success!!!!'); + const onError = (error: Error | undefined) => (

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

); - const fieldExtensions = useGetCustomFields(props.customExtensionsElement); + + const fieldExtensions = useGetCustomFields(customExtensionsElement); return ( Date: Thu, 8 Dec 2022 15:12:43 +0000 Subject: [PATCH 04/33] wrap TemplateWizardContent in Signed-off-by: Paul Cowan --- .../app/src/components/SecurityTab/SecurityTab.tsx | 4 ++-- .../TemplateWizardContent/TemplateWizardContent.tsx | 10 ++++++++++ .../scaffolder/src/next/TemplateWizardContent/index.ts | 5 ++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/SecurityTab/SecurityTab.tsx b/packages/app/src/components/SecurityTab/SecurityTab.tsx index 3a40f9c1d7..69c2455ea5 100644 --- a/packages/app/src/components/SecurityTab/SecurityTab.tsx +++ b/packages/app/src/components/SecurityTab/SecurityTab.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { - TemplateWizardContent, + TemplateContent, useGetCustomFields, } from '@backstage/plugin-scaffolder'; @@ -37,7 +37,7 @@ export function SecurityTab({ const fieldExtensions = useGetCustomFields(customExtensionsElement); return ( - (() => ({ markdown: { @@ -110,3 +114,9 @@ export const TemplateWizardContent = ( ); }; + +export const TemplateContent = (props: TemplateWizardContentProps) => ( + + + +); diff --git a/plugins/scaffolder/src/next/TemplateWizardContent/index.ts b/plugins/scaffolder/src/next/TemplateWizardContent/index.ts index 3fda3efb4f..fdb0589cc2 100644 --- a/plugins/scaffolder/src/next/TemplateWizardContent/index.ts +++ b/plugins/scaffolder/src/next/TemplateWizardContent/index.ts @@ -13,4 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { TemplateWizardContent } from './TemplateWizardContent'; +export { + TemplateWizardContent, + TemplateContent, +} from './TemplateWizardContent'; From e2789aea3d0bd0d170e3b5cff1fab2a7fce3d6fa Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 8 Dec 2022 15:59:28 +0000 Subject: [PATCH 05/33] prefill template content Signed-off-by: Paul Cowan --- .../app/src/components/SecurityTab/SecurityTab.tsx | 14 ++++++++++++-- packages/app/src/components/catalog/EntityPage.tsx | 2 +- .../TemplateWizardContent.tsx | 2 ++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/SecurityTab/SecurityTab.tsx b/packages/app/src/components/SecurityTab/SecurityTab.tsx index 69c2455ea5..87311ed34f 100644 --- a/packages/app/src/components/SecurityTab/SecurityTab.tsx +++ b/packages/app/src/components/SecurityTab/SecurityTab.tsx @@ -21,10 +21,14 @@ import { } from '@backstage/plugin-scaffolder'; interface SecurityTabProps { + namespace: string; + templateName: string; customExtensionsElement?: React.ReactNode; } export function SecurityTab({ + namespace, + templateName, customExtensionsElement = <>, }: SecurityTabProps): JSX.Element | null { // eslint-disable-next-line no-alert @@ -38,11 +42,17 @@ export function SecurityTab({ return ( ); } diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 6288dc86ee..25f9b4fdc8 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -404,7 +404,7 @@ const overviewContent = ( const securityContent = ( - + ); diff --git a/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx b/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx index ced1be5fdc..72d3b6c921 100644 --- a/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx @@ -55,6 +55,7 @@ export interface TemplateWizardContentProps { transformErrors?: ErrorTransformer; onComplete: (values: Record) => Promise; onError(error: Error | undefined): JSX.Element | null; + initialFormState?: Record; } export const TemplateWizardContent = ( @@ -107,6 +108,7 @@ export const TemplateWizardContent = ( extensions={props.customFieldExtensions} onComplete={props.onComplete} transformErrors={props.transformErrors} + initialFormState={props.initialFormState} /> )} From 5fb19bf12aad8bec8bb482448eac3943c61bff09 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 8 Dec 2022 17:18:55 +0000 Subject: [PATCH 06/33] EmbeddedScaffolderWorkflow component Signed-off-by: Paul Cowan --- .../EmbeddedScaffolderWorkflow.tsx | 80 +++++++++++++++++++ .../components/SecurityTab/SecurityTab.tsx | 45 +++++------ .../app/src/components/catalog/EntityPage.tsx | 2 +- .../TemplateWizardContent.tsx | 63 +++++++-------- .../TemplateWizardPage/TemplateWizardPage.tsx | 2 + 5 files changed, 129 insertions(+), 63 deletions(-) create mode 100644 packages/app/src/components/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx diff --git a/packages/app/src/components/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx b/packages/app/src/components/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx new file mode 100644 index 0000000000..0c06836bbe --- /dev/null +++ b/packages/app/src/components/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx @@ -0,0 +1,80 @@ +/* + * 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, { useState, useCallback } from 'react'; +import type { ReactNode } from 'react'; +import { + TemplateContent, + useGetCustomFields, +} from '@backstage/plugin-scaffolder'; +import { 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; + customExtensionsElement?: React.ReactNode; + initialFormState?: Record; + onComplete: (values: Record) => Promise; + onError(error: Error | undefined): JSX.Element | null; + FormProps: FormProps +} + +export function EmbeddedScaffolderWorkflow({ + namespace, + templateName, + customExtensionsElement = <>, + frontPage, + onComplete, + onError, +}: WorkflowProps): JSX.Element { + const [showTemplateContent, setShowTemplateContent] = useState(false); + const fieldExtensions = useGetCustomFields(customExtensionsElement); + + const showContent = !showTemplateContent; + + const startTemplate = useCallback(() => setShowTemplateContent(true), []); + + return ( + <> + {showContent && ( + <> + {frontPage} + + + )} + {showTemplateContent && ( + + )} + + ); +} diff --git a/packages/app/src/components/SecurityTab/SecurityTab.tsx b/packages/app/src/components/SecurityTab/SecurityTab.tsx index 87311ed34f..6d53e542f6 100644 --- a/packages/app/src/components/SecurityTab/SecurityTab.tsx +++ b/packages/app/src/components/SecurityTab/SecurityTab.tsx @@ -15,22 +15,11 @@ */ import React from 'react'; -import { - TemplateContent, - useGetCustomFields, -} from '@backstage/plugin-scaffolder'; +import { EmbeddedScaffolderWorkflow } from '../EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow'; -interface SecurityTabProps { - namespace: string; - templateName: string; - customExtensionsElement?: React.ReactNode; -} +interface SecurityTabProps {} -export function SecurityTab({ - namespace, - templateName, - customExtensionsElement = <>, -}: SecurityTabProps): JSX.Element | null { +export function SecurityTab({}: SecurityTabProps): JSX.Element | null { // eslint-disable-next-line no-alert const onComplete = async () => alert('success!!!!'); @@ -38,21 +27,25 @@ export function SecurityTab({

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

); - const fieldExtensions = useGetCustomFields(customExtensionsElement); - return ( - +

Security Insights

+

+ Security insights actionable advice to improve security posture of + your application +

+

+ You must complete on-boarding process to activate security insights + on this project. +

+ + } /> ); } diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 25f9b4fdc8..6288dc86ee 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -404,7 +404,7 @@ const overviewContent = ( const securityContent = ( - + ); diff --git a/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx b/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx index 72d3b6c921..744f0a6d6d 100644 --- a/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardContent/TemplateWizardContent.tsx @@ -16,25 +16,23 @@ import React, { useEffect } from 'react'; import { Content, - Header, InfoCard, MarkdownContent, - Page, Progress, } from '@backstage/core-components'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { useTemplateParameterSchema } from '../TemplateWizardPage/TemplateWizardPage'; -import { NextFieldExtensionOptions } from '../../extensions'; 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 { Stepper } from '../TemplateWizardPage/Stepper'; import { - SecretsContext, SecretsContextProvider, -} from '../../components/secrets/SecretsContext'; + Stepper, + type NextFieldExtensionOptions, +} from '@backstage/plugin-scaffolder-react'; +import { type FormProps } from '@backstage/plugin-scaffolder-react'; const useStyles = makeStyles(() => ({ markdown: { @@ -56,6 +54,7 @@ export interface TemplateWizardContentProps { onComplete: (values: Record) => Promise; onError(error: Error | undefined): JSX.Element | null; initialFormState?: Record; + FormProps?: FormProps; } export const TemplateWizardContent = ( @@ -83,37 +82,29 @@ export const TemplateWizardContent = ( } return ( - -
- - {loading && } - {manifest && ( - - } - noPadding - titleTypographyProps={{ component: 'h2' }} - > - + {loading && } + {manifest && ( + - - )} - - + } + noPadding + titleTypographyProps={{ component: 'h2' }} + > + + + )} + ); }; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index 4ff47e41e9..e0be68dbe0 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -33,6 +33,7 @@ import { FormProps } from '../types'; 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[]; @@ -85,6 +86,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { onComplete={onComplete} onError={onError} customFieldExtensions={props.customFieldExtensions} + FormProps={props.FormProps} /> ); From fa79ce2b594b2b9d2e6d7f9cbf0fb55807b9aa28 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Fri, 9 Dec 2022 16:02:03 +0000 Subject: [PATCH 07/33] 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) => { ) : ( <> - +
- - ), - workflow: ( - - ), - finish: ( - - {finishPage} - - ), - }; - - return <>{DisplayElements[display]}; -} diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index eb23284649..8fb6f0a31e 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -27,29 +27,18 @@ import { useTemplateSecrets, NextFieldExtensionOptions, } from '@backstage/plugin-scaffolder-react'; -import useAsync from 'react-use/lib/useAsync'; import { JsonValue } from '@backstage/types'; import { FormProps } from '@backstage/plugin-scaffolder-react'; import { nextRouteRef } from '../routes'; import { scaffolderTaskRouteRef, selectedTemplateRouteRef } from '../../routes'; import { Header, Page } from '@backstage/core-components'; -import { Workflow } from '../Workflow/Workflow'; +import { Workflow } from '@backstage/plugin-scaffolder-react'; type TemplateWizardPageProps = { customFieldExtensions: NextFieldExtensionOptions[]; FormProps?: FormProps; }; -export const useTemplateParameterSchema = (templateRef: string) => { - const scaffolderApi = useApi(scaffolderApiRef); - const { value, loading, error } = useAsync( - () => scaffolderApi.getTemplateParameterSchema(templateRef), - [scaffolderApi, templateRef], - ); - - return { manifest: value, loading, error }; -}; - export const TemplateWizardPage = (props: TemplateWizardPageProps) => { const rootRef = useRouteRef(nextRouteRef); const taskRoute = useRouteRef(scaffolderTaskRouteRef); diff --git a/plugins/scaffolder/src/next/index.ts b/plugins/scaffolder/src/next/index.ts index c03e7f9aa4..1ca9096459 100644 --- a/plugins/scaffolder/src/next/index.ts +++ b/plugins/scaffolder/src/next/index.ts @@ -18,7 +18,3 @@ export * from './TemplateListPage'; export * from './TemplateWizardPage'; export * from './types'; export * from './routes'; -export * from './Workflow'; -export * from './EmbeddedScaffolderWorkflow'; -export type { WorkflowProps } from './Workflow'; - From b965a83a1f0134250a1d3e585f83664992ea194b Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 12 Jan 2023 17:02:21 +0000 Subject: [PATCH 24/33] add tests to scaffoler-react Signed-off-by: Paul Cowan --- plugins/scaffolder-react/package.json | 2 +- .../components/ReviewState/ReviewState.tsx | 2 +- .../next/components/Stepper/Stepper.test.tsx | 38 ++++++ .../src/next/components/Stepper/Stepper.tsx | 9 +- .../components/Workflow/Workflow.test.tsx | 116 ++++++++++++++++++ .../src/next/components/Workflow/Workflow.tsx | 2 +- yarn.lock | 1 + 7 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index d08edb75fb..e44a1d94ee 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -57,8 +57,8 @@ "json-schema": "^0.4.0", "json-schema-library": "^7.3.9", "lodash": "^4.17.21", - "react-use": "^17.2.4", "qs": "^6.9.4", + "react-use": "^17.2.4", "zen-observable": "^0.10.0", "zod": "~3.18.0", "zod-to-json-schema": "~3.18.0" diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx index 11fb28b29c..56abc52469 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx @@ -26,7 +26,7 @@ import { ParsedTemplateSchema } from '../../hooks/useTemplateSchema'; export interface ReviewStateProps { schemas: ParsedTemplateSchema[]; formState: JsonObject; -}; +} /** * The component used by the {@link Stepper} to render the review step. diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx index 786f45b05d..e460afbdfc 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx @@ -352,4 +352,42 @@ describe('Stepper', () => { // flush promises return new Promise(process.nextTick); }); + + it('should override the Create and Review button text', async () => { + const manifest: TemplateParameterSchema = { + title: 'Custom Fields', + steps: [ + { + title: 'Test', + schema: { + properties: { + name: { + type: 'string', + }, + }, + }, + }, + ], + }; + + const { getByRole } = await renderInTestApp( + , + ); + + await act(async () => { + await fireEvent.click(getByRole('button', { name: 'Inspect' })); + }); + + expect(getByRole('button', { name: 'Make' })).toBeInTheDocument(); + + await act(async () => { + await fireEvent.click(getByRole('button', { name: 'Make' })); + }); + }); }); diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 6bd3ec8c71..5d0ad29513 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -61,8 +61,9 @@ export type StepperProps = { initialState?: Record; onComplete: (values: Record) => Promise; - initialFormState?: Record; ReviewStateWrapper?: (props: ReviewStateProps) => JSX.Element; + createButtonText?: string; + reviewButtonText?: string; }; // TODO(blam): We require here, as the types in this package depend on @rjsf/core explicitly @@ -77,6 +78,8 @@ const Form = withTheme(require('@rjsf/material-ui-v5').Theme); export const Stepper = ({ ReviewStateWrapper = ReviewState, + createButtonText = 'Create', + reviewButtonText = 'Review', ...props }: StepperProps) => { const analytics = useAnalytics(); @@ -182,7 +185,7 @@ export const Stepper = ({ Back
@@ -211,7 +214,7 @@ export const Stepper = ({ ); }} > - Create + {createButtonText} diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx new file mode 100644 index 0000000000..8be6d58407 --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx @@ -0,0 +1,116 @@ +/* + * 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 { ApiProvider } from '@backstage/core-app-api'; +import { + MockAnalyticsApi, + renderInTestApp, + TestApiRegistry, +} from '@backstage/test-utils'; +import { act, fireEvent } from '@testing-library/react'; +import React from 'react'; +import { Workflow } from './Workflow'; +import { analyticsApiRef } from '@backstage/core-plugin-api'; +import { ScaffolderApi } from '../../../api/types'; +import { scaffolderApiRef } from '../../../api/ref'; + +const scaffolderApiMock: jest.Mocked = { + scaffold: jest.fn(), + getTemplateParameterSchema: jest.fn(), + getIntegrationsList: jest.fn(), + getTask: jest.fn(), + streamLogs: jest.fn(), + listActions: jest.fn(), + listTasks: jest.fn(), +}; + +const analyticsMock = new MockAnalyticsApi(); +const apis = TestApiRegistry.from( + [scaffolderApiRef, scaffolderApiMock], + [analyticsApiRef, analyticsMock], +); + +describe('', () => { + it('should complete a workflow', async () => { + const onComplete = jest.fn(); + const onError = jest.fn(); + scaffolderApiMock.scaffold.mockResolvedValue({ taskId: 'xyz' }); + + scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({ + steps: [ + { + title: 'Step 1', + schema: { + properties: { + name: { + type: 'string', + }, + }, + }, + }, + ], + title: 'React JSON Schema Form Test', + }); + + const { getByRole, getAllByRole, getByText } = await renderInTestApp( + + ( +

This is a different wrapper for the review page

+ )} + customFieldExtensions={[]} + /> +
, + ); + + // Test template title is overriden + expect(getByRole('heading', { level: 2 }).innerHTML).toBe( + 'Different title than template', + ); + + const input = getByRole('textbox') as HTMLInputElement; + + expect(input).toBeInTheDocument(); + + expect(input.value).toBe('prefilled-name'); + + await act(async () => { + fireEvent.click(getByRole('button', { name: 'Review' })); + }); + + expect( + getByText('This is a different wrapper for the review page'), + ).toBeDefined(); + + await act(async () => { + fireEvent.click(getAllByRole('button')[1] as HTMLButtonElement); + }); + + expect(onComplete).toHaveBeenCalledWith({ name: 'prefilled-name' }); + }); +}); diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx index 783c504cc8..095ff49fe8 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx @@ -113,7 +113,7 @@ export const Workflow = ({ extensions={props.customFieldExtensions} onComplete={props.onComplete} FormProps={FormProps} - initialFormState={props.initialFormState} + initialState={props.initialFormState} ReviewStateWrapper={ReviewStateWrapper} /> diff --git a/yarn.lock b/yarn.lock index f8df88cb37..6a6a81f40b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7459,6 +7459,7 @@ __metadata: json-schema-library: ^7.3.9 lodash: ^4.17.21 qs: ^6.9.4 + react-use: ^17.2.4 zen-observable: ^0.10.0 zod: ~3.18.0 zod-to-json-schema: ~3.18.0 From 693695ee468f1eefde944e3ed8f470f3653a6fdf Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 12 Jan 2023 17:54:29 +0000 Subject: [PATCH 25/33] run api-report Signed-off-by: Paul Cowan --- .changeset/pink-falcons-serve.md | 1 + packages/app/src/App.tsx | 13 +-- plugins/scaffolder-react/api-report.md | 62 +++++++++- plugins/scaffolder/api-report.md | 108 +----------------- plugins/scaffolder/src/index.ts | 2 + plugins/scaffolder/src/next/Router/Router.tsx | 2 - 6 files changed, 69 insertions(+), 119 deletions(-) diff --git a/.changeset/pink-falcons-serve.md b/.changeset/pink-falcons-serve.md index 73ceff0ddc..8cc6859c25 100644 --- a/.changeset/pink-falcons-serve.md +++ b/.changeset/pink-falcons-serve.md @@ -1,5 +1,6 @@ --- '@backstage/plugin-scaffolder': minor +'@backstage/plugin-scaffolder-react': minor --- Embed scaffolder workflow in other components diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index ccf3d98149..f223924294 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -27,7 +27,7 @@ import { RELATION_PROVIDES_API, } from '@backstage/catalog-model'; import { createApp } from '@backstage/app-defaults'; -import { FlatRoutes } from '@backstage/core-app-api'; +import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; import { AlertDisplay, OAuthRequestDialog, @@ -149,9 +149,6 @@ const app = createApp({ }, }); -const AppProvider = app.getProvider(); -const AppRouter = app.getRouter(); - const routes = ( } /> @@ -282,14 +279,12 @@ const routes = ( ); -const App = () => ( - +export default app.createRoot( + <> {routes} - + , ); - -export default App; diff --git a/plugins/scaffolder-react/api-report.md b/plugins/scaffolder-react/api-report.md index adc097775b..97f05dfb16 100644 --- a/plugins/scaffolder-react/api-report.md +++ b/plugins/scaffolder-react/api-report.md @@ -8,6 +8,7 @@ import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/core-plugin-api'; import { Dispatch } from 'react'; +import type { ErrorTransformer } from '@rjsf/utils'; import { Extension } from '@backstage/core-plugin-api'; import { FieldProps } from '@rjsf/core'; import { FieldProps as FieldProps_2 } from '@rjsf/utils'; @@ -79,6 +80,9 @@ export type CustomFieldValidator = ( }, ) => void | Promise; +// @alpha (undocumented) +export const EmbeddableWorkflow: (props: WorkflowProps) => JSX.Element; + // @alpha export const extractSchemaFromStep: (inputStep: JsonObject) => { uiSchema: UiSchema; @@ -186,10 +190,12 @@ export interface ParsedTemplateSchema { export const ReviewState: (props: ReviewStateProps) => JSX.Element; // @alpha -export type ReviewStateProps = { - schemas: ParsedTemplateSchema[]; +export interface ReviewStateProps { + // (undocumented) formState: JsonObject; -}; + // (undocumented) + schemas: ParsedTemplateSchema[]; +} // @public export interface ScaffolderApi { @@ -342,7 +348,12 @@ export const SecretsContextProvider: ({ }: PropsWithChildren<{}>) => JSX.Element; // @alpha -export const Stepper: (props: StepperProps) => JSX.Element; +export const Stepper: ({ + ReviewStateWrapper, + createButtonText, + reviewButtonText, + ...props +}: StepperProps) => JSX.Element; // @alpha export type StepperProps = { @@ -352,6 +363,9 @@ export type StepperProps = { FormProps?: FormProps; initialState?: Record; onComplete: (values: Record) => Promise; + ReviewStateWrapper?: (props: ReviewStateProps) => JSX.Element; + createButtonText?: string; + reviewButtonText?: string; }; // @alpha @@ -418,6 +432,13 @@ export const useFormDataFromQuery: ( initialState?: Record, ) => [Record, Dispatch>>]; +// @alpha (undocumented) +export const useTemplateParameterSchema: (templateRef: string) => { + manifest: TemplateParameterSchema | undefined; + loading: boolean; + error: Error | undefined; +}; + // @alpha export const useTemplateSchema: (manifest: TemplateParameterSchema) => { steps: ParsedTemplateSchema[]; @@ -426,5 +447,38 @@ export const useTemplateSchema: (manifest: TemplateParameterSchema) => { // @public export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets; +// @alpha (undocumented) +export const Workflow: ({ + ReviewStateWrapper, + FormProps, + ...props +}: WorkflowProps) => JSX.Element | null; + +// @alpha (undocumented) +export interface WorkflowProps { + // (undocumented) + customFieldExtensions: NextFieldExtensionOptions[]; + // (undocumented) + description?: string; + // (undocumented) + FormProps?: FormProps; + // (undocumented) + initialFormState?: Record; + // (undocumented) + namespace: string; + // (undocumented) + onComplete: (values: Record) => Promise; + // (undocumented) + onError(error: Error | undefined): JSX.Element | null; + // (undocumented) + ReviewStateWrapper?: (props: ReviewStateProps) => JSX.Element; + // (undocumented) + templateName: string; + // (undocumented) + title?: string; + // (undocumented) + transformErrors?: ErrorTransformer; +} + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 2fd5fb464e..45d8a77f1e 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -22,7 +22,8 @@ import { FieldExtensionComponentProps as FieldExtensionComponentProps_2 } from ' import { FieldExtensionOptions as FieldExtensionOptions_2 } from '@backstage/plugin-scaffolder-react'; import { FieldValidation } from '@rjsf/core'; import type { FormProps as FormProps_2 } from '@rjsf/core'; -import type { FormProps as FormProps_3 } from '@rjsf/core-v5'; +import type { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; +import type { FormProps as FormProps_4 } from '@rjsf/core-v5'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; import { ListActionsResponse as ListActionsResponse_2 } from '@backstage/plugin-scaffolder-react'; @@ -31,11 +32,7 @@ import { Observable } from '@backstage/types'; import { PathParams } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; -<<<<<<< HEAD import { ReactNode } from 'react'; -======= -import type { ReactNode } from 'react'; ->>>>>>> 1a12ad8e8d (fix api-reports) import { RouteRef } from '@backstage/core-plugin-api'; import { ScaffolderApi as ScaffolderApi_2 } from '@backstage/plugin-scaffolder-react'; import { ScaffolderDryRunOptions as ScaffolderDryRunOptions_2 } from '@backstage/plugin-scaffolder-react'; @@ -73,45 +70,7 @@ export type CustomFieldValidator = CustomFieldValidator_2; // @public -<<<<<<< HEAD export const EntityNamePickerFieldExtension: FieldExtensionComponent_2< -======= -export type CustomFieldValidator = ( - data: TFieldReturnValue, - field: FieldValidation, - context: { - apiHolder: ApiHolder; - }, -) => void | Promise; - -// @alpha -export function EmbeddedScaffolderWorkflow({ - namespace, - templateName, - customExtensionsElement, - frontPage, - finishPage, - onComplete, - onError, - title, - description, - ReviewStateWrapper, - initialFormState, -}: EmbeddedScaffolderWorkflowProps): JSX.Element; - -// @alpha (undocumented) -export type EmbeddedScaffolderWorkflowProps = Omit< - WorkflowProps, - 'customFieldExtensions' | 'onComplete' -> & { - customExtensionsElement?: React_2.ReactNode; - frontPage: ReactNode; - finishPage: ReactNode; -} & Partial>; - -// @public -export const EntityNamePickerFieldExtension: FieldExtensionComponent< ->>>>>>> b2747a3f35 (api-reports) string, {} >; @@ -199,7 +158,7 @@ export interface FieldSchema { // @alpha @deprecated export type FormProps = Pick< - FormProps_3, + FormProps_4, 'transformErrors' | 'noHtml5Validate' >; @@ -249,7 +208,7 @@ export type NextRouterProps = { TaskPageComponent?: React_2.ComponentType<{}>; }; groups?: TemplateGroupFilter[]; - FormProps?: FormProps; + FormProps?: FormProps_3; }; // @alpha @@ -324,20 +283,6 @@ export const OwnerPickerFieldSchema: FieldSchema< // @public export type OwnerPickerUiOptions = typeof OwnerPickerFieldSchema.uiOptionsType; -// @alpha (undocumented) -export interface ParsedTemplateSchema { - // (undocumented) - description?: string; - // (undocumented) - mergedSchema: JsonObject; - // (undocumented) - schema: JsonObject; - // (undocumented) - title: string; - // (undocumented) - uiSchema: UiSchema; -} - // @public export const repoPickerValidation: ( value: string, @@ -403,14 +348,6 @@ export const RepoUrlPickerFieldSchema: FieldSchema< export type RepoUrlPickerUiOptions = typeof RepoUrlPickerFieldSchema.uiOptionsType; -// @alpha (undocumented) -export interface ReviewStateProps { - // (undocumented) - formState: JsonObject; - // (undocumented) - schemas: ParsedTemplateSchema[]; -} - // @public export type ReviewStepProps = { disableButtons: boolean; @@ -590,43 +527,6 @@ export type TemplateParameterSchema = TemplateParameterSchema_2; // @public export const TemplateTypePicker: () => JSX.Element | null; -<<<<<<< HEAD -<<<<<<< HEAD // @public @deprecated (undocumented) export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets_2; -======= -// @alpha -export const useGetCustomFields: ( - element: React_2.ReactNode, -) => NextFieldExtensionOptions[]; - -======= ->>>>>>> 1a12ad8e8d (fix api-reports) -// @public -export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets; - -// @alpha (undocumented) -export interface WorkflowProps { - // (undocumented) - customFieldExtensions: NextFieldExtensionOptions[]; - // (undocumented) - description?: string; - // (undocumented) - initialFormState?: Record; - // (undocumented) - namespace: string; - // (undocumented) - onComplete: (values: Record) => Promise; - // (undocumented) - onError(error: Error | undefined): JSX.Element | null; - // (undocumented) - ReviewStateWrapper?: (props: ReviewStateProps) => JSX.Element; - // (undocumented) - templateName: string; - // (undocumented) - title?: string; - // (undocumented) - transformErrors?: ErrorTransformer; -} ->>>>>>> b2747a3f35 (api-reports) ``` diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 60b2dfeeb2..0e2b293982 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -49,5 +49,7 @@ export { nextRouteRef, nextScaffolderTaskRouteRef, nextSelectedTemplateRouteRef, + type TemplateGroupFilter, + type NextRouterProps, type FormProps, } from './next'; diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 77e0423fd6..0db23d1cd4 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -52,9 +52,7 @@ export type NextRouterProps = { */ export const Router = (props: PropsWithChildren) => { const { components: { TemplateCardComponent } = {} } = props; - const outlet = useOutlet() || props.children; - const customFieldExtensions = useCustomFieldExtensions(outlet); const fieldExtensions = [ From e78ba09f8b24c04991bee4f49ca5f8d677130f44 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 12 Jan 2023 18:39:00 +0000 Subject: [PATCH 26/33] FormProps export Signed-off-by: Paul Cowan --- plugins/scaffolder-react/src/next/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-react/src/next/index.ts b/plugins/scaffolder-react/src/next/index.ts index a18649f506..ff3d502677 100644 --- a/plugins/scaffolder-react/src/next/index.ts +++ b/plugins/scaffolder-react/src/next/index.ts @@ -15,6 +15,6 @@ */ export * from './components'; export * from './extensions'; -export * from './types'; +export { type FormProps } from './types'; export * from './lib'; export * from './hooks'; From aeff4e6b5a428c08a3b8abd62e3e8722cdc16179 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 12 Jan 2023 18:41:01 +0000 Subject: [PATCH 27/33] FormProps export Signed-off-by: Paul Cowan --- plugins/scaffolder-react/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder-react/src/index.ts b/plugins/scaffolder-react/src/index.ts index 0b053e6bf4..41a1298b37 100644 --- a/plugins/scaffolder-react/src/index.ts +++ b/plugins/scaffolder-react/src/index.ts @@ -21,3 +21,4 @@ export * from './api'; export * from './hooks'; export * from './next'; +export type { FormProps } from './next'; From 2f0c9d9ca0743690f27bea04a47cd55c142de911 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 12 Jan 2023 19:10:10 +0000 Subject: [PATCH 28/33] make FormProps public Signed-off-by: Paul Cowan --- plugins/scaffolder-react/src/next/index.ts | 2 +- plugins/scaffolder-react/src/next/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-react/src/next/index.ts b/plugins/scaffolder-react/src/next/index.ts index ff3d502677..ea009ea556 100644 --- a/plugins/scaffolder-react/src/next/index.ts +++ b/plugins/scaffolder-react/src/next/index.ts @@ -15,6 +15,6 @@ */ export * from './components'; export * from './extensions'; -export { type FormProps } from './types'; +export type { FormProps } from './types'; export * from './lib'; export * from './hooks'; diff --git a/plugins/scaffolder-react/src/next/types.ts b/plugins/scaffolder-react/src/next/types.ts index 32d0fe85f0..a6f699791b 100644 --- a/plugins/scaffolder-react/src/next/types.ts +++ b/plugins/scaffolder-react/src/next/types.ts @@ -18,7 +18,7 @@ import type { FormProps as SchemaFormProps } from '@rjsf/core-v5'; /** * Any `@rjsf/core` form properties that are publicly exposed to the `NextScaffolderpage` * - * @alpha + * @public */ export type FormProps = Pick< SchemaFormProps, From 9081955021b58c67016c0acb66fb35e55e9ed329 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 12 Jan 2023 19:14:40 +0000 Subject: [PATCH 29/33] revert FormProps imports Signed-off-by: Paul Cowan --- plugins/scaffolder-react/src/next/types.ts | 2 +- plugins/scaffolder/src/next/Router/Router.tsx | 2 +- .../src/next/TemplateWizardPage/TemplateWizardPage.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-react/src/next/types.ts b/plugins/scaffolder-react/src/next/types.ts index a6f699791b..32d0fe85f0 100644 --- a/plugins/scaffolder-react/src/next/types.ts +++ b/plugins/scaffolder-react/src/next/types.ts @@ -18,7 +18,7 @@ import type { FormProps as SchemaFormProps } from '@rjsf/core-v5'; /** * Any `@rjsf/core` form properties that are publicly exposed to the `NextScaffolderpage` * - * @public + * @alpha */ export type FormProps = Pick< SchemaFormProps, diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 0db23d1cd4..f91e56bba7 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -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 type { FormProps } from '@backstage/plugin-scaffolder-react'; +import { type FormProps } from '../types'; import { nextSelectedTemplateRouteRef } from '../routes'; /** diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index 8fb6f0a31e..01a1468c90 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -28,7 +28,7 @@ import { NextFieldExtensionOptions, } from '@backstage/plugin-scaffolder-react'; import { JsonValue } from '@backstage/types'; -import { FormProps } from '@backstage/plugin-scaffolder-react'; +import { type FormProps } from '../types'; import { nextRouteRef } from '../routes'; import { scaffolderTaskRouteRef, selectedTemplateRouteRef } from '../../routes'; import { Header, Page } from '@backstage/core-components'; From 72e9ee90ae1614075cbab6de65da95ea693ba2ef Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 12 Jan 2023 19:50:41 +0000 Subject: [PATCH 30/33] run api-report Signed-off-by: Paul Cowan --- plugins/scaffolder/api-report.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 45d8a77f1e..82a8dfd8d3 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -22,8 +22,7 @@ import { FieldExtensionComponentProps as FieldExtensionComponentProps_2 } from ' import { FieldExtensionOptions as FieldExtensionOptions_2 } from '@backstage/plugin-scaffolder-react'; import { FieldValidation } from '@rjsf/core'; import type { FormProps as FormProps_2 } from '@rjsf/core'; -import type { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; -import type { FormProps as FormProps_4 } from '@rjsf/core-v5'; +import type { FormProps as FormProps_3 } from '@rjsf/core-v5'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; import { ListActionsResponse as ListActionsResponse_2 } from '@backstage/plugin-scaffolder-react'; @@ -158,7 +157,7 @@ export interface FieldSchema { // @alpha @deprecated export type FormProps = Pick< - FormProps_4, + FormProps_3, 'transformErrors' | 'noHtml5Validate' >; @@ -208,7 +207,7 @@ export type NextRouterProps = { TaskPageComponent?: React_2.ComponentType<{}>; }; groups?: TemplateGroupFilter[]; - FormProps?: FormProps_3; + FormProps?: FormProps; }; // @alpha From 7e6fdc0146e291b7c78a44217edb8ce94d8ac9ff Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Sun, 15 Jan 2023 08:14:51 +0000 Subject: [PATCH 31/33] rename ReviewStateWrapper to ReviewStateComponent Signed-off-by: Paul Cowan --- plugins/scaffolder-react/api-report.md | 25 ++++++------------- .../components/ReviewState/ReviewState.tsx | 4 +-- .../src/next/components/Stepper/Stepper.tsx | 18 +++++++------ .../components/Workflow/Workflow.test.tsx | 2 +- .../src/next/components/Workflow/Workflow.tsx | 16 ++++++------ 5 files changed, 29 insertions(+), 36 deletions(-) diff --git a/plugins/scaffolder-react/api-report.md b/plugins/scaffolder-react/api-report.md index 97f05dfb16..3c03bf2d15 100644 --- a/plugins/scaffolder-react/api-report.md +++ b/plugins/scaffolder-react/api-report.md @@ -190,12 +190,10 @@ export interface ParsedTemplateSchema { export const ReviewState: (props: ReviewStateProps) => JSX.Element; // @alpha -export interface ReviewStateProps { - // (undocumented) - formState: JsonObject; - // (undocumented) +export type ReviewStateProps = { schemas: ParsedTemplateSchema[]; -} + formState: JsonObject; +}; // @public export interface ScaffolderApi { @@ -348,12 +346,7 @@ export const SecretsContextProvider: ({ }: PropsWithChildren<{}>) => JSX.Element; // @alpha -export const Stepper: ({ - ReviewStateWrapper, - createButtonText, - reviewButtonText, - ...props -}: StepperProps) => JSX.Element; +export const Stepper: (stepperProps: StepperProps) => JSX.Element; // @alpha export type StepperProps = { @@ -363,7 +356,7 @@ export type StepperProps = { FormProps?: FormProps; initialState?: Record; onComplete: (values: Record) => Promise; - ReviewStateWrapper?: (props: ReviewStateProps) => JSX.Element; + ReviewStateComponent?: (props: ReviewStateProps) => JSX.Element; createButtonText?: string; reviewButtonText?: string; }; @@ -448,11 +441,7 @@ export const useTemplateSchema: (manifest: TemplateParameterSchema) => { export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets; // @alpha (undocumented) -export const Workflow: ({ - ReviewStateWrapper, - FormProps, - ...props -}: WorkflowProps) => JSX.Element | null; +export const Workflow: (workflowProps: WorkflowProps) => JSX.Element | null; // @alpha (undocumented) export interface WorkflowProps { @@ -471,7 +460,7 @@ export interface WorkflowProps { // (undocumented) onError(error: Error | undefined): JSX.Element | null; // (undocumented) - ReviewStateWrapper?: (props: ReviewStateProps) => JSX.Element; + ReviewStateComponent?: (props: ReviewStateProps) => JSX.Element; // (undocumented) templateName: string; // (undocumented) diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx index 56abc52469..52ad80c4c2 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx @@ -23,10 +23,10 @@ import { ParsedTemplateSchema } from '../../hooks/useTemplateSchema'; * The props for the {@link ReviewState} component. * @alpha */ -export interface ReviewStateProps { +export type ReviewStateProps = { schemas: ParsedTemplateSchema[]; formState: JsonObject; -} +}; /** * The component used by the {@link Stepper} to render the review step. diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 5d0ad29513..37e86d1afa 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -61,7 +61,7 @@ export type StepperProps = { initialState?: Record; onComplete: (values: Record) => Promise; - ReviewStateWrapper?: (props: ReviewStateProps) => JSX.Element; + ReviewStateComponent?: (props: ReviewStateProps) => JSX.Element; createButtonText?: string; reviewButtonText?: string; }; @@ -76,12 +76,14 @@ const Form = withTheme(require('@rjsf/material-ui-v5').Theme); * @alpha */ -export const Stepper = ({ - ReviewStateWrapper = ReviewState, - createButtonText = 'Create', - reviewButtonText = 'Review', - ...props -}: StepperProps) => { +export const Stepper = (stepperProps: StepperProps) => { + const { + ReviewStateComponent = ReviewState, + createButtonText = 'Create', + reviewButtonText = 'Review', + ...props + } = stepperProps; + const analytics = useAnalytics(); const { steps } = useTemplateSchema(props.manifest); const apiHolder = useApiHolder(); @@ -191,7 +193,7 @@ export const Stepper = ({ ) : ( <> - +