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,