diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx new file mode 100644 index 0000000000..ff5f27ff60 --- /dev/null +++ b/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx @@ -0,0 +1,128 @@ +/* + * Copyright 2020 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 { Box, Button, Paper, Typography } from '@material-ui/core'; +import React from 'react'; +import { Content, StructuredMetadataTable } from '@backstage/core-components'; +import { Step } from './types'; +import { UiSchema } from '@rjsf/core'; +import { JsonObject } from '@backstage/types'; + +export type LastStepFormProps = { + disableButtons: boolean; + finishButtonLabel?: string; + formData: Record; + handleBack: () => void; + handleCreate: () => void; + handleReset: () => void; + onFinish?: () => Promise; + steps: Step[]; +}; + +export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { + const uiSchemas: Array = []; + steps.forEach(step => { + const schemaProps = step.schema.properties as JsonObject; + for (const key in schemaProps) { + if (schemaProps.hasOwnProperty(key)) { + const uiSchema = schemaProps[key] as UiSchema; + uiSchema.name = key; + uiSchemas.push(uiSchema); + } + } + }); + return uiSchemas; +} + +export function getReviewData(formData: Record, steps: Step[]) { + const uiSchemas = getUiSchemasFromSteps(steps); + const reviewData: Record = {}; + for (const key in formData) { + if (formData.hasOwnProperty(key)) { + const uiSchema = uiSchemas.find(us => us.name === key); + + if (!uiSchema) { + reviewData[key] = formData[key]; + continue; + } + + if (uiSchema['ui:widget'] === 'password') { + reviewData[key] = '******'; + continue; + } + + if (!uiSchema['ui:backstage'] || !uiSchema['ui:backstage'].review) { + reviewData[key] = formData[key]; + continue; + } + + const review = uiSchema['ui:backstage'].review as JsonObject; + if (review.mask) { + reviewData[key] = review.mask; + continue; + } + + if (!review.show) { + continue; + } + reviewData[key] = formData[key]; + } + } + + return reviewData; +} + +export const LastStepForm = (props: LastStepFormProps) => { + const { + disableButtons, + finishButtonLabel, + formData, + handleBack, + handleCreate, + handleReset, + onFinish, + steps, + } = props; + return ( + + + Review and create + + + + + + + + ); +}; + +export const lastStepFormComponent = (props: LastStepFormProps) => ( + +); diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx index 21540a4895..60cca38f06 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getReviewData } from './MultistepJsonForm'; +import { getReviewData } from './LastStepForm'; describe('MultistepJsonForm', () => { const formDataMock = { diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 8c71df67b6..acb4ce8429 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -13,11 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JsonObject } from '@backstage/types'; import { - Box, Button, - Paper, Step as StepUI, StepContent, StepLabel, @@ -26,23 +23,20 @@ import { } from '@material-ui/core'; import { errorApiRef, - useApi, featureFlagsApiRef, + useApi, } from '@backstage/core-plugin-api'; -import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core'; +import { FormProps, IChangeEvent, withTheme } from '@rjsf/core'; import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useState } from 'react'; import { transformSchemaToProps } from './schema'; -import { Content, StructuredMetadataTable } from '@backstage/core-components'; import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; +import { Step } from './types'; +import { useScaffolderPluginOptions } from '../../options'; const Form = withTheme(MuiTheme); -type Step = { - schema: JsonObject; - title: string; -} & Partial, 'schema'>>; type Props = { /** @@ -59,59 +53,6 @@ type Props = { layouts: LayoutOptions[]; }; -export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { - const uiSchemas: Array = []; - steps.forEach(step => { - const schemaProps = step.schema.properties as JsonObject; - for (const key in schemaProps) { - if (schemaProps.hasOwnProperty(key)) { - const uiSchema = schemaProps[key] as UiSchema; - uiSchema.name = key; - uiSchemas.push(uiSchema); - } - } - }); - return uiSchemas; -} - -export function getReviewData(formData: Record, steps: Step[]) { - const uiSchemas = getUiSchemasFromSteps(steps); - const reviewData: Record = {}; - for (const key in formData) { - if (formData.hasOwnProperty(key)) { - const uiSchema = uiSchemas.find(us => us.name === key); - - if (!uiSchema) { - reviewData[key] = formData[key]; - continue; - } - - if (uiSchema['ui:widget'] === 'password') { - reviewData[key] = '******'; - continue; - } - - if (!uiSchema['ui:backstage'] || !uiSchema['ui:backstage'].review) { - reviewData[key] = formData[key]; - continue; - } - - const review = uiSchema['ui:backstage'].review as JsonObject; - if (review.mask) { - reviewData[key] = review.mask; - continue; - } - - if (!review.show) { - continue; - } - reviewData[key] = formData[key]; - } - } - - return reviewData; -} - export const MultistepJsonForm = (props: Props) => { const { formData, @@ -134,7 +75,7 @@ export const MultistepJsonForm = (props: Props) => { if (filteredStep.schema.properties) { filteredStep.schema.properties = Object.fromEntries( Object.entries(filteredStep.schema.properties).filter( - ([key, value]) => { + ([key, value]: [string, any]) => { if (value[featureFlagKey]) { if (featureFlagApi.isActive(value[featureFlagKey])) { return true; @@ -189,6 +130,8 @@ export const MultistepJsonForm = (props: Props) => { } }; + const { lastStepFormComponent } = useScaffolderPluginOptions(); + return ( <> @@ -231,32 +174,16 @@ export const MultistepJsonForm = (props: Props) => { ); })} - {activeStep === steps.length && ( - - - Review and create - - - - - - - - )} + {activeStep === steps.length && + lastStepFormComponent({ + disableButtons, + handleBack, + handleCreate, + handleReset, + finishButtonLabel, + formData, + steps, + })} ); }; diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts b/plugins/scaffolder/src/components/MultistepJsonForm/index.ts index b125e7c4c2..d78d7376ba 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts +++ b/plugins/scaffolder/src/components/MultistepJsonForm/index.ts @@ -14,3 +14,5 @@ * limitations under the License. */ export { MultistepJsonForm } from './MultistepJsonForm'; +export { lastStepFormComponent, LastStepForm } from './LastStepForm'; +export type { LastStepFormProps } from './LastStepForm'; diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/types.ts b/plugins/scaffolder/src/components/MultistepJsonForm/types.ts new file mode 100644 index 0000000000..864698e1a9 --- /dev/null +++ b/plugins/scaffolder/src/components/MultistepJsonForm/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 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 { FormProps } from '@rjsf/core'; +import { JsonObject } from '@backstage/types'; + +export type Step = { + schema: JsonObject; + title: string; +} & Partial, 'schema'>>; diff --git a/plugins/scaffolder/src/options.ts b/plugins/scaffolder/src/options.ts new file mode 100644 index 0000000000..430274470c --- /dev/null +++ b/plugins/scaffolder/src/options.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2020 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 { usePluginOptions } from '@backstage/core-plugin-api'; +import { ReactElement } from 'react'; +import { LastStepFormProps } from './components/MultistepJsonForm'; + +export type ScaffolderPluginOptions = { + lastStepFormComponent: (props: LastStepFormProps) => ReactElement; +}; + +/** @ignore */ +export type ScaffolderInputPluginOptionsOptions = { + lastStepFormComponent: (props: LastStepFormProps) => ReactElement; +}; + +export const useScaffolderPluginOptions = () => + usePluginOptions(); diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.tsx similarity index 89% rename from plugins/scaffolder/src/plugin.ts rename to plugins/scaffolder/src/plugin.tsx index f9b359d93a..c439faecd5 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.tsx @@ -20,7 +20,7 @@ import { EntityPicker } from './components/fields/EntityPicker/EntityPicker'; import { entityNamePickerValidation } from './components/fields/EntityNamePicker'; import { EntityNamePicker } from './components/fields/EntityNamePicker/EntityNamePicker'; import { OwnerPicker } from './components/fields/OwnerPicker/OwnerPicker'; -import { repoPickerValidation } from './components/fields/RepoUrlPicker'; +import { repoPickerValidation } from './components'; import { RepoUrlPicker } from './components/fields/RepoUrlPicker/RepoUrlPicker'; import { createScaffolderFieldExtension } from './extensions'; import { @@ -39,6 +39,11 @@ import { } from '@backstage/core-plugin-api'; import { OwnedEntityPicker } from './components/fields/OwnedEntityPicker/OwnedEntityPicker'; import { EntityTagsPicker } from './components/fields/EntityTagsPicker/EntityTagsPicker'; +import { + ScaffolderInputPluginOptionsOptions, + ScaffolderPluginOptions, +} from './options'; +import { lastStepFormComponent } from './components/MultistepJsonForm'; /** * The main plugin export for the scaffolder. @@ -71,6 +76,14 @@ export const scaffolderPlugin = createPlugin({ registerComponent: registerComponentRouteRef, viewTechDoc: viewTechDocRouteRef, }, + __experimentalConfigure( + options?: ScaffolderInputPluginOptionsOptions, + ): ScaffolderPluginOptions { + const defaultOptions = { + lastStepFormComponent, + }; + return { ...defaultOptions, ...options }; + }, }); /** @@ -100,7 +113,7 @@ export const EntityNamePickerFieldExtension = scaffolderPlugin.provide( /** * The field extension which provides the ability to select a RepositoryUrl. - * Currently this is an encoded URL that looks something like the following `github.com?repo=myRepoName&owner=backstage`. + * Currently, this is an encoded URL that looks something like the following `github.com?repo=myRepoName&owner=backstage`. * * @public */