From 1a6b2d4aec9d11b5e1d7d6d859b4fbed24623ee5 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Thu, 13 Oct 2022 16:44:22 +0200 Subject: [PATCH 01/89] Make last step (a summary page of scaffolder template) configurable. Signed-off-by: bnechyporenko --- .../MultistepJsonForm/LastStepForm.tsx | 128 ++++++++++++++++++ .../MultistepJsonForm.test.tsx | 2 +- .../MultistepJsonForm/MultistepJsonForm.tsx | 107 +++------------ .../src/components/MultistepJsonForm/index.ts | 2 + .../src/components/MultistepJsonForm/types.ts | 22 +++ plugins/scaffolder/src/options.ts | 31 +++++ .../scaffolder/src/{plugin.ts => plugin.tsx} | 17 ++- 7 files changed, 216 insertions(+), 93 deletions(-) create mode 100644 plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx create mode 100644 plugins/scaffolder/src/components/MultistepJsonForm/types.ts create mode 100644 plugins/scaffolder/src/options.ts rename plugins/scaffolder/src/{plugin.ts => plugin.tsx} (89%) 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 */ From 6965ead7800974c3136b2f38995ae50ac99bda46 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Fri, 14 Oct 2022 09:09:24 +0200 Subject: [PATCH 02/89] Make last step (a summary page of scaffolder template) configurable. Signed-off-by: bnechyporenko --- .../MultistepJsonForm/LastStepForm.tsx | 17 +++++++++++++++++ .../MultistepJsonForm/MultistepJsonForm.tsx | 5 +++++ .../src/components/MultistepJsonForm/index.ts | 1 + .../src/components/MultistepJsonForm/types.ts | 5 +++++ plugins/scaffolder/src/components/index.ts | 1 + plugins/scaffolder/src/index.ts | 4 ++++ plugins/scaffolder/src/options.ts | 13 +++++++++++-- plugins/scaffolder/src/plugin.tsx | 2 +- 8 files changed, 45 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx index ff5f27ff60..3ee6609c73 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx @@ -20,6 +20,12 @@ import { Step } from './types'; import { UiSchema } from '@rjsf/core'; import { JsonObject } from '@backstage/types'; +/** + * The props for the Last Step in scaffolder template form. + * Which represents the summary of the input provided by the end user. + * + * @public + */ export type LastStepFormProps = { disableButtons: boolean; finishButtonLabel?: string; @@ -84,6 +90,12 @@ export function getReviewData(formData: Record, steps: Step[]) { return reviewData; } +/** + * The component displaying the Last Step in scaffolder template form. + * Which represents the summary of the input provided by the end user. + * + * @public + */ export const LastStepForm = (props: LastStepFormProps) => { const { disableButtons, @@ -123,6 +135,11 @@ export const LastStepForm = (props: LastStepFormProps) => { ); }; +/** + * Creates LastStepForm from provided properties. + * + * @public + */ export const lastStepFormComponent = (props: LastStepFormProps) => ( ); diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index acb4ce8429..4f067141c9 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -53,6 +53,11 @@ type Props = { layouts: LayoutOptions[]; }; +/** + * Creates the dynamic form for a scaffolder template. + * + * @public + */ export const MultistepJsonForm = (props: Props) => { const { formData, diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts b/plugins/scaffolder/src/components/MultistepJsonForm/index.ts index d78d7376ba..59c58fcb05 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts +++ b/plugins/scaffolder/src/components/MultistepJsonForm/index.ts @@ -16,3 +16,4 @@ export { MultistepJsonForm } from './MultistepJsonForm'; export { lastStepFormComponent, LastStepForm } from './LastStepForm'; export type { LastStepFormProps } from './LastStepForm'; +export type { Step } from './types'; diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/types.ts b/plugins/scaffolder/src/components/MultistepJsonForm/types.ts index 864698e1a9..8be77bbfe5 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/types.ts +++ b/plugins/scaffolder/src/components/MultistepJsonForm/types.ts @@ -16,6 +16,11 @@ import { FormProps } from '@rjsf/core'; import { JsonObject } from '@backstage/types'; +/** + * The props for the Step in scaffolder template. + * + * @public + */ export type Step = { schema: JsonObject; title: string; diff --git a/plugins/scaffolder/src/components/index.ts b/plugins/scaffolder/src/components/index.ts index d0442cb906..20bdcf2ad3 100644 --- a/plugins/scaffolder/src/components/index.ts +++ b/plugins/scaffolder/src/components/index.ts @@ -19,3 +19,4 @@ export { TemplateTypePicker } from './TemplateTypePicker'; export * from './secrets'; export { TaskPage } from './TaskPage'; export type { RouterProps } from './Router'; +export * from './MultistepJsonForm'; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index e68eece8e5..12aa8e28ec 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -67,6 +67,10 @@ export type { TaskPageProps } from './components/TaskPage'; export { NextScaffolderPage } from './plugin'; export type { NextRouterProps } from './next'; export type { TemplateGroupFilter } from './next'; +export type { + ScaffolderInputPluginOptionsOptions, + ScaffolderPluginOptions, +} from './options'; export { createNextScaffolderFieldExtension, type NextCustomFieldValidator, diff --git a/plugins/scaffolder/src/options.ts b/plugins/scaffolder/src/options.ts index 430274470c..90cdd9ba2a 100644 --- a/plugins/scaffolder/src/options.ts +++ b/plugins/scaffolder/src/options.ts @@ -16,13 +16,22 @@ import { usePluginOptions } from '@backstage/core-plugin-api'; import { ReactElement } from 'react'; -import { LastStepFormProps } from './components/MultistepJsonForm'; +import { LastStepFormProps } from './components'; +/** + * Scaffolder Plugin options to override default plugin behavior. + * + * @public + */ export type ScaffolderPluginOptions = { lastStepFormComponent: (props: LastStepFormProps) => ReactElement; }; -/** @ignore */ +/** + * Scaffolder Plugin options to override default plugin behavior. + * + * @public + */ export type ScaffolderInputPluginOptionsOptions = { lastStepFormComponent: (props: LastStepFormProps) => ReactElement; }; diff --git a/plugins/scaffolder/src/plugin.tsx b/plugins/scaffolder/src/plugin.tsx index c439faecd5..324bd1bf28 100644 --- a/plugins/scaffolder/src/plugin.tsx +++ b/plugins/scaffolder/src/plugin.tsx @@ -43,7 +43,7 @@ import { ScaffolderInputPluginOptionsOptions, ScaffolderPluginOptions, } from './options'; -import { lastStepFormComponent } from './components/MultistepJsonForm'; +import { lastStepFormComponent } from './components'; /** * The main plugin export for the scaffolder. From d4d07cf55eabd60a8d22c6ecdaf7b0f9dfdccb0a Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Fri, 14 Oct 2022 11:33:45 +0200 Subject: [PATCH 03/89] Make last step (a summary page of scaffolder template) configurable. Signed-off-by: bnechyporenko --- .changeset/angry-trees-relax.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .changeset/angry-trees-relax.md diff --git a/.changeset/angry-trees-relax.md b/.changeset/angry-trees-relax.md new file mode 100644 index 0000000000..17fad10e0f --- /dev/null +++ b/.changeset/angry-trees-relax.md @@ -0,0 +1,15 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Enabling the customization of the last page in the scaffolder template. + +To override the content you have to do the next: + +```typescript +scaffolderPlugin.__experimentalReconfigure({ + lastStepFormComponent: (props: LastStepFormProps) => ( + + ), +}); +``` From 9e5d5dfe75a0559f99576a99394f671f7e393f50 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Fri, 14 Oct 2022 11:52:56 +0200 Subject: [PATCH 04/89] Updated api-report.md Signed-off-by: bnechyporenko --- plugins/scaffolder/api-report.md | 45 ++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index cf69736bd4..f8628b193a 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -18,7 +18,8 @@ import { FieldProps } from '@rjsf/core'; import { FieldProps as FieldProps_2 } from '@rjsf/utils'; import { FieldValidation } from '@rjsf/core'; import { FieldValidation as FieldValidation_2 } from '@rjsf/utils'; -import type { FormProps } from '@rjsf/core'; +import { FormProps } from '@rjsf/core'; +import { IChangeEvent } from '@rjsf/core'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; @@ -26,6 +27,7 @@ import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; +import { ReactElement } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; @@ -130,6 +132,24 @@ export type FieldExtensionOptions< validation?: CustomFieldValidator; }; +// @public +export const LastStepForm: (props: LastStepFormProps) => JSX.Element; + +// @public +export const lastStepFormComponent: (props: LastStepFormProps) => JSX.Element; + +// @public +export type LastStepFormProps = { + disableButtons: boolean; + finishButtonLabel?: string; + formData: Record; + handleBack: () => void; + handleCreate: () => void; + handleReset: () => void; + onFinish?: () => Promise; + steps: Step[]; +}; + // @public export type LayoutComponent<_TInputProps> = () => null; @@ -167,6 +187,11 @@ export type LogEvent = { taskId: string; }; +// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts +// +// @public +export const MultistepJsonForm: (props: Props) => JSX.Element; + // @alpha export type NextCustomFieldValidator = ( data: TFieldReturnValue, @@ -421,6 +446,11 @@ export interface ScaffolderGetIntegrationsListResponse { }[]; } +// @public +export type ScaffolderInputPluginOptionsOptions = { + lastStepFormComponent: (props: LastStepFormProps) => ReactElement; +}; + // @public export const ScaffolderLayouts: React.ComponentType; @@ -451,9 +481,14 @@ export const scaffolderPlugin: BackstagePlugin< true >; }, - {} + ScaffolderInputPluginOptionsOptions >; +// @public +export type ScaffolderPluginOptions = { + lastStepFormComponent: (props: LastStepFormProps) => ReactElement; +}; + // @public export interface ScaffolderScaffoldOptions { // (undocumented) @@ -508,6 +543,12 @@ export interface ScaffolderUseTemplateSecrets { setSecrets: (input: Record) => void; } +// @public +export type Step = { + schema: JsonObject; + title: string; +} & Partial, 'schema'>>; + // @public export const TaskPage: ({ loadingText }: TaskPageProps) => JSX.Element; From be2ee4cc66a6f907340ecdf24925d4f3d4baf1af Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Fri, 14 Oct 2022 13:23:05 +0200 Subject: [PATCH 05/89] Updated api-report.md Signed-off-by: bnechyporenko --- .changeset/angry-trees-relax.md | 2 +- plugins/scaffolder/api-report.md | 17 ++++++++++++++--- .../MultistepJsonForm/MultistepJsonForm.tsx | 9 +++++++-- .../src/components/MultistepJsonForm/index.ts | 1 + 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/.changeset/angry-trees-relax.md b/.changeset/angry-trees-relax.md index 17fad10e0f..23d5704cbd 100644 --- a/.changeset/angry-trees-relax.md +++ b/.changeset/angry-trees-relax.md @@ -6,7 +6,7 @@ Enabling the customization of the last page in the scaffolder template. To override the content you have to do the next: -```typescript +```typescript jsx scaffolderPlugin.__experimentalReconfigure({ lastStepFormComponent: (props: LastStepFormProps) => ( diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index f8628b193a..9db48b03cc 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -187,10 +187,21 @@ export type LogEvent = { taskId: string; }; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// // @public -export const MultistepJsonForm: (props: Props) => JSX.Element; +export const MultistepJsonForm: (props: MultistepJsonFormProps) => JSX.Element; + +// @public +export type MultistepJsonFormProps = { + steps: Step[]; + formData: Record; + onChange: (e: IChangeEvent) => void; + onReset: () => void; + onFinish?: () => Promise; + widgets?: FormProps['widgets']; + fields?: FormProps['fields']; + finishButtonLabel?: string; + layouts: LayoutOptions[]; +}; // @alpha export type NextCustomFieldValidator = ( diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 4f067141c9..bf85210eee 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -38,7 +38,12 @@ import { useScaffolderPluginOptions } from '../../options'; const Form = withTheme(MuiTheme); -type Props = { +/** + * The props for a dynamic form of a scaffolder template. + * + * @public + */ +export type MultistepJsonFormProps = { /** * Steps for the form, each contains title and form schema */ @@ -58,7 +63,7 @@ type Props = { * * @public */ -export const MultistepJsonForm = (props: Props) => { +export const MultistepJsonForm = (props: MultistepJsonFormProps) => { const { formData, onChange, diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts b/plugins/scaffolder/src/components/MultistepJsonForm/index.ts index 59c58fcb05..262bd0447f 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts +++ b/plugins/scaffolder/src/components/MultistepJsonForm/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ export { MultistepJsonForm } from './MultistepJsonForm'; +export type { MultistepJsonFormProps } from './MultistepJsonForm'; export { lastStepFormComponent, LastStepForm } from './LastStepForm'; export type { LastStepFormProps } from './LastStepForm'; export type { Step } from './types'; From 20ea58ad60c60940e5bfa3a0bab497155d82a967 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Fri, 14 Oct 2022 14:29:17 +0200 Subject: [PATCH 06/89] Fixed the test Signed-off-by: bnechyporenko --- .../TemplatePage/TemplatePage.test.tsx | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx index d35b561f2b..6090ae370d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx @@ -22,12 +22,15 @@ import { ScaffolderApi } from '../../types'; import { rootRouteRef } from '../../routes'; import { TemplatePage } from './TemplatePage'; import { - featureFlagsApiRef, + BackstagePlugin, + errorApiRef, FeatureFlagsApi, + featureFlagsApiRef, + PluginProvider, } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; -import { errorApiRef } from '@backstage/core-plugin-api'; +import { lastStepFormComponent } from '../MultistepJsonForm'; jest.mock('react-router-dom', () => { return { @@ -107,6 +110,12 @@ const apis = TestApiRegistry.from( [featureFlagsApiRef, featureFlagsApiMock], ); +const plugin = { + getPluginOptions: () => ({ + lastStepFormComponent, + }), +} as unknown as BackstagePlugin; + describe('TemplatePage', () => { beforeEach(() => jest.resetAllMocks()); @@ -117,7 +126,9 @@ describe('TemplatePage', () => { }); const rendered = await renderInTestApp( - + + + , { mountedRoutes: { @@ -219,7 +230,9 @@ describe('TemplatePage', () => { const { findByText, findByLabelText, findAllByRole, findByRole } = await renderInTestApp( - + + + , { mountedRoutes: { @@ -263,7 +276,9 @@ describe('TemplatePage', () => { const { queryByText } = await renderInTestApp( - + + + , { mountedRoutes: { From 8357691f00bd6f3c4451b123b42f0f4de49eeea1 Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Wed, 19 Oct 2022 23:56:39 +0200 Subject: [PATCH 07/89] Incorporated the feedback Signed-off-by: bnechyporenko Signed-off-by: bogdannechyporenko --- .changeset/angry-trees-relax.md | 2 +- .../MultistepJsonForm/LastStepForm.tsx | 2 +- .../MultistepJsonForm/MultistepJsonForm.tsx | 31 ++++++++++--------- .../src/components/MultistepJsonForm/index.ts | 1 - plugins/scaffolder/src/components/Router.tsx | 14 ++++++--- .../components/TemplatePage/TemplatePage.tsx | 6 +++- .../{MultistepJsonForm => }/types.ts | 12 +++++++ 7 files changed, 45 insertions(+), 23 deletions(-) rename plugins/scaffolder/src/components/{MultistepJsonForm => }/types.ts (76%) diff --git a/.changeset/angry-trees-relax.md b/.changeset/angry-trees-relax.md index 23d5704cbd..761d7db854 100644 --- a/.changeset/angry-trees-relax.md +++ b/.changeset/angry-trees-relax.md @@ -2,7 +2,7 @@ '@backstage/plugin-scaffolder': patch --- -Enabling the customization of the last page in the scaffolder template. +Enabling the customization of the last step in the scaffolder template. To override the content you have to do the next: diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx index 3ee6609c73..04ac867a23 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx @@ -16,9 +16,9 @@ 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'; +import { Step } from '../types'; /** * The props for the Last Step in scaffolder template form. diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index bf85210eee..0c54e84ead 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -28,13 +28,13 @@ import { } from '@backstage/core-plugin-api'; import { FormProps, IChangeEvent, withTheme } from '@rjsf/core'; import { Theme as MuiTheme } from '@rjsf/material-ui'; -import React, { useState } from 'react'; +import React, { ComponentType, useState } from 'react'; import { transformSchemaToProps } from './schema'; import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; -import { Step } from './types'; -import { useScaffolderPluginOptions } from '../../options'; +import { LastStepFormProps, Step } from '../types'; +import { LastStepForm } from './LastStepForm'; const Form = withTheme(MuiTheme); @@ -56,6 +56,7 @@ export type MultistepJsonFormProps = { fields?: FormProps['fields']; finishButtonLabel?: string; layouts: LayoutOptions[]; + LastStepFormComponent?: ComponentType; }; /** @@ -73,6 +74,7 @@ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { widgets, finishButtonLabel, layouts, + LastStepFormComponent, } = props; const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); @@ -140,7 +142,7 @@ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { } }; - const { lastStepFormComponent } = useScaffolderPluginOptions(); + const LastStepFormElement = LastStepFormComponent ?? LastStepForm; return ( <> @@ -184,16 +186,17 @@ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { ); })} - {activeStep === steps.length && - lastStepFormComponent({ - disableButtons, - handleBack, - handleCreate, - handleReset, - finishButtonLabel, - formData, - steps, - })} + {activeStep === steps.length && ( + + )} ); }; diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts b/plugins/scaffolder/src/components/MultistepJsonForm/index.ts index 262bd0447f..784dd7c1b9 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts +++ b/plugins/scaffolder/src/components/MultistepJsonForm/index.ts @@ -17,4 +17,3 @@ export { MultistepJsonForm } from './MultistepJsonForm'; export type { MultistepJsonFormProps } from './MultistepJsonForm'; export { lastStepFormComponent, LastStepForm } from './LastStepForm'; export type { LastStepFormProps } from './LastStepForm'; -export type { Step } from './types'; diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 021d73f5e9..a27d16644e 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -15,7 +15,7 @@ */ import React, { ComponentType, useEffect } from 'react'; -import { Routes, Route, useOutlet, Navigate } from 'react-router'; +import { Navigate, Route, Routes, useOutlet } from 'react-router'; import { Entity } from '@backstage/catalog-model'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { ScaffolderPage } from './ScaffolderPage'; @@ -26,10 +26,10 @@ import { SecretsContextProvider } from './secrets/SecretsContext'; import { TemplateEditorPage } from './TemplateEditorPage'; import { - FieldExtensionOptions, - FIELD_EXTENSION_WRAPPER_KEY, - FIELD_EXTENSION_KEY, DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS, + FIELD_EXTENSION_KEY, + FIELD_EXTENSION_WRAPPER_KEY, + FieldExtensionOptions, } from '../extensions'; import { useElementFilter, @@ -46,6 +46,7 @@ import { } from '../routes'; import { ListTasksPage } from './ListTasksPage'; import { LayoutOptions, LAYOUTS_KEY, LAYOUTS_WRAPPER_KEY } from '../layouts'; +import { LastStepFormProps } from './types'; /** * The props for the entrypoint `ScaffolderPage` component the plugin. @@ -53,6 +54,7 @@ import { LayoutOptions, LAYOUTS_KEY, LAYOUTS_WRAPPER_KEY } from '../layouts'; */ export type RouterProps = { components?: { + LastStepFormComponent?: ComponentType; TemplateCardComponent?: | ComponentType<{ template: TemplateEntityV1beta3 }> | undefined; @@ -82,7 +84,8 @@ export type RouterProps = { export const Router = (props: RouterProps) => { const { groups, components = {}, defaultPreviewTemplate } = props; - const { TemplateCardComponent, TaskPageComponent } = components; + const { LastStepFormComponent, TemplateCardComponent, TaskPageComponent } = + components; const outlet = useOutlet(); const TaskPageElement = TaskPageComponent ?? TaskPage; @@ -155,6 +158,7 @@ export const Router = (props: RouterProps) => { element={ diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 9eba61b53b..6d4eef328f 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -16,7 +16,7 @@ import { LinearProgress } from '@material-ui/core'; import { IChangeEvent } from '@rjsf/core'; import qs from 'qs'; -import React, { useCallback, useContext, useState } from 'react'; +import React, { ComponentType, useCallback, useContext, useState } from 'react'; import { Navigate, useNavigate } from 'react-router'; import useAsync from 'react-use/lib/useAsync'; import { scaffolderApiRef } from '../../api'; @@ -40,6 +40,7 @@ import { } from '@backstage/core-plugin-api'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { LayoutOptions } from '../../layouts'; +import { LastStepFormProps } from '../types'; const useTemplateParameterSchema = (templateRef: string) => { const scaffolderApi = useApi(scaffolderApiRef); @@ -51,9 +52,11 @@ const useTemplateParameterSchema = (templateRef: string) => { }; export const TemplatePage = ({ + LastStepFormComponent, customFieldExtensions = [], layouts = [], }: { + LastStepFormComponent?: ComponentType; customFieldExtensions?: FieldExtensionOptions[]; layouts?: LayoutOptions[]; }) => { @@ -144,6 +147,7 @@ export const TemplatePage = ({ titleTypographyProps={{ component: 'h2' }} > , 'schema'>>; + +export type LastStepFormProps = { + disableButtons: boolean; + finishButtonLabel?: string; + formData: Record; + handleBack: () => void; + handleCreate: () => void; + handleReset: () => void; + onFinish?: () => Promise; + steps: Step[]; +}; From c1c117da99406f64e87e9aae807cc53196d59f0c Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Thu, 20 Oct 2022 00:00:41 +0200 Subject: [PATCH 08/89] Incorporated the feedback Signed-off-by: bnechyporenko Signed-off-by: bogdannechyporenko --- .../MultistepJsonForm/LastStepForm.tsx | 4 -- .../MultistepJsonForm/MultistepJsonForm.tsx | 2 - .../TemplatePage/TemplatePage.test.tsx | 25 +++--------- plugins/scaffolder/src/components/types.ts | 2 - plugins/scaffolder/src/index.ts | 4 -- plugins/scaffolder/src/options.ts | 40 ------------------- plugins/scaffolder/src/plugin.tsx | 13 ------ 7 files changed, 5 insertions(+), 85 deletions(-) delete mode 100644 plugins/scaffolder/src/options.ts diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx index 04ac867a23..8cdd8581d1 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx @@ -23,8 +23,6 @@ import { Step } from '../types'; /** * The props for the Last Step in scaffolder template form. * Which represents the summary of the input provided by the end user. - * - * @public */ export type LastStepFormProps = { disableButtons: boolean; @@ -93,8 +91,6 @@ export function getReviewData(formData: Record, steps: Step[]) { /** * The component displaying the Last Step in scaffolder template form. * Which represents the summary of the input provided by the end user. - * - * @public */ export const LastStepForm = (props: LastStepFormProps) => { const { diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 0c54e84ead..0faa6653c7 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -61,8 +61,6 @@ export type MultistepJsonFormProps = { /** * Creates the dynamic form for a scaffolder template. - * - * @public */ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { const { diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx index 6090ae370d..d35b561f2b 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx @@ -22,15 +22,12 @@ import { ScaffolderApi } from '../../types'; import { rootRouteRef } from '../../routes'; import { TemplatePage } from './TemplatePage'; import { - BackstagePlugin, - errorApiRef, - FeatureFlagsApi, featureFlagsApiRef, - PluginProvider, + FeatureFlagsApi, } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; -import { lastStepFormComponent } from '../MultistepJsonForm'; +import { errorApiRef } from '@backstage/core-plugin-api'; jest.mock('react-router-dom', () => { return { @@ -110,12 +107,6 @@ const apis = TestApiRegistry.from( [featureFlagsApiRef, featureFlagsApiMock], ); -const plugin = { - getPluginOptions: () => ({ - lastStepFormComponent, - }), -} as unknown as BackstagePlugin; - describe('TemplatePage', () => { beforeEach(() => jest.resetAllMocks()); @@ -126,9 +117,7 @@ describe('TemplatePage', () => { }); const rendered = await renderInTestApp( - - - + , { mountedRoutes: { @@ -230,9 +219,7 @@ describe('TemplatePage', () => { const { findByText, findByLabelText, findAllByRole, findByRole } = await renderInTestApp( - - - + , { mountedRoutes: { @@ -276,9 +263,7 @@ describe('TemplatePage', () => { const { queryByText } = await renderInTestApp( - - - + , { mountedRoutes: { diff --git a/plugins/scaffolder/src/components/types.ts b/plugins/scaffolder/src/components/types.ts index 9717db97a8..ee5efdbeee 100644 --- a/plugins/scaffolder/src/components/types.ts +++ b/plugins/scaffolder/src/components/types.ts @@ -19,8 +19,6 @@ import { JsonObject } from '@backstage/types'; /** * The props for the Step in scaffolder template. - * - * @public */ export type Step = { schema: JsonObject; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 12aa8e28ec..e68eece8e5 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -67,10 +67,6 @@ export type { TaskPageProps } from './components/TaskPage'; export { NextScaffolderPage } from './plugin'; export type { NextRouterProps } from './next'; export type { TemplateGroupFilter } from './next'; -export type { - ScaffolderInputPluginOptionsOptions, - ScaffolderPluginOptions, -} from './options'; export { createNextScaffolderFieldExtension, type NextCustomFieldValidator, diff --git a/plugins/scaffolder/src/options.ts b/plugins/scaffolder/src/options.ts deleted file mode 100644 index 90cdd9ba2a..0000000000 --- a/plugins/scaffolder/src/options.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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'; - -/** - * Scaffolder Plugin options to override default plugin behavior. - * - * @public - */ -export type ScaffolderPluginOptions = { - lastStepFormComponent: (props: LastStepFormProps) => ReactElement; -}; - -/** - * Scaffolder Plugin options to override default plugin behavior. - * - * @public - */ -export type ScaffolderInputPluginOptionsOptions = { - lastStepFormComponent: (props: LastStepFormProps) => ReactElement; -}; - -export const useScaffolderPluginOptions = () => - usePluginOptions(); diff --git a/plugins/scaffolder/src/plugin.tsx b/plugins/scaffolder/src/plugin.tsx index 324bd1bf28..670ed37ad9 100644 --- a/plugins/scaffolder/src/plugin.tsx +++ b/plugins/scaffolder/src/plugin.tsx @@ -39,11 +39,6 @@ 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'; /** * The main plugin export for the scaffolder. @@ -76,14 +71,6 @@ export const scaffolderPlugin = createPlugin({ registerComponent: registerComponentRouteRef, viewTechDoc: viewTechDocRouteRef, }, - __experimentalConfigure( - options?: ScaffolderInputPluginOptionsOptions, - ): ScaffolderPluginOptions { - const defaultOptions = { - lastStepFormComponent, - }; - return { ...defaultOptions, ...options }; - }, }); /** From 1d55859deef09cec03ad73320fd2e9e9fe493f1d Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Thu, 20 Oct 2022 00:02:38 +0200 Subject: [PATCH 09/89] Incorporated the feedback Signed-off-by: bnechyporenko Signed-off-by: bogdannechyporenko --- .changeset/angry-trees-relax.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.changeset/angry-trees-relax.md b/.changeset/angry-trees-relax.md index 761d7db854..584ecf2c1b 100644 --- a/.changeset/angry-trees-relax.md +++ b/.changeset/angry-trees-relax.md @@ -7,9 +7,5 @@ Enabling the customization of the last step in the scaffolder template. To override the content you have to do the next: ```typescript jsx -scaffolderPlugin.__experimentalReconfigure({ - lastStepFormComponent: (props: LastStepFormProps) => ( - - ), -}); + ``` From 518b41ec23efd21c47d32414e33116dbdbcdeab4 Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Thu, 20 Oct 2022 00:23:44 +0200 Subject: [PATCH 10/89] Incorporated the feedback Signed-off-by: bnechyporenko Signed-off-by: bogdannechyporenko --- plugins/scaffolder/api-report.md | 37 +------------------ .../MultistepJsonForm/LastStepForm.tsx | 26 +------------ .../MultistepJsonForm/MultistepJsonForm.tsx | 2 - .../src/components/MultistepJsonForm/index.ts | 3 -- plugins/scaffolder/src/components/index.ts | 2 +- plugins/scaffolder/src/components/types.ts | 8 ++++ 6 files changed, 12 insertions(+), 66 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 9db48b03cc..5317e082ce 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -19,7 +19,6 @@ import { FieldProps as FieldProps_2 } from '@rjsf/utils'; import { FieldValidation } from '@rjsf/core'; import { FieldValidation as FieldValidation_2 } from '@rjsf/utils'; import { FormProps } from '@rjsf/core'; -import { IChangeEvent } from '@rjsf/core'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; @@ -27,7 +26,6 @@ import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; -import { ReactElement } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; @@ -132,12 +130,6 @@ export type FieldExtensionOptions< validation?: CustomFieldValidator; }; -// @public -export const LastStepForm: (props: LastStepFormProps) => JSX.Element; - -// @public -export const lastStepFormComponent: (props: LastStepFormProps) => JSX.Element; - // @public export type LastStepFormProps = { disableButtons: boolean; @@ -187,22 +179,6 @@ export type LogEvent = { taskId: string; }; -// @public -export const MultistepJsonForm: (props: MultistepJsonFormProps) => JSX.Element; - -// @public -export type MultistepJsonFormProps = { - steps: Step[]; - formData: Record; - onChange: (e: IChangeEvent) => void; - onReset: () => void; - onFinish?: () => Promise; - widgets?: FormProps['widgets']; - fields?: FormProps['fields']; - finishButtonLabel?: string; - layouts: LayoutOptions[]; -}; - // @alpha export type NextCustomFieldValidator = ( data: TFieldReturnValue, @@ -326,6 +302,7 @@ export interface RepoUrlPickerUiOptions { // @public export type RouterProps = { components?: { + LastStepFormComponent?: ComponentType; TemplateCardComponent?: | ComponentType<{ template: TemplateEntityV1beta3; @@ -457,11 +434,6 @@ export interface ScaffolderGetIntegrationsListResponse { }[]; } -// @public -export type ScaffolderInputPluginOptionsOptions = { - lastStepFormComponent: (props: LastStepFormProps) => ReactElement; -}; - // @public export const ScaffolderLayouts: React.ComponentType; @@ -492,14 +464,9 @@ export const scaffolderPlugin: BackstagePlugin< true >; }, - ScaffolderInputPluginOptionsOptions + {} >; -// @public -export type ScaffolderPluginOptions = { - lastStepFormComponent: (props: LastStepFormProps) => ReactElement; -}; - // @public export interface ScaffolderScaffoldOptions { // (undocumented) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx index 8cdd8581d1..6109cc0262 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/LastStepForm.tsx @@ -18,22 +18,7 @@ import React from 'react'; import { Content, StructuredMetadataTable } from '@backstage/core-components'; import { UiSchema } from '@rjsf/core'; import { JsonObject } from '@backstage/types'; -import { Step } from '../types'; - -/** - * The props for the Last Step in scaffolder template form. - * Which represents the summary of the input provided by the end user. - */ -export type LastStepFormProps = { - disableButtons: boolean; - finishButtonLabel?: string; - formData: Record; - handleBack: () => void; - handleCreate: () => void; - handleReset: () => void; - onFinish?: () => Promise; - steps: Step[]; -}; +import { LastStepFormProps, Step } from '../types'; export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { const uiSchemas: Array = []; @@ -130,12 +115,3 @@ export const LastStepForm = (props: LastStepFormProps) => { ); }; - -/** - * Creates LastStepForm from provided properties. - * - * @public - */ -export const lastStepFormComponent = (props: LastStepFormProps) => ( - -); diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 0faa6653c7..a868e3692f 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -40,8 +40,6 @@ const Form = withTheme(MuiTheme); /** * The props for a dynamic form of a scaffolder template. - * - * @public */ export type MultistepJsonFormProps = { /** diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts b/plugins/scaffolder/src/components/MultistepJsonForm/index.ts index 784dd7c1b9..b125e7c4c2 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts +++ b/plugins/scaffolder/src/components/MultistepJsonForm/index.ts @@ -14,6 +14,3 @@ * limitations under the License. */ export { MultistepJsonForm } from './MultistepJsonForm'; -export type { MultistepJsonFormProps } from './MultistepJsonForm'; -export { lastStepFormComponent, LastStepForm } from './LastStepForm'; -export type { LastStepFormProps } from './LastStepForm'; diff --git a/plugins/scaffolder/src/components/index.ts b/plugins/scaffolder/src/components/index.ts index 20bdcf2ad3..0f9d91653c 100644 --- a/plugins/scaffolder/src/components/index.ts +++ b/plugins/scaffolder/src/components/index.ts @@ -19,4 +19,4 @@ export { TemplateTypePicker } from './TemplateTypePicker'; export * from './secrets'; export { TaskPage } from './TaskPage'; export type { RouterProps } from './Router'; -export * from './MultistepJsonForm'; +export type { LastStepFormProps, Step } from './types'; diff --git a/plugins/scaffolder/src/components/types.ts b/plugins/scaffolder/src/components/types.ts index ee5efdbeee..9eb1e58f3d 100644 --- a/plugins/scaffolder/src/components/types.ts +++ b/plugins/scaffolder/src/components/types.ts @@ -19,12 +19,20 @@ import { JsonObject } from '@backstage/types'; /** * The props for the Step in scaffolder template. + * + * @public */ export type Step = { schema: JsonObject; title: string; } & Partial, 'schema'>>; +/** + * The props for the Last Step in scaffolder template form. + * Which represents the summary of the input provided by the end user. + * + * @public + */ export type LastStepFormProps = { disableButtons: boolean; finishButtonLabel?: string; From 2b393780758aead41ab05ef2e9ae3d35583d6c8e Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Thu, 20 Oct 2022 14:41:36 +0200 Subject: [PATCH 11/89] Ran prettier Signed-off-by: bnechyporenko --- .changeset/angry-trees-relax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/angry-trees-relax.md b/.changeset/angry-trees-relax.md index 584ecf2c1b..0aa88813c1 100644 --- a/.changeset/angry-trees-relax.md +++ b/.changeset/angry-trees-relax.md @@ -7,5 +7,5 @@ Enabling the customization of the last step in the scaffolder template. To override the content you have to do the next: ```typescript jsx - + ``` From 392431e5cd32ff9a93eecac72fa9e99617e2a120 Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Sun, 23 Oct 2022 16:31:42 +0200 Subject: [PATCH 12/89] Fix Signed-off-by: bnechyporenko Signed-off-by: bogdannechyporenko --- .../src/components/MultistepJsonForm/MultistepJsonForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 75a80640b6..44b739d57b 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -198,6 +198,7 @@ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { handleReset={handleReset} finishButtonLabel={finishButtonLabel} formData={formData} + onFinish={onFinish} steps={steps} /> )} From 577d7ad3ad043421f4fae9a2c3caaba2218bd65b Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Mon, 24 Oct 2022 20:49:53 +0200 Subject: [PATCH 13/89] Incorporated a feedback Signed-off-by: bnechyporenko Signed-off-by: bogdannechyporenko --- .../MultistepJsonForm/MultistepJsonForm.test.tsx | 2 +- .../MultistepJsonForm/MultistepJsonForm.tsx | 12 ++++++------ .../{LastStepForm.tsx => ReviewStep.tsx} | 4 ++-- plugins/scaffolder/src/components/Router.tsx | 8 ++++---- .../src/components/TemplatePage/TemplatePage.tsx | 8 ++++---- plugins/scaffolder/src/components/index.ts | 2 +- plugins/scaffolder/src/components/types.ts | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) rename plugins/scaffolder/src/components/MultistepJsonForm/{LastStepForm.tsx => ReviewStep.tsx} (96%) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx index 60cca38f06..f238c62a8a 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 './LastStepForm'; +import { getReviewData } from './ReviewStep'; describe('MultistepJsonForm', () => { const formDataMock = { diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 44b739d57b..ce0e3616fd 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -35,8 +35,8 @@ import { transformSchemaToProps } from './schema'; import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; -import { LastStepFormProps, Step } from '../types'; -import { LastStepForm } from './LastStepForm'; +import { ReviewStepComponentProps, Step } from '../types'; +import { ReviewStep } from './ReviewStep'; import { selectedTemplateRouteRef } from '../../routes'; const Form = withTheme(MuiTheme); @@ -57,7 +57,7 @@ export type MultistepJsonFormProps = { fields?: FormProps['fields']; finishButtonLabel?: string; layouts: LayoutOptions[]; - LastStepFormComponent?: ComponentType; + ReviewStepComponent?: ComponentType; }; /** @@ -73,7 +73,7 @@ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { widgets, finishButtonLabel, layouts, - LastStepFormComponent, + ReviewStepComponent, } = props; const { templateName } = useRouteRefParams(selectedTemplateRouteRef); const analytics = useAnalytics(); @@ -146,7 +146,7 @@ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { } }; - const LastStepFormElement = LastStepFormComponent ?? LastStepForm; + const ReviewStepElement = ReviewStepComponent ?? ReviewStep; return ( <> @@ -191,7 +191,7 @@ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { })} {activeStep === steps.length && ( - = []; @@ -77,7 +77,7 @@ export function getReviewData(formData: Record, steps: Step[]) { * The component displaying the Last Step in scaffolder template form. * Which represents the summary of the input provided by the end user. */ -export const LastStepForm = (props: LastStepFormProps) => { +export const ReviewStep = (props: ReviewStepComponentProps) => { const { disableButtons, finishButtonLabel, diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 7dba0a9fba..4f394ebccf 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -46,7 +46,7 @@ import { } from '../routes'; import { ListTasksPage } from './ListTasksPage'; import { LayoutOptions, LAYOUTS_KEY, LAYOUTS_WRAPPER_KEY } from '../layouts'; -import { LastStepFormProps } from './types'; +import { ReviewStepComponentProps } from './types'; /** * The props for the entrypoint `ScaffolderPage` component the plugin. @@ -54,7 +54,7 @@ import { LastStepFormProps } from './types'; */ export type RouterProps = { components?: { - LastStepFormComponent?: ComponentType; + ReviewStepComponent?: ComponentType; TemplateCardComponent?: | ComponentType<{ template: TemplateEntityV1beta3 }> | undefined; @@ -89,7 +89,7 @@ export type RouterProps = { export const Router = (props: RouterProps) => { const { groups, components = {}, defaultPreviewTemplate } = props; - const { LastStepFormComponent, TemplateCardComponent, TaskPageComponent } = + const { ReviewStepComponent, TemplateCardComponent, TaskPageComponent } = components; const outlet = useOutlet(); @@ -164,7 +164,7 @@ export const Router = (props: RouterProps) => { element={ diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index dffbfadf62..64d62943b1 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -41,7 +41,7 @@ import { } from '@backstage/core-plugin-api'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { LayoutOptions } from '../../layouts'; -import { LastStepFormProps } from '../types'; +import { ReviewStepComponentProps } from '../types'; const useTemplateParameterSchema = (templateRef: string) => { const scaffolderApi = useApi(scaffolderApiRef); @@ -53,11 +53,11 @@ const useTemplateParameterSchema = (templateRef: string) => { }; export const TemplatePage = ({ - LastStepFormComponent, + ReviewStepComponent, customFieldExtensions = [], layouts = [], }: { - LastStepFormComponent?: ComponentType; + ReviewStepComponent?: ComponentType; customFieldExtensions?: FieldExtensionOptions[]; layouts?: LayoutOptions[]; }) => { @@ -149,7 +149,7 @@ export const TemplatePage = ({ titleTypographyProps={{ component: 'h2' }} > ; From 16cd6d442a52822431d04c6734acad75b25d4010 Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Wed, 26 Oct 2022 22:00:32 +0200 Subject: [PATCH 14/89] Incorporated a feedback Signed-off-by: bnechyporenko Signed-off-by: bogdannechyporenko --- plugins/scaffolder/api-report.md | 33 ++++++++-------- .../MultistepJsonForm/MultistepJsonForm.tsx | 30 +++++++++++---- .../MultistepJsonForm/ReviewStep.tsx | 38 +++++++------------ plugins/scaffolder/src/components/Router.tsx | 4 +- .../components/TemplatePage/TemplatePage.tsx | 4 +- plugins/scaffolder/src/components/index.ts | 2 +- plugins/scaffolder/src/components/types.ts | 16 ++++---- 7 files changed, 68 insertions(+), 59 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 8ea360f050..4c447d4ab1 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -34,7 +34,8 @@ import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TaskStep } from '@backstage/plugin-scaffolder-common'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { UIOptionsType } from '@rjsf/utils'; -import { UiSchema } from '@rjsf/utils'; +import { UiSchema } from '@rjsf/core'; +import { UiSchema as UiSchema_2 } from '@rjsf/utils'; // @alpha export function createNextScaffolderFieldExtension< @@ -132,18 +133,6 @@ export type FieldExtensionOptions< validation?: CustomFieldValidator; }; -// @public -export type LastStepFormProps = { - disableButtons: boolean; - finishButtonLabel?: string; - formData: Record; - handleBack: () => void; - handleCreate: () => void; - handleReset: () => void; - onFinish?: () => Promise; - steps: Step[]; -}; - // @public export type LayoutComponent<_TInputProps> = () => null; @@ -196,7 +185,7 @@ export interface NextFieldExtensionComponentProps< TUiOptions = {}, > extends PropsWithChildren> { // (undocumented) - uiSchema?: UiSchema & { + uiSchema?: UiSchema_2 & { 'ui:options'?: TUiOptions & UIOptionsType; }; } @@ -309,13 +298,27 @@ export interface RepoUrlPickerUiOptions { }; } +// @public +export type ReviewStepProps = { + disableButtons: boolean; + formData: JsonObject; + handleBack: () => void; + handleReset: () => void; + handleCreate: () => void; + steps: { + uiSchema: UiSchema; + mergedSchema: JsonObject; + schema: JsonObject; + }[]; +}; + // @public (undocumented) export const rootRouteRef: RouteRef; // @public export type RouterProps = { components?: { - LastStepFormComponent?: ComponentType; + ReviewStepComponent?: ComponentType; TemplateCardComponent?: | ComponentType<{ template: TemplateEntityV1beta3; diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index ce0e3616fd..4b14f42d65 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -21,6 +21,7 @@ import { Stepper, Typography, } from '@material-ui/core'; +import { JsonObject } from '@backstage/types'; import { errorApiRef, featureFlagsApiRef, @@ -28,16 +29,17 @@ import { useRouteRefParams, useApi, } from '@backstage/core-plugin-api'; -import { FormProps, IChangeEvent, withTheme } from '@rjsf/core'; +import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core'; import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { ComponentType, useState } from 'react'; import { transformSchemaToProps } from './schema'; import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; -import { ReviewStepComponentProps, Step } from '../types'; +import { ReviewStepProps, Step } from '../types'; import { ReviewStep } from './ReviewStep'; import { selectedTemplateRouteRef } from '../../routes'; +import { extractSchemaFromStep } from '../../next/TemplateWizardPage/Stepper/schema'; const Form = withTheme(MuiTheme); @@ -57,9 +59,26 @@ export type MultistepJsonFormProps = { fields?: FormProps['fields']; finishButtonLabel?: string; layouts: LayoutOptions[]; - ReviewStepComponent?: ComponentType; + ReviewStepComponent?: ComponentType; }; +function getUiSchemasFromSteps(steps: Step[]): { + uiSchema: UiSchema; + mergedSchema: JsonObject; + schema: JsonObject; +}[] { + const res = steps.map(({ schema }) => ({ + mergedSchema: schema, + ...extractSchemaFromStep(schema), + })); + + return res as unknown as { + uiSchema: UiSchema; + mergedSchema: JsonObject; + schema: JsonObject; + }[]; +} + /** * Creates the dynamic form for a scaffolder template. */ @@ -71,7 +90,6 @@ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { onFinish, fields, widgets, - finishButtonLabel, layouts, ReviewStepComponent, } = props; @@ -196,10 +214,8 @@ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { handleBack={handleBack} handleCreate={handleCreate} handleReset={handleReset} - finishButtonLabel={finishButtonLabel} formData={formData} - onFinish={onFinish} - steps={steps} + steps={getUiSchemasFromSteps(steps)} /> )} diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx index 52e94aa1f7..1d60a49102 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx @@ -18,29 +18,16 @@ import React from 'react'; import { Content, StructuredMetadataTable } from '@backstage/core-components'; import { UiSchema } from '@rjsf/core'; import { JsonObject } from '@backstage/types'; -import { ReviewStepComponentProps, Step } from '../types'; +import { ReviewStepProps } from '../types'; -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); +export function getReviewData( + formData: Record, + steps: UiSchema[], +) { const reviewData: Record = {}; for (const key in formData) { if (formData.hasOwnProperty(key)) { - const uiSchema = uiSchemas.find(us => us.name === key); + const uiSchema = steps.find(us => us.name === key); if (!uiSchema) { reviewData[key] = formData[key]; @@ -77,15 +64,13 @@ export function getReviewData(formData: Record, steps: Step[]) { * The component displaying the Last Step in scaffolder template form. * Which represents the summary of the input provided by the end user. */ -export const ReviewStep = (props: ReviewStepComponentProps) => { +export const ReviewStep = (props: ReviewStepProps) => { const { disableButtons, - finishButtonLabel, formData, handleBack, handleCreate, handleReset, - onFinish, steps, } = props; return ( @@ -94,7 +79,10 @@ export const ReviewStep = (props: ReviewStepComponentProps) => { Review and create ({ uiSchema: step.uiSchema })), + )} /> diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 4f394ebccf..da7ceebe5b 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -46,7 +46,7 @@ import { } from '../routes'; import { ListTasksPage } from './ListTasksPage'; import { LayoutOptions, LAYOUTS_KEY, LAYOUTS_WRAPPER_KEY } from '../layouts'; -import { ReviewStepComponentProps } from './types'; +import { ReviewStepProps } from './types'; /** * The props for the entrypoint `ScaffolderPage` component the plugin. @@ -54,7 +54,7 @@ import { ReviewStepComponentProps } from './types'; */ export type RouterProps = { components?: { - ReviewStepComponent?: ComponentType; + ReviewStepComponent?: ComponentType; TemplateCardComponent?: | ComponentType<{ template: TemplateEntityV1beta3 }> | undefined; diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 64d62943b1..01dc3b832d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -41,7 +41,7 @@ import { } from '@backstage/core-plugin-api'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { LayoutOptions } from '../../layouts'; -import { ReviewStepComponentProps } from '../types'; +import { ReviewStepProps } from '../types'; const useTemplateParameterSchema = (templateRef: string) => { const scaffolderApi = useApi(scaffolderApiRef); @@ -57,7 +57,7 @@ export const TemplatePage = ({ customFieldExtensions = [], layouts = [], }: { - ReviewStepComponent?: ComponentType; + ReviewStepComponent?: ComponentType; customFieldExtensions?: FieldExtensionOptions[]; layouts?: LayoutOptions[]; }) => { diff --git a/plugins/scaffolder/src/components/index.ts b/plugins/scaffolder/src/components/index.ts index 7c84ca7be2..b10e1f9e76 100644 --- a/plugins/scaffolder/src/components/index.ts +++ b/plugins/scaffolder/src/components/index.ts @@ -19,4 +19,4 @@ export { TemplateTypePicker } from './TemplateTypePicker'; export * from './secrets'; export { TaskPage } from './TaskPage'; export type { RouterProps } from './Router'; -export type { ReviewStepComponentProps, Step } from './types'; +export type { ReviewStepProps, Step } from './types'; diff --git a/plugins/scaffolder/src/components/types.ts b/plugins/scaffolder/src/components/types.ts index 97cb8d2d88..45528b46f3 100644 --- a/plugins/scaffolder/src/components/types.ts +++ b/plugins/scaffolder/src/components/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FormProps } from '@rjsf/core'; +import { FormProps, UiSchema } from '@rjsf/core'; import { JsonObject } from '@backstage/types'; /** @@ -33,13 +33,15 @@ export type Step = { * * @public */ -export type ReviewStepComponentProps = { +export type ReviewStepProps = { disableButtons: boolean; - finishButtonLabel?: string; - formData: Record; + formData: JsonObject; handleBack: () => void; - handleCreate: () => void; handleReset: () => void; - onFinish?: () => Promise; - steps: Step[]; + handleCreate: () => void; + steps: { + uiSchema: UiSchema; + mergedSchema: JsonObject; + schema: JsonObject; + }[]; }; From 158aa895d8d354a1d304c8d085acd2cb8a65d0ee Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Wed, 26 Oct 2022 22:03:50 +0200 Subject: [PATCH 15/89] Incorporated a feedback Signed-off-by: bogdannechyporenko --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2332db24c1..c42aac2eec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3314,7 +3314,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@npm:^1.1.1, @backstage/catalog-model@npm:^1.1.2": +"@backstage/catalog-model@npm:^1.1.2": version: 1.1.2 resolution: "@backstage/catalog-model@npm:1.1.2" dependencies: @@ -3599,7 +3599,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@npm:^0.11.1, @backstage/core-components@npm:^0.11.2": +"@backstage/core-components@npm:^0.11.2": version: 0.11.2 resolution: "@backstage/core-components@npm:0.11.2" dependencies: @@ -3722,7 +3722,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-plugin-api@npm:^1.0.6, @backstage/core-plugin-api@npm:^1.0.7": +"@backstage/core-plugin-api@npm:^1.0.7": version: 1.0.7 resolution: "@backstage/core-plugin-api@npm:1.0.7" dependencies: @@ -5066,7 +5066,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@npm:^1.1.4, @backstage/plugin-catalog-react@npm:^1.2.0": +"@backstage/plugin-catalog-react@npm:^1.2.0": version: 1.2.0 resolution: "@backstage/plugin-catalog-react@npm:1.2.0" dependencies: From a821696bb14890bb793b97211b81c27a896e3194 Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Wed, 26 Oct 2022 22:10:57 +0200 Subject: [PATCH 16/89] Updated the doc Signed-off-by: bogdannechyporenko --- .changeset/angry-trees-relax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/angry-trees-relax.md b/.changeset/angry-trees-relax.md index 0aa88813c1..64dd50366c 100644 --- a/.changeset/angry-trees-relax.md +++ b/.changeset/angry-trees-relax.md @@ -7,5 +7,5 @@ Enabling the customization of the last step in the scaffolder template. To override the content you have to do the next: ```typescript jsx - + ``` From f16fa9b0bf5ebe4517b6283c5d672ac214b292fc Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Wed, 26 Oct 2022 22:12:24 +0200 Subject: [PATCH 17/89] A small change Signed-off-by: bogdannechyporenko --- .../src/components/MultistepJsonForm/MultistepJsonForm.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 4b14f42d65..b94eb43121 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -67,12 +67,10 @@ function getUiSchemasFromSteps(steps: Step[]): { mergedSchema: JsonObject; schema: JsonObject; }[] { - const res = steps.map(({ schema }) => ({ + return steps.map(({ schema }) => ({ mergedSchema: schema, ...extractSchemaFromStep(schema), - })); - - return res as unknown as { + })) as unknown as { uiSchema: UiSchema; mergedSchema: JsonObject; schema: JsonObject; From 59f4917e82349e750f245eef802fe8793cae8f2f Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Wed, 26 Oct 2022 22:15:47 +0200 Subject: [PATCH 18/89] Hidden Step type back Signed-off-by: bogdannechyporenko --- .../MultistepJsonForm/MultistepJsonForm.tsx | 7 ++++++- plugins/scaffolder/src/components/index.ts | 2 +- plugins/scaffolder/src/components/types.ts | 12 +----------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index b94eb43121..c24284d3e0 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -36,13 +36,18 @@ import { transformSchemaToProps } from './schema'; import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; -import { ReviewStepProps, Step } from '../types'; +import { ReviewStepProps } from '../types'; import { ReviewStep } from './ReviewStep'; import { selectedTemplateRouteRef } from '../../routes'; import { extractSchemaFromStep } from '../../next/TemplateWizardPage/Stepper/schema'; const Form = withTheme(MuiTheme); +type Step = { + schema: JsonObject; + title: string; +} & Partial, 'schema'>>; + /** * The props for a dynamic form of a scaffolder template. */ diff --git a/plugins/scaffolder/src/components/index.ts b/plugins/scaffolder/src/components/index.ts index b10e1f9e76..23f49ea748 100644 --- a/plugins/scaffolder/src/components/index.ts +++ b/plugins/scaffolder/src/components/index.ts @@ -19,4 +19,4 @@ export { TemplateTypePicker } from './TemplateTypePicker'; export * from './secrets'; export { TaskPage } from './TaskPage'; export type { RouterProps } from './Router'; -export type { ReviewStepProps, Step } from './types'; +export type { ReviewStepProps } from './types'; diff --git a/plugins/scaffolder/src/components/types.ts b/plugins/scaffolder/src/components/types.ts index 45528b46f3..3a0866a139 100644 --- a/plugins/scaffolder/src/components/types.ts +++ b/plugins/scaffolder/src/components/types.ts @@ -14,19 +14,9 @@ * limitations under the License. */ -import { FormProps, UiSchema } from '@rjsf/core'; +import { UiSchema } from '@rjsf/core'; import { JsonObject } from '@backstage/types'; -/** - * The props for the Step in scaffolder template. - * - * @public - */ -export type Step = { - schema: JsonObject; - title: string; -} & Partial, 'schema'>>; - /** * The props for the Last Step in scaffolder template form. * Which represents the summary of the input provided by the end user. From 4713e9dd063efb130c5250afe491dbef4ab2b08c Mon Sep 17 00:00:00 2001 From: bogdannechyporenko Date: Wed, 26 Oct 2022 23:04:06 +0200 Subject: [PATCH 19/89] Updated api-report.md Signed-off-by: bogdannechyporenko --- plugins/scaffolder/api-report.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 4c447d4ab1..2d06042a90 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -18,7 +18,7 @@ import { FieldProps } from '@rjsf/core'; import { FieldProps as FieldProps_2 } from '@rjsf/utils'; import { FieldValidation } from '@rjsf/core'; import { FieldValidation as FieldValidation_2 } from '@rjsf/utils'; -import { FormProps } from '@rjsf/core'; +import type { FormProps } from '@rjsf/core'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; @@ -547,12 +547,6 @@ export const selectedTemplateRouteRef: SubRouteRef< PathParams<'/templates/:namespace/:templateName'> >; -// @public -export type Step = { - schema: JsonObject; - title: string; -} & Partial, 'schema'>>; - // @public export const TaskPage: ({ loadingText }: TaskPageProps) => JSX.Element; From d4af3731fb718cb0e44e6a1e6c38ca002a9464f0 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Thu, 27 Oct 2022 09:34:31 +0200 Subject: [PATCH 20/89] Wip Signed-off-by: bnechyporenko --- .../MultistepJsonForm.test.tsx | 5 ++-- .../MultistepJsonForm/MultistepJsonForm.tsx | 4 +-- .../MultistepJsonForm/ReviewStep.tsx | 28 +++++++++++++++---- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx index f238c62a8a..3f8e153b65 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 './ReviewStep'; +import { getReviewData, getUiSchemasFromSteps } from './ReviewStep'; describe('MultistepJsonForm', () => { const formDataMock = { @@ -76,7 +76,8 @@ describe('MultistepJsonForm', () => { ]; test('Fields are defined to be hidden or masked', () => { - const reviewData = getReviewData(formDataMock, stepsMock); + const schemas = getUiSchemasFromSteps(stepsMock); + const reviewData = getReviewData(formDataMock, schemas); expect(reviewData.password).toBe('******'); expect(reviewData.masked).toBe('******'); diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index c24284d3e0..91cbdb2243 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -67,7 +67,7 @@ export type MultistepJsonFormProps = { ReviewStepComponent?: ComponentType; }; -function getUiSchemasFromSteps(steps: Step[]): { +export function getSchemasFromSteps(steps: Step[]): { uiSchema: UiSchema; mergedSchema: JsonObject; schema: JsonObject; @@ -218,7 +218,7 @@ export const MultistepJsonForm = (props: MultistepJsonFormProps) => { handleCreate={handleCreate} handleReset={handleReset} formData={formData} - steps={getUiSchemasFromSteps(steps)} + steps={getSchemasFromSteps(steps)} /> )} diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx index 1d60a49102..d78d726869 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx @@ -22,12 +22,12 @@ import { ReviewStepProps } from '../types'; export function getReviewData( formData: Record, - steps: UiSchema[], + uiSchemas: UiSchema[], ) { const reviewData: Record = {}; for (const key in formData) { if (formData.hasOwnProperty(key)) { - const uiSchema = steps.find(us => us.name === key); + const uiSchema = uiSchemas.find(us => us.name === key); if (!uiSchema) { reviewData[key] = formData[key]; @@ -60,6 +60,25 @@ export function getReviewData( return reviewData; } +export function getUiSchemasFromSteps( + steps: { + schema: JsonObject; + }[], +): 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; +} + /** * The component displaying the Last Step in scaffolder template form. * Which represents the summary of the input provided by the end user. @@ -79,10 +98,7 @@ export const ReviewStep = (props: ReviewStepProps) => { Review and create ({ uiSchema: step.uiSchema })), - )} + metadata={getReviewData(formData, getUiSchemasFromSteps(steps))} />