From 784673638a59a4b56fa8e03cba882d7bfb4c9b07 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 19 Aug 2022 11:44:56 +0200 Subject: [PATCH 1/6] chore: reworking how we do the review step Signed-off-by: blam --- .../Stepper/ReviewState.test.tsx | 61 +++++++++++++++++++ .../Stepper/ReviewState.tsx | 58 ++++++++++++++++++ .../TemplateWizardPage/Stepper/Stepper.tsx | 54 +++++++++------- .../Stepper/useTemplateSchema.ts | 17 +++--- 4 files changed, 158 insertions(+), 32 deletions(-) create mode 100644 plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx create mode 100644 plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx new file mode 100644 index 0000000000..b77d0d0c59 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ReviewState } from './ReviewState'; +import { render } from '@testing-library/react'; + +describe('ReviewState', () => { + it('should render the text as normal with no options', () => { + const formState = { + name: 'John Doe', + test: 'bob', + }; + + const { getByRole } = render( + , + ); + + expect(getByRole('row', { name: 'Name John Doe' })).toBeInTheDocument(); + expect(getByRole('row', { name: 'Test bob' })).toBeInTheDocument(); + }); + + it('should mask password ui:fields', () => { + const formState = { + name: 'John Doe', + test: 'bob', + }; + + const schemas = [ + { + mergedSchema: { + type: 'object', + properties: { + name: { + type: 'string', + 'ui:widget': 'password', + }, + }, + }, + }, + ]; + + const { getByRole } = render( + , + ); + + expect(getByRole('row', { name: 'Name ******' })).toBeInTheDocument(); + }); +}); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx new file mode 100644 index 0000000000..4889b24483 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { StructuredMetadataTable } from '@backstage/core-components'; +import { JsonObject } from '@backstage/types'; +import { ParsedTemplateSchema } from './useTemplateSchema'; +import { Draft07 as JSONSchema } from 'json-schema-library'; + +interface ReviewStateProps { + schemas: ParsedTemplateSchema[]; + formState: JsonObject; +} + +export const ReviewState = (props: ReviewStateProps) => { + const reviewData = Object.fromEntries( + Object.entries(props.formState).map(([key, value]) => { + for (const step of props.schemas) { + const parsedSchema = new JSONSchema(step.mergedSchema); + const definitionInSchema = parsedSchema.getSchema( + `#/${key}`, + props.formState, + ); + if (definitionInSchema) { + if (definitionInSchema['ui:widget'] === 'password') { + return [key, '******']; + } + + const backstageReviewOptions = + definitionInSchema['ui:backstage']?.review; + + if (backstageReviewOptions) { + if (backstageReviewOptions.mask) { + return [key, backstageReviewOptions.mask]; + } + if (!backstageReviewOptions.show) { + return []; + } + } + } + } + return [key, value]; + }), + ); + return ; +}; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index b85a1e1f5d..80926e5909 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -29,6 +29,7 @@ import { FieldExtensionOptions } from '../../../extensions'; import { TemplateParameterSchema } from '../../../types'; import { createAsyncValidators } from './createAsyncValidators'; import { useTemplateSchema } from './useTemplateSchema'; +import { ReviewState } from './ReviewState'; const useStyles = makeStyles(theme => ({ backButton: { @@ -74,7 +75,7 @@ export const Stepper = (props: StepperProps) => { }, [props.extensions]); const validation = useMemo(() => { - const { mergedSchema } = steps[activeStep]; + const { mergedSchema } = steps[activeStep] ?? {}; return createAsyncValidators(mergedSchema, validators, { apiHolder, }); @@ -112,30 +113,37 @@ export const Stepper = (props: StepperProps) => { {step.title} ))} + + Review +
-
-
- - -
-
+ {activeStep < steps.length ? ( +
+
+ + +
+
+ ) : ( + + )}
); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts index 0482d89503..dc23a8a49c 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts @@ -19,17 +19,16 @@ import { UiSchema } from '@rjsf/core'; import { TemplateParameterSchema } from '../../../types'; import { extractSchemaFromStep } from './schema'; +export interface ParsedTemplateSchema { + uiSchema: UiSchema; + mergedSchema: JsonObject; + schema: JsonObject; + title: string; + description?: string; +} export const useTemplateSchema = ( manifest: TemplateParameterSchema, -): { - steps: { - uiSchema: UiSchema; - mergedSchema: JsonObject; - schema: JsonObject; - title: string; - description?: string; - }[]; -} => { +): { steps: ParsedTemplateSchema } => { const featureFlags = useApi(featureFlagsApiRef); const steps = manifest.steps.map(({ title, description, schema }) => ({ title, From d8e317e6612624043896f284c50b9c2269672978 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 19 Aug 2022 13:23:16 +0200 Subject: [PATCH 2/6] chore: working out the review step a little more Signed-off-by: blam --- .../Stepper/ReviewState.test.tsx | 7 ++++++- .../TemplateWizardPage/Stepper/Stepper.test.tsx | 14 +++++++------- .../next/TemplateWizardPage/Stepper/Stepper.tsx | 3 +-- .../Stepper/useTemplateSchema.ts | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx index b77d0d0c59..d603213c19 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx @@ -16,6 +16,7 @@ import React from 'react'; import { ReviewState } from './ReviewState'; import { render } from '@testing-library/react'; +import { ParsedTemplateSchema } from './useTemplateSchema'; describe('ReviewState', () => { it('should render the text as normal with no options', () => { @@ -38,7 +39,7 @@ describe('ReviewState', () => { test: 'bob', }; - const schemas = [ + const schemas: ParsedTemplateSchema[] = [ { mergedSchema: { type: 'object', @@ -49,6 +50,10 @@ describe('ReviewState', () => { }, }, }, + schema: {}, + title: 'test', + uiSchema: {}, + description: 'asd', }, ]; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx index 90901f22c5..f605e9c114 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx @@ -47,17 +47,17 @@ describe('Stepper', () => { title: 'React JSON Schema Form Test', }; - const { getByText } = await renderInTestApp( + const { getByRole } = await renderInTestApp( , ); - expect(getByText('Next')).toBeInTheDocument(); + expect(getByRole('button', { name: 'Next' })).toBeInTheDocument(); await act(async () => { - await fireEvent.click(getByText('Next')); + await fireEvent.click(getByRole('button', { name: 'Next' })); }); - expect(getByText('Review')).toBeInTheDocument(); + expect(getByRole('button', { name: 'Review' })).toBeInTheDocument(); }); it('should remember the state of the form when cycling through the pages', async () => { @@ -87,7 +87,7 @@ describe('Stepper', () => { title: 'React JSON Schema Form Test', }; - const { getByRole, getByText } = await renderInTestApp( + const { getByRole } = await renderInTestApp( , ); @@ -96,11 +96,11 @@ describe('Stepper', () => { }); await act(async () => { - await fireEvent.click(getByText('Next')); + await fireEvent.click(getByRole('button', { name: 'Next' })); }); await act(async () => { - await fireEvent.click(getByText('Back')); + await fireEvent.click(getByRole('button', { name: 'Back' })); }); expect(getByRole('textbox', { name: 'name' })).toHaveValue( diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index 80926e5909..be4a300755 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -75,8 +75,7 @@ export const Stepper = (props: StepperProps) => { }, [props.extensions]); const validation = useMemo(() => { - const { mergedSchema } = steps[activeStep] ?? {}; - return createAsyncValidators(mergedSchema, validators, { + return createAsyncValidators(steps[activeStep].mergedSchema, validators, { apiHolder, }); }, [steps, activeStep, validators, apiHolder]); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts index dc23a8a49c..d0d8590a7b 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts @@ -28,7 +28,7 @@ export interface ParsedTemplateSchema { } export const useTemplateSchema = ( manifest: TemplateParameterSchema, -): { steps: ParsedTemplateSchema } => { +): { steps: ParsedTemplateSchema[] } => { const featureFlags = useApi(featureFlagsApiRef); const steps = manifest.steps.map(({ title, description, schema }) => ({ title, From b5986929b79999a96fcf6e486989401039a73d2c Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 19 Aug 2022 13:28:09 +0200 Subject: [PATCH 3/6] chore: added in the ability to customise the review step Signed-off-by: blam --- .../Stepper/ReviewState.test.tsx | 74 +++++++++++++++++++ .../Stepper/ReviewState.tsx | 10 +-- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx index d603213c19..2b2e706102 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.test.tsx @@ -63,4 +63,78 @@ describe('ReviewState', () => { expect(getByRole('row', { name: 'Name ******' })).toBeInTheDocument(); }); + + it('should hide from review if show is not set', async () => { + const formState = { + name: 'John Doe', + test: 'bob', + }; + + const schemas: ParsedTemplateSchema[] = [ + { + mergedSchema: { + type: 'object', + properties: { + name: { + type: 'string', + 'ui:widget': 'password', + 'ui:backstage': { + review: { + show: false, + }, + }, + }, + }, + }, + schema: {}, + title: 'test', + uiSchema: {}, + description: 'asd', + }, + ]; + + const { queryByRole } = render( + , + ); + + expect( + await queryByRole('row', { name: 'Name ******' }), + ).not.toBeInTheDocument(); + }); + + it('should allow for masking an option with a set text', () => { + const formState = { + name: 'John Doe', + test: 'bob', + }; + + const schemas: ParsedTemplateSchema[] = [ + { + mergedSchema: { + type: 'object', + properties: { + name: { + type: 'string', + 'ui:widget': 'password', + 'ui:backstage': { + review: { + mask: 'lols', + }, + }, + }, + }, + }, + schema: {}, + title: 'test', + uiSchema: {}, + description: 'asd', + }, + ]; + + const { getByRole } = render( + , + ); + + expect(getByRole('row', { name: 'Name lols' })).toBeInTheDocument(); + }); }); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx index 4889b24483..f39a26622a 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/ReviewState.tsx @@ -34,10 +34,6 @@ export const ReviewState = (props: ReviewStateProps) => { props.formState, ); if (definitionInSchema) { - if (definitionInSchema['ui:widget'] === 'password') { - return [key, '******']; - } - const backstageReviewOptions = definitionInSchema['ui:backstage']?.review; @@ -45,10 +41,14 @@ export const ReviewState = (props: ReviewStateProps) => { if (backstageReviewOptions.mask) { return [key, backstageReviewOptions.mask]; } - if (!backstageReviewOptions.show) { + if (backstageReviewOptions.show === false) { return []; } } + + if (definitionInSchema['ui:widget'] === 'password') { + return [key, '******']; + } } } return [key, value]; From d558c5bf09e6b89e3b22e6528e63553cae662a04 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 19 Aug 2022 13:46:24 +0200 Subject: [PATCH 4/6] chore: working some more on the review Signed-off-by: blam --- .../TemplateWizardPage/Stepper/Stepper.tsx | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index be4a300755..f5656092c1 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -35,6 +35,7 @@ const useStyles = makeStyles(theme => ({ backButton: { marginRight: theme.spacing(1), }, + footer: { display: 'flex', flexDirection: 'row', @@ -75,7 +76,7 @@ export const Stepper = (props: StepperProps) => { }, [props.extensions]); const validation = useMemo(() => { - return createAsyncValidators(steps[activeStep].mergedSchema, validators, { + return createAsyncValidators(steps[activeStep]?.mergedSchema, validators, { apiHolder, }); }, [steps, activeStep, validators, apiHolder]); @@ -104,6 +105,10 @@ export const Stepper = (props: StepperProps) => { setFormState(current => ({ ...current, ...formData })); }; + const handleCreate = () => { + // TODO(blam): Create the template in a modal with the ability to view the logs etc. + }; + return ( <> @@ -141,7 +146,21 @@ export const Stepper = (props: StepperProps) => { ) : ( - + <> + +
+ + +
+ )} From 192d856495bec8b387d371a0bd8717bb24e7837e Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 19 Aug 2022 13:55:09 +0200 Subject: [PATCH 5/6] chore: added changeset : Signed-off-by: blam --- .changeset/two-planets-provide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/two-planets-provide.md diff --git a/.changeset/two-planets-provide.md b/.changeset/two-planets-provide.md new file mode 100644 index 0000000000..d794a90dbf --- /dev/null +++ b/.changeset/two-planets-provide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': major +--- + +Implementing review step for scaffolder/next From ddb3d6bc6e5fe744c8c22115ba207badfe979a05 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Fri, 19 Aug 2022 17:34:06 +0200 Subject: [PATCH 6/6] Update two-planets-provide.md Signed-off-by: Ben Lambert --- .changeset/two-planets-provide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/two-planets-provide.md b/.changeset/two-planets-provide.md index d794a90dbf..920eebc01d 100644 --- a/.changeset/two-planets-provide.md +++ b/.changeset/two-planets-provide.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder': major +'@backstage/plugin-scaffolder': minor --- -Implementing review step for scaffolder/next +Implementing review step for the scaffolder under `create/next`