From ac96bc7975e611caaca49d2425943a08ffa195c9 Mon Sep 17 00:00:00 2001 From: chicoribas Date: Fri, 9 Jul 2021 10:22:42 -0300 Subject: [PATCH 01/11] Add ui:options to mask and hide Signed-off-by: chicoribas --- .../MultistepJsonForm.test.tsx | 88 +++++++++++++++++++ .../MultistepJsonForm/MultistepJsonForm.tsx | 68 +++++++++++++- 2 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx new file mode 100644 index 0000000000..261892c4ac --- /dev/null +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx @@ -0,0 +1,88 @@ +/* + * 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 { getReviewData } from './MultistepJsonForm'; + +describe('MultistepJsonForm', () => { + const formDataMock = { + password: 'password', + masked: 'Some info to mask', + open: 'Some open info', + hidden: 'Some info to hide', + 'other-open': 'Other open info', + }; + + const stepsMock = [ + { + title: 'The test template', + schema: { + title: 'The test template', + properties: { + password: { + title: 'Password', + type: 'string', + 'ui:widget': 'password', + }, + masked: { + title: 'Masked', + type: 'string', + 'ui:options': { + review: { + show: true, + mask: '******', + }, + }, + }, + open: { + title: 'Open info', + type: 'string', + }, + }, + }, + }, + { + title: 'Other fields', + schema: { + title: 'Other fields', + properties: { + hidden: { + title: 'Hidden', + type: 'string', + 'ui:options': { + review: { + show: false, + }, + }, + }, + 'other-open': { + title: 'Other Open Info', + type: 'string', + }, + }, + }, + }, + ]; + + test('Fields are defined to be hidden or masked', () => { + const reviewData = getReviewData(formDataMock, stepsMock); + + expect(reviewData.password).toBe('******'); + expect(reviewData.masked).toBe('******'); + expect(reviewData.open).toBe('Some open info'); + expect(reviewData.hidden).toBeUndefined(); + expect(reviewData['other-open']).toBe('Other open info'); + }); +}); diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 0fc998f82e..676d3562e1 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -24,7 +24,7 @@ import { Stepper, Typography, } from '@material-ui/core'; -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, { useState } from 'react'; import { transformSchemaToProps } from './schema'; @@ -49,6 +49,62 @@ type Props = { fields?: FormProps['fields']; }; +export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { + const uiSchemas: Array = []; + steps.forEach(step => { + if (!step.schema || !step.schema.properties) return; + + const schemaProps = step.schema.properties as JsonObject; + for (const key in schemaProps) { + if (Object.prototype.hasOwnProperty.call(schemaProps, 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 (Object.prototype.hasOwnProperty.call(formData, 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:options'] || !uiSchema['ui:options'].review) { + reviewData[key] = formData[key]; + continue; + } + + const review = uiSchema['ui:options'].review as JsonObject; + if (!review.show) { + continue; + } + + if (review.mask) { + reviewData[key] = review.mask; + continue; + } + reviewData[key] = formData[key]; + } + } + + return reviewData; +} + export const MultistepJsonForm = ({ steps, formData, @@ -59,13 +115,19 @@ export const MultistepJsonForm = ({ widgets, }: Props) => { const [activeStep, setActiveStep] = useState(0); + const [reviewData, setReviewData] = useState({}); const handleReset = () => { setActiveStep(0); onReset(); }; - const handleNext = () => + const handleNext = () => { setActiveStep(Math.min(activeStep + 1, steps.length)); + + if (Math.min(activeStep + 1, steps.length) === steps.length) { + setReviewData(getReviewData(formData, steps)); + } + }; const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0)); return ( @@ -113,7 +175,7 @@ export const MultistepJsonForm = ({ Review and create - + From 4f9eeacc7183cb348cc0c7b4141cfd9b76dd5a1d Mon Sep 17 00:00:00 2001 From: chicoribas Date: Fri, 9 Jul 2021 10:24:13 -0300 Subject: [PATCH 02/11] Documentation Signed-off-by: chicoribas --- .../software-templates/writing-templates.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 7b475f2fa1..b8a3bc62eb 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -227,6 +227,33 @@ spec: inputType: tel ``` +#### Hide or mask sensitive data on Review step + +Sometimes, specially in custom fields, you collect some data on Create form that +must note be shown to the user on Review step. To hide or mask this data, you +can set some properties of `ui:options`: + +```yaml +- title: Hide or mask values + properties: + password: + title: Password + type: string + ui:widget: password # will print '******' as value for property 'password' on Review Step + masked: + title: Masked + type: string + ui:options: + review: + mask: '' # will print '' as value for property 'Masked' on Review Step + hidden: + title: Hidden + type: string + ui:options: + review: + show: false # wont print any info about 'hidden' property on Review Step +``` + #### The Repository Picker So in order to make working with repository providers easier, we've built a From 73951fc44be1686134608cc0d351675f92e17389 Mon Sep 17 00:00:00 2001 From: chicoribas Date: Fri, 9 Jul 2021 11:43:36 -0300 Subject: [PATCH 03/11] Changeset Signed-off-by: chicoribas --- .changeset/fifty-frogs-prove.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fifty-frogs-prove.md diff --git a/.changeset/fifty-frogs-prove.md b/.changeset/fifty-frogs-prove.md new file mode 100644 index 0000000000..91bb8b6e72 --- /dev/null +++ b/.changeset/fifty-frogs-prove.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Add options to mask or hide values on review state From d2e4012f455c90fc97e5cc53e4d658c6630e60c5 Mon Sep 17 00:00:00 2001 From: chicoribas Date: Fri, 9 Jul 2021 14:34:33 -0300 Subject: [PATCH 04/11] Change ui:options to ui:backstage Signed-off-by: chicoribas --- docs/features/software-templates/writing-templates.md | 6 +++--- .../components/MultistepJsonForm/MultistepJsonForm.test.tsx | 4 ++-- .../src/components/MultistepJsonForm/MultistepJsonForm.tsx | 5 ++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index b8a3bc62eb..0246a0bfe3 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -231,7 +231,7 @@ spec: Sometimes, specially in custom fields, you collect some data on Create form that must note be shown to the user on Review step. To hide or mask this data, you -can set some properties of `ui:options`: +can use `ui:widget: password` or set some properties of `ui:backstage`: ```yaml - title: Hide or mask values @@ -243,13 +243,13 @@ can set some properties of `ui:options`: masked: title: Masked type: string - ui:options: + ui:backstage: review: mask: '' # will print '' as value for property 'Masked' on Review Step hidden: title: Hidden type: string - ui:options: + ui:backstage: review: show: false # wont print any info about 'hidden' property on Review Step ``` diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx index 261892c4ac..3ca25bd6fd 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx @@ -39,7 +39,7 @@ describe('MultistepJsonForm', () => { masked: { title: 'Masked', type: 'string', - 'ui:options': { + 'ui:backstage': { review: { show: true, mask: '******', @@ -61,7 +61,7 @@ describe('MultistepJsonForm', () => { hidden: { title: 'Hidden', type: 'string', - 'ui:options': { + 'ui:backstage': { review: { show: false, }, diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 676d3562e1..4e32f88231 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -63,7 +63,6 @@ export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { } } }); - return uiSchemas; } @@ -84,12 +83,12 @@ export function getReviewData(formData: Record, steps: Step[]) { continue; } - if (!uiSchema['ui:options'] || !uiSchema['ui:options'].review) { + if (!uiSchema['ui:backstage'] || !uiSchema['ui:backstage'].review) { reviewData[key] = formData[key]; continue; } - const review = uiSchema['ui:options'].review as JsonObject; + const review = uiSchema['ui:backstage'].review as JsonObject; if (!review.show) { continue; } From c76502dc9372f88f6de1e20474da0286fd5dfa11 Mon Sep 17 00:00:00 2001 From: Francisco Ribas Date: Mon, 12 Jul 2021 11:19:09 -0300 Subject: [PATCH 05/11] Update docs/features/software-templates/writing-templates.md Spelling fix Co-authored-by: Andrea Falzetti Signed-off-by: chicoribas --- docs/features/software-templates/writing-templates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 0246a0bfe3..cf13c4ed19 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -230,7 +230,7 @@ spec: #### Hide or mask sensitive data on Review step Sometimes, specially in custom fields, you collect some data on Create form that -must note be shown to the user on Review step. To hide or mask this data, you +must not be shown to the user on Review step. To hide or mask this data, you can use `ui:widget: password` or set some properties of `ui:backstage`: ```yaml From fe103911805d037ec2507f65a8f98e1374469756 Mon Sep 17 00:00:00 2001 From: Francisco Ribas Date: Mon, 12 Jul 2021 11:19:22 -0300 Subject: [PATCH 06/11] Update plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx Co-authored-by: Andrea Falzetti Signed-off-by: chicoribas --- .../src/components/MultistepJsonForm/MultistepJsonForm.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx index 3ca25bd6fd..5f7091832f 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 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. From 49d814226c8be5798c09a2c756b12c1e35ffa495 Mon Sep 17 00:00:00 2001 From: Francisco Ribas Date: Mon, 12 Jul 2021 11:21:54 -0300 Subject: [PATCH 07/11] Update plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx Co-authored-by: Andrea Falzetti Signed-off-by: chicoribas --- .../src/components/MultistepJsonForm/MultistepJsonForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 4e32f88231..a8580482b8 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -52,7 +52,7 @@ type Props = { export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { const uiSchemas: Array = []; steps.forEach(step => { - if (!step.schema || !step.schema.properties) return; + if (!step.schema || !step.schema.properties) return []; const schemaProps = step.schema.properties as JsonObject; for (const key in schemaProps) { From 5e10eb26c7df64fff164d27b1a9c762bc65d0f9c Mon Sep 17 00:00:00 2001 From: chicoribas Date: Mon, 12 Jul 2021 12:09:10 -0300 Subject: [PATCH 08/11] Changeset from minor to patch Signed-off-by: chicoribas --- .changeset/fifty-frogs-prove.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fifty-frogs-prove.md b/.changeset/fifty-frogs-prove.md index 91bb8b6e72..a3296499cf 100644 --- a/.changeset/fifty-frogs-prove.md +++ b/.changeset/fifty-frogs-prove.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder': minor +'@backstage/plugin-scaffolder': patch --- Add options to mask or hide values on review state From 6b5c0c3fcf1ddbf2fdc345515bede50fdd7cac61 Mon Sep 17 00:00:00 2001 From: chicoribas Date: Tue, 13 Jul 2021 20:05:49 -0300 Subject: [PATCH 09/11] Review fix Signed-off-by: chicoribas --- .../MultistepJsonForm/MultistepJsonForm.tsx | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index a8580482b8..c5c33d866e 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -52,11 +52,9 @@ type Props = { export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { const uiSchemas: Array = []; steps.forEach(step => { - if (!step.schema || !step.schema.properties) return []; - const schemaProps = step.schema.properties as JsonObject; for (const key in schemaProps) { - if (Object.prototype.hasOwnProperty.call(schemaProps, key)) { + if (schemaProps.hasOwnProperty.call(schemaProps, key)) { const uiSchema = schemaProps[key] as UiSchema; uiSchema.name = key; uiSchemas.push(uiSchema); @@ -70,7 +68,7 @@ export function getReviewData(formData: Record, steps: Step[]) { const uiSchemas = getUiSchemasFromSteps(steps); const reviewData: Record = {}; for (const key in formData) { - if (Object.prototype.hasOwnProperty.call(formData, key)) { + if (formData.hasOwnProperty.call(formData, key)) { const uiSchema = uiSchemas.find(us => us.name === key); if (!uiSchema) { @@ -114,7 +112,6 @@ export const MultistepJsonForm = ({ widgets, }: Props) => { const [activeStep, setActiveStep] = useState(0); - const [reviewData, setReviewData] = useState({}); const handleReset = () => { setActiveStep(0); @@ -122,10 +119,6 @@ export const MultistepJsonForm = ({ }; const handleNext = () => { setActiveStep(Math.min(activeStep + 1, steps.length)); - - if (Math.min(activeStep + 1, steps.length) === steps.length) { - setReviewData(getReviewData(formData, steps)); - } }; const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0)); @@ -174,7 +167,10 @@ export const MultistepJsonForm = ({ Review and create - + From 6b23a6052f0453dd88b4c03394ec1ab35d9b94b1 Mon Sep 17 00:00:00 2001 From: chicoribas Date: Tue, 13 Jul 2021 21:23:02 -0300 Subject: [PATCH 10/11] Prettier Signed-off-by: chicoribas --- docs/features/software-templates/writing-templates.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index cf13c4ed19..02bb698f64 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -230,8 +230,8 @@ spec: #### Hide or mask sensitive data on Review step Sometimes, specially in custom fields, you collect some data on Create form that -must not be shown to the user on Review step. To hide or mask this data, you -can use `ui:widget: password` or set some properties of `ui:backstage`: +must not be shown to the user on Review step. To hide or mask this data, you can +use `ui:widget: password` or set some properties of `ui:backstage`: ```yaml - title: Hide or mask values From c87f271a52d2032dbf1333c9b12ae739d15ef1d7 Mon Sep 17 00:00:00 2001 From: chicoribas Date: Wed, 14 Jul 2021 09:23:55 -0300 Subject: [PATCH 11/11] Code Review Signed-off-by: chicoribas --- .../src/components/MultistepJsonForm/MultistepJsonForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index c5c33d866e..cdcc22da29 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -54,7 +54,7 @@ export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { steps.forEach(step => { const schemaProps = step.schema.properties as JsonObject; for (const key in schemaProps) { - if (schemaProps.hasOwnProperty.call(schemaProps, key)) { + if (schemaProps.hasOwnProperty(key)) { const uiSchema = schemaProps[key] as UiSchema; uiSchema.name = key; uiSchemas.push(uiSchema); @@ -68,7 +68,7 @@ export function getReviewData(formData: Record, steps: Step[]) { const uiSchemas = getUiSchemasFromSteps(steps); const reviewData: Record = {}; for (const key in formData) { - if (formData.hasOwnProperty.call(formData, key)) { + if (formData.hasOwnProperty(key)) { const uiSchema = uiSchemas.find(us => us.name === key); if (!uiSchema) {