diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 5b41163ae4..70071721fa 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -24,7 +24,7 @@ import { LinearProgress, } from '@material-ui/core'; import { type IChangeEvent } from '@rjsf/core'; -import { ErrorSchema } from '@rjsf/utils'; +import { ErrorSchema, ValidatorType } from '@rjsf/utils'; import React, { useCallback, useMemo, @@ -49,6 +49,7 @@ import { LayoutOptions, FieldExtensionOptions, FormProps, + useTemplateSecrets, } from '@backstage/plugin-scaffolder-react'; import { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; @@ -102,6 +103,7 @@ export const Stepper = (stepperProps: StepperProps) => { reviewButtonText = 'Review', } = components; const analytics = useAnalytics(); + const { secrets } = useTemplateSecrets(); const { presentation, steps } = useTemplateSchema(props.manifest); const apiHolder = useApiHolder(); const [activeStep, setActiveStep] = useState(0); @@ -111,6 +113,31 @@ export const Stepper = (stepperProps: StepperProps) => { const [errors, setErrors] = useState(); const styles = useStyles(); + const stringifiedSecrets = JSON.stringify(secrets); + + // Because secrets can be defined in the schema, we need to make sure that they + // are included in the validation process. So we merge the secrets and the formData + // together in validation. + const customValidator = useMemo( + () => ({ + isValid: (schema, formData, rootSchema) => + validator.isValid(schema, { ...formData, ...secrets }, rootSchema), + rawValidation: (schema, formData) => + validator.rawValidation(schema, { ...formData, ...secrets }), + validateFormData: (formData, schema, customFormats, transformErrors) => + validator.validateFormData( + { ...formData, ...secrets }, + schema, + customFormats, + transformErrors, + ), + // @deprecated + toErrorList: validator.toErrorList, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [secrets, stringifiedSecrets], + ); + const extensions = useMemo(() => { return Object.fromEntries( props.extensions.map(({ name, component }) => [name, component]), @@ -220,7 +247,7 @@ export const Stepper = (stepperProps: StepperProps) => { {/* eslint-disable-next-line no-nested-ternary */} {activeStep < steps.length ? (
', () => { 'ui:field': 'Secret', }} fields={{ - Secret: Secret, + Secret: SecretInput, }} onSubmit={onSubmit} /> @@ -86,7 +86,7 @@ describe('', () => { }, }} fields={{ - Secret: Secret, + Secret: SecretInput, }} onSubmit={onSubmit} /> diff --git a/plugins/scaffolder/src/components/fields/Secret/Secret.tsx b/plugins/scaffolder/src/components/fields/SecretInput/SecretInput.tsx similarity index 70% rename from plugins/scaffolder/src/components/fields/Secret/Secret.tsx rename to plugins/scaffolder/src/components/fields/SecretInput/SecretInput.tsx index 58dd18476f..d68216650b 100644 --- a/plugins/scaffolder/src/components/fields/Secret/Secret.tsx +++ b/plugins/scaffolder/src/components/fields/SecretInput/SecretInput.tsx @@ -19,10 +19,7 @@ import { ScaffolderRJSFFieldProps } from '@backstage/plugin-scaffolder-react'; import { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha'; import { Input, InputLabel } from '@material-ui/core'; -/** - * @public - */ -export const Secret = (props: ScaffolderRJSFFieldProps) => { +export const SecretInput = (props: ScaffolderRJSFFieldProps) => { const { setSecrets, secrets } = useTemplateSecrets(); const { name, @@ -30,7 +27,6 @@ export const Secret = (props: ScaffolderRJSFFieldProps) => { rawErrors, disabled, errors, - onChange, required, } = props; @@ -46,18 +42,7 @@ export const Secret = (props: ScaffolderRJSFFieldProps) => { { - // TODO(blam): this is a bit of a hack. We need to to probably figure out - // how to provide our own validator that can filter out the secrets from the - // jsonschema, or merge the secrets with the formData for validation? - // Makes the review step a little cleaner with this though. - onChange( - Array(e.target?.value.length ?? 0) - .fill('*') - .join(''), - ); - setSecrets({ [name]: e.target?.value }); - }} + onChange={e => setSecrets({ [name]: e.target?.value })} value={secrets[name] ?? ''} type="password" autoComplete="off" diff --git a/plugins/scaffolder/src/components/fields/Secret/index.tsx b/plugins/scaffolder/src/components/fields/SecretInput/index.tsx similarity index 95% rename from plugins/scaffolder/src/components/fields/Secret/index.tsx rename to plugins/scaffolder/src/components/fields/SecretInput/index.tsx index e28248ef7d..859a04a4d3 100644 --- a/plugins/scaffolder/src/components/fields/Secret/index.tsx +++ b/plugins/scaffolder/src/components/fields/SecretInput/index.tsx @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './Secret'; +export * from './SecretInput'; diff --git a/plugins/scaffolder/src/components/fields/index.ts b/plugins/scaffolder/src/components/fields/index.ts index e29a4d791c..7f119f27be 100644 --- a/plugins/scaffolder/src/components/fields/index.ts +++ b/plugins/scaffolder/src/components/fields/index.ts @@ -19,6 +19,5 @@ export * from './RepoUrlPicker'; export * from './OwnedEntityPicker'; export * from './EntityTagsPicker'; export * from './MyGroupsPicker'; -export * from './Secret'; export { type FieldSchema, makeFieldSchemaFromZod } from './utils'; diff --git a/plugins/scaffolder/src/extensions/default.ts b/plugins/scaffolder/src/extensions/default.ts index ef4a701e86..2e4b7032de 100644 --- a/plugins/scaffolder/src/extensions/default.ts +++ b/plugins/scaffolder/src/extensions/default.ts @@ -43,7 +43,8 @@ import { MyGroupsPicker, MyGroupsPickerSchema, } from '../components/fields/MyGroupsPicker/MyGroupsPicker'; -import { Secret } from '../components'; + +import { SecretInput } from '../components/fields/SecretInput'; export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [ { @@ -84,7 +85,7 @@ export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [ schema: MyGroupsPickerSchema, }, { - component: Secret, + component: SecretInput, name: 'Secret', }, ];