diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index d668bbe3f1..d08edb75fb 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -57,6 +57,7 @@ "json-schema": "^0.4.0", "json-schema-library": "^7.3.9", "lodash": "^4.17.21", + "react-use": "^17.2.4", "qs": "^6.9.4", "zen-observable": "^0.10.0", "zod": "~3.18.0", diff --git a/plugins/scaffolder/src/next/Workflow/Workflow.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx similarity index 88% rename from plugins/scaffolder/src/next/Workflow/Workflow.tsx rename to plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx index 7acc29c10f..783c504cc8 100644 --- a/plugins/scaffolder/src/next/Workflow/Workflow.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx @@ -21,20 +21,17 @@ import { Progress, } from '@backstage/core-components'; import { stringifyEntityRef } from '@backstage/catalog-model'; -import { useTemplateParameterSchema } from '../TemplateWizardPage/TemplateWizardPage'; 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 { - ReviewState, - type ReviewStateProps, - SecretsContextProvider, - Stepper, - type NextFieldExtensionOptions, -} from '@backstage/plugin-scaffolder-react'; -import { type FormProps } from '@backstage/plugin-scaffolder-react'; +import { type NextFieldExtensionOptions } from '../../extensions/types'; +import { type FormProps } from '../../types'; +import { ReviewState, ReviewStateProps } from '../ReviewState/ReviewState'; +import { useTemplateParameterSchema } from '../../hooks/useTemplateParameterSchema'; +import { Stepper } from '../Stepper/Stepper'; +import { SecretsContextProvider } from '../../../secrets/SecretsContext'; const useStyles = makeStyles(() => ({ markdown: { @@ -65,6 +62,9 @@ export interface WorkflowProps { ReviewStateWrapper?: (props: ReviewStateProps) => JSX.Element; } +/** + * @alpha + */ export const Workflow = ({ ReviewStateWrapper = ReviewState, FormProps = {}, @@ -122,6 +122,9 @@ export const Workflow = ({ ); }; +/** + * @alpha + */ export const EmbeddableWorkflow = (props: WorkflowProps) => ( diff --git a/plugins/scaffolder/src/next/Workflow/index.ts b/plugins/scaffolder-react/src/next/components/Workflow/index.ts similarity index 88% rename from plugins/scaffolder/src/next/Workflow/index.ts rename to plugins/scaffolder-react/src/next/components/Workflow/index.ts index 55ac44a29c..193a6417fc 100644 --- a/plugins/scaffolder/src/next/Workflow/index.ts +++ b/plugins/scaffolder-react/src/next/components/Workflow/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { EmbeddableWorkflow, type WorkflowProps } from './Workflow'; +export { Workflow, EmbeddableWorkflow, type WorkflowProps } from './Workflow'; diff --git a/plugins/scaffolder-react/src/next/components/index.ts b/plugins/scaffolder-react/src/next/components/index.ts index c710bd8891..babe3b4dcb 100644 --- a/plugins/scaffolder-react/src/next/components/index.ts +++ b/plugins/scaffolder-react/src/next/components/index.ts @@ -17,3 +17,4 @@ export * from './Stepper'; export * from './TemplateCard'; export * from './ReviewState'; export * from './TemplateGroup'; +export * from './Workflow'; diff --git a/plugins/scaffolder-react/src/next/hooks/index.ts b/plugins/scaffolder-react/src/next/hooks/index.ts index 78a4d2a777..ca8ce10eff 100644 --- a/plugins/scaffolder-react/src/next/hooks/index.ts +++ b/plugins/scaffolder-react/src/next/hooks/index.ts @@ -18,3 +18,4 @@ export { useTemplateSchema, type ParsedTemplateSchema, } from './useTemplateSchema'; +export { useTemplateParameterSchema } from './useTemplateParameterSchema'; diff --git a/plugins/scaffolder/src/next/EmbeddedScaffolderWorkflow/index.ts b/plugins/scaffolder-react/src/next/hooks/useTemplateParameterSchema.ts similarity index 51% rename from plugins/scaffolder/src/next/EmbeddedScaffolderWorkflow/index.ts rename to plugins/scaffolder-react/src/next/hooks/useTemplateParameterSchema.ts index 3b646398d1..36b8b1e0bc 100644 --- a/plugins/scaffolder/src/next/EmbeddedScaffolderWorkflow/index.ts +++ b/plugins/scaffolder-react/src/next/hooks/useTemplateParameterSchema.ts @@ -1,5 +1,7 @@ +import { useApi } from '@backstage/core-plugin-api'; + /* - * Copyright 2022 The Backstage Authors + * Copyright 2023 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. @@ -13,7 +15,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { - EmbeddedScaffolderWorkflow, - type EmbeddedScaffolderWorkflowProps, -} from './EmbeddedScaffolderWorkflow'; +import useAsync from 'react-use/lib/useAsync'; +import { scaffolderApiRef } from '../../api/ref'; + +/** + * @alpha + */ +export const useTemplateParameterSchema = (templateRef: string) => { + const scaffolderApi = useApi(scaffolderApiRef); + const { value, loading, error } = useAsync( + () => scaffolderApi.getTemplateParameterSchema(templateRef), + [scaffolderApi, templateRef], + ); + + return { manifest: value, loading, error }; +}; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 2b1eccc711..60b2dfeeb2 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -45,11 +45,6 @@ export * from './deprecated'; /** next exports */ export { NextScaffolderPage } from './plugin'; -export { - EmbeddedScaffolderWorkflow, - type EmbeddedScaffolderWorkflowProps, - type WorkflowProps, -} from './next'; export { nextRouteRef, nextScaffolderTaskRouteRef, diff --git a/plugins/scaffolder/src/next/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.test.tsx b/plugins/scaffolder/src/next/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.test.tsx deleted file mode 100644 index 62b3c96718..0000000000 --- a/plugins/scaffolder/src/next/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.test.tsx +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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 { EmbeddedScaffolderWorkflow } from './EmbeddedScaffolderWorkflow'; -import { - scaffolderApiRef, - type ScaffolderApi, -} from '@backstage/plugin-scaffolder-react'; -import { analyticsApiRef } from '@backstage/core-plugin-api'; - -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 embed workflow inside another component', 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( - - -

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. -

- - } - finishPage={ - <> -

Congratulations, this application is complete!

- - } - ReviewStateWrapper={() => ( -

This is a different wrapper for the review page

- )} - /> -
, - ); - - // frontPage is rendered - expect(getByRole('heading').innerHTML).toBe('Security Insights'); - - // move to workflow - await act(async () => { - fireEvent.click(getByRole('button')); - }); - - // Test template title is overriden - expect(getByRole('heading').innerHTML).toBe( - 'Different title than template', - ); - - expect(getByRole('textbox').innerHTML).toBeDefined(); - - const input = getByRole('textbox') as HTMLInputElement; - - // The initial state of the form can be set - expect(input.value).toBe('prefilled-name'); - - await act(async () => { - fireEvent.click(getAllByRole('button')[1] as HTMLButtonElement); - }); - - // Can supply a different Review wrapper - expect( - getByText('This is a different wrapper for the review page'), - ).toBeDefined(); - - await act(async () => { - fireEvent.click(getAllByRole('button')[1] as HTMLButtonElement); - }); - - // the final page is inserted after the workflow - expect( - getByText('Congratulations, this application is complete!'), - ).toBeDefined(); - }); -}); diff --git a/plugins/scaffolder/src/next/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx b/plugins/scaffolder/src/next/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx deleted file mode 100644 index 32b616274c..0000000000 --- a/plugins/scaffolder/src/next/EmbeddedScaffolderWorkflow/EmbeddedScaffolderWorkflow.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 type { JsonValue } from '@backstage/types'; -import { EmbeddableWorkflow, type WorkflowProps } from '../Workflow/Workflow'; -import { FormProps } from '@backstage/plugin-scaffolder-react'; -import { Box, Button } from '@material-ui/core'; - -/** - * @alpha - */ -export type EmbeddedScaffolderWorkflowProps = Omit< - WorkflowProps, - 'onComplete' | 'FormProps' -> & { - customExtensionsElement?: React.ReactNode; - initialFormState?: Record; - onComplete: (values: Record) => Promise; - onError(error: Error | undefined): JSX.Element | null; - FormProps?: FormProps; - frontPage: ReactNode; - finishPage: ReactNode; -} & Partial>; - -type Display = 'front' | 'workflow' | 'finish'; - -type DisplayComponents = Record; - -type OnCompleteArgs = Parameters[0]; - -/** - * Allows the EmbeddableWorkflow to be called from outside of a normal scaffolder workflow - * @alpha - */ -export function EmbeddedScaffolderWorkflow({ - namespace, - templateName, - frontPage, - finishPage, - onComplete = async (_values: OnCompleteArgs) => void 0, - onError, - title, - description, - ReviewStateWrapper, - initialFormState, - customFieldExtensions, - FormProps: formProps = {}, -}: EmbeddedScaffolderWorkflowProps): JSX.Element { - const [display, setDisplay] = useState('front'); - - const startTemplate = useCallback(() => setDisplay('workflow'), []); - - const onWorkFlowComplete = useCallback( - async (values: OnCompleteArgs) => { - setDisplay('finish'); - - await onComplete(values); - }, - [onComplete], - ); - - const DisplayElements: DisplayComponents = { - front: ( - - {frontPage} - - - ), - 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'; -