From ddd1c3308d4d838a78342d5167365a5c6e054d57 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Wed, 5 Oct 2022 13:58:43 -0400 Subject: [PATCH 1/4] feat(scaffolder): implement custom field explorer Signed-off-by: Phil Kuang --- .changeset/soft-nails-arrive.md | 5 + plugins/scaffolder/api-report.md | 332 ++++++++++++++---- plugins/scaffolder/package.json | 4 +- .../CustomFieldExplorer.tsx | 201 +++++++++++ .../TemplateEditorIntro.tsx | 19 +- .../TemplateEditorPage/TemplateEditorPage.tsx | 13 + .../EntityNamePicker/EntityNamePicker.tsx | 5 +- .../fields/EntityNamePicker/schema.ts | 30 ++ .../fields/EntityPicker/EntityPicker.tsx | 19 +- .../components/fields/EntityPicker/index.ts | 5 +- .../components/fields/EntityPicker/schema.ts | 63 ++++ .../EntityTagsPicker/EntityTagsPicker.tsx | 21 +- .../fields/EntityTagsPicker/index.ts | 5 +- .../fields/EntityTagsPicker/schema.ts | 56 +++ .../OwnedEntityPicker/OwnedEntityPicker.tsx | 23 +- .../fields/OwnedEntityPicker/index.ts | 5 +- .../fields/OwnedEntityPicker/schema.ts | 67 ++++ .../fields/OwnerPicker/OwnerPicker.tsx | 18 +- .../components/fields/OwnerPicker/index.ts | 5 +- .../components/fields/OwnerPicker/schema.ts | 60 ++++ .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 29 +- .../components/fields/RepoUrlPicker/index.ts | 5 +- .../components/fields/RepoUrlPicker/schema.ts | 101 ++++++ plugins/scaffolder/src/extensions/default.ts | 36 +- plugins/scaffolder/src/extensions/index.tsx | 2 + plugins/scaffolder/src/extensions/types.ts | 16 +- plugins/scaffolder/src/index.ts | 1 + plugins/scaffolder/src/plugin.ts | 36 +- yarn.lock | 2 + 29 files changed, 1026 insertions(+), 158 deletions(-) create mode 100644 .changeset/soft-nails-arrive.md create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/CustomFieldExplorer.tsx create mode 100644 plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts create mode 100644 plugins/scaffolder/src/components/fields/EntityPicker/schema.ts create mode 100644 plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts create mode 100644 plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts create mode 100644 plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts create mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts diff --git a/.changeset/soft-nails-arrive.md b/.changeset/soft-nails-arrive.md new file mode 100644 index 0000000000..e9c0a40a05 --- /dev/null +++ b/.changeset/soft-nails-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Implement Custom Field Explorer to view and play around with available installed custom field extensions diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 158c75e5bd..69512e7af0 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -35,6 +35,7 @@ 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 { z } from 'zod'; // @alpha export function createNextScaffolderFieldExtension< @@ -57,6 +58,12 @@ export function createScaffolderLayout( options: LayoutOptions, ): Extension>; +// @public +export type CustomFieldExtensionSchema = { + uiOptions?: JSONSchema7; + returnValue?: JSONSchema7; +}; + // @public export type CustomFieldValidator = ( data: TFieldReturnValue, @@ -75,36 +82,78 @@ export const EntityNamePickerFieldExtension: FieldExtensionComponent< // @public export const EntityPickerFieldExtension: FieldExtensionComponent< string, - EntityPickerUiOptions + { + defaultKind?: string | undefined; + defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; + } >; // @public -export interface EntityPickerUiOptions { - // (undocumented) - allowArbitraryValues?: boolean; - // (undocumented) - allowedKinds?: string[]; - // (undocumented) - defaultKind?: string; - // (undocumented) - defaultNamespace?: string | false; -} +export type EntityPickerUiOptions = z.infer; + +// @public (undocumented) +export const EntityPickerUiOptionsSchema: z.ZodObject< + { + allowedKinds: z.ZodOptional>; + defaultKind: z.ZodOptional; + allowArbitraryValues: z.ZodOptional; + defaultNamespace: z.ZodOptional< + z.ZodUnion<[z.ZodString, z.ZodLiteral]> + >; + }, + 'strip', + z.ZodTypeAny, + { + defaultKind?: string | undefined; + defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; + }, + { + defaultKind?: string | undefined; + defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; + } +>; // @public export const EntityTagsPickerFieldExtension: FieldExtensionComponent< string[], - EntityTagsPickerUiOptions + { + showCounts?: boolean | undefined; + kinds?: string[] | undefined; + helperText?: string | undefined; + } >; // @public -export interface EntityTagsPickerUiOptions { - // (undocumented) - helperText?: string; - // (undocumented) - kinds?: string[]; - // (undocumented) - showCounts?: boolean; -} +export type EntityTagsPickerUiOptions = z.infer< + typeof EntityTagsPickerUiOptionsSchema +>; + +// @public (undocumented) +export const EntityTagsPickerUiOptionsSchema: z.ZodObject< + { + kinds: z.ZodOptional>; + showCounts: z.ZodOptional; + helperText: z.ZodOptional; + }, + 'strip', + z.ZodTypeAny, + { + showCounts?: boolean | undefined; + kinds?: string[] | undefined; + helperText?: string | undefined; + }, + { + showCounts?: boolean | undefined; + kinds?: string[] | undefined; + helperText?: string | undefined; + } +>; // @public export type FieldExtensionComponent<_TReturnValue, _TInputProps> = () => null; @@ -130,6 +179,7 @@ export type FieldExtensionOptions< props: FieldExtensionComponentProps, ) => JSX.Element | null; validation?: CustomFieldValidator; + schema?: CustomFieldExtensionSchema; }; // @public @@ -199,6 +249,7 @@ export type NextFieldExtensionOptions< props: NextFieldExtensionComponentProps, ) => JSX.Element | null; validation?: NextCustomFieldValidator; + schema?: CustomFieldExtensionSchema; }; // @alpha (undocumented) @@ -228,36 +279,80 @@ export const nextSelectedTemplateRouteRef: SubRouteRef< // @public export const OwnedEntityPickerFieldExtension: FieldExtensionComponent< string, - OwnedEntityPickerUiOptions + { + defaultKind?: string | undefined; + defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; + } >; // @public -export interface OwnedEntityPickerUiOptions { - // (undocumented) - allowArbitraryValues?: boolean; - // (undocumented) - allowedKinds?: string[]; - // (undocumented) - defaultKind?: string; - // (undocumented) - defaultNamespace?: string | false; -} +export type OwnedEntityPickerUiOptions = z.infer< + typeof OwnedEntityPickerUiOptionsSchema +>; + +// @public (undocumented) +export const OwnedEntityPickerUiOptionsSchema: z.ZodObject< + { + allowedKinds: z.ZodOptional>; + defaultKind: z.ZodOptional; + allowArbitraryValues: z.ZodOptional; + defaultNamespace: z.ZodOptional< + z.ZodUnion<[z.ZodString, z.ZodLiteral]> + >; + }, + 'strip', + z.ZodTypeAny, + { + defaultKind?: string | undefined; + defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; + }, + { + defaultKind?: string | undefined; + defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; + } +>; // @public export const OwnerPickerFieldExtension: FieldExtensionComponent< string, - OwnerPickerUiOptions + { + defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; + } >; // @public -export interface OwnerPickerUiOptions { - // (undocumented) - allowArbitraryValues?: boolean; - // (undocumented) - allowedKinds?: string[]; - // (undocumented) - defaultNamespace?: string | false; -} +export type OwnerPickerUiOptions = z.infer; + +// @public (undocumented) +export const OwnerPickerUiOptionsSchema: z.ZodObject< + { + allowedKinds: z.ZodOptional>>; + allowArbitraryValues: z.ZodOptional; + defaultNamespace: z.ZodOptional< + z.ZodUnion<[z.ZodString, z.ZodLiteral]> + >; + }, + 'strip', + z.ZodTypeAny, + { + defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; + }, + { + defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; + } +>; // @public export const repoPickerValidation: ( @@ -271,31 +366,144 @@ export const repoPickerValidation: ( // @public export const RepoUrlPickerFieldExtension: FieldExtensionComponent< string, - RepoUrlPickerUiOptions + { + allowedOwners?: string[] | undefined; + allowedOrganizations?: string[] | undefined; + allowedRepos?: string[] | undefined; + allowedHosts?: string[] | undefined; + requestUserCredentials?: + | { + additionalScopes?: + | { + azure?: string[] | undefined; + github?: string[] | undefined; + gitlab?: string[] | undefined; + bitbucket?: string[] | undefined; + gerrit?: string[] | undefined; + } + | undefined; + secretsKey: string; + } + | undefined; + } >; // @public -export interface RepoUrlPickerUiOptions { - // (undocumented) - allowedHosts?: string[]; - // (undocumented) - allowedOrganizations?: string[]; - // (undocumented) - allowedOwners?: string[]; - // (undocumented) - allowedRepos?: string[]; - // (undocumented) - requestUserCredentials?: { - secretsKey: string; - additionalScopes?: { - gerrit?: string[]; - github?: string[]; - gitlab?: string[]; - bitbucket?: string[]; - azure?: string[]; - }; - }; -} +export type RepoUrlPickerUiOptions = z.infer< + typeof RepoUrlPickerUiOptionsSchema +>; + +// @public (undocumented) +export const RepoUrlPickerUiOptionsSchema: z.ZodObject< + { + allowedHosts: z.ZodOptional>; + allowedOrganizations: z.ZodOptional>; + allowedOwners: z.ZodOptional>; + allowedRepos: z.ZodOptional>; + requestUserCredentials: z.ZodOptional< + z.ZodObject< + { + secretsKey: z.ZodString; + additionalScopes: z.ZodOptional< + z.ZodObject< + { + gerrit: z.ZodOptional>; + github: z.ZodOptional>; + gitlab: z.ZodOptional>; + bitbucket: z.ZodOptional>; + azure: z.ZodOptional>; + }, + 'strip', + z.ZodTypeAny, + { + azure?: string[] | undefined; + github?: string[] | undefined; + gitlab?: string[] | undefined; + bitbucket?: string[] | undefined; + gerrit?: string[] | undefined; + }, + { + azure?: string[] | undefined; + github?: string[] | undefined; + gitlab?: string[] | undefined; + bitbucket?: string[] | undefined; + gerrit?: string[] | undefined; + } + > + >; + }, + 'strip', + z.ZodTypeAny, + { + additionalScopes?: + | { + azure?: string[] | undefined; + github?: string[] | undefined; + gitlab?: string[] | undefined; + bitbucket?: string[] | undefined; + gerrit?: string[] | undefined; + } + | undefined; + secretsKey: string; + }, + { + additionalScopes?: + | { + azure?: string[] | undefined; + github?: string[] | undefined; + gitlab?: string[] | undefined; + bitbucket?: string[] | undefined; + gerrit?: string[] | undefined; + } + | undefined; + secretsKey: string; + } + > + >; + }, + 'strip', + z.ZodTypeAny, + { + allowedOwners?: string[] | undefined; + allowedOrganizations?: string[] | undefined; + allowedRepos?: string[] | undefined; + allowedHosts?: string[] | undefined; + requestUserCredentials?: + | { + additionalScopes?: + | { + azure?: string[] | undefined; + github?: string[] | undefined; + gitlab?: string[] | undefined; + bitbucket?: string[] | undefined; + gerrit?: string[] | undefined; + } + | undefined; + secretsKey: string; + } + | undefined; + }, + { + allowedOwners?: string[] | undefined; + allowedOrganizations?: string[] | undefined; + allowedRepos?: string[] | undefined; + allowedHosts?: string[] | undefined; + requestUserCredentials?: + | { + additionalScopes?: + | { + azure?: string[] | undefined; + github?: string[] | undefined; + gitlab?: string[] | undefined; + bitbucket?: string[] | undefined; + gerrit?: string[] | undefined; + } + | undefined; + secretsKey: string; + } + | undefined; + } +>; // @public (undocumented) export const rootRouteRef: RouteRef; diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 214351e2b7..07ad33dd03 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -74,7 +74,9 @@ "react-use": "^17.2.4", "use-immer": "^0.7.0", "yaml": "^2.0.0", - "zen-observable": "^0.8.15" + "zen-observable": "^0.8.15", + "zod": "^3.11.6", + "zod-to-json-schema": "^3.18.1" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/CustomFieldExplorer.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/CustomFieldExplorer.tsx new file mode 100644 index 0000000000..3510968acd --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/CustomFieldExplorer.tsx @@ -0,0 +1,201 @@ +/* + * 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 { StreamLanguage } from '@codemirror/language'; +import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; +import { + Button, + Card, + CardContent, + CardHeader, + FormControl, + IconButton, + InputLabel, + makeStyles, + MenuItem, + Select, +} from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; +import { withTheme } from '@rjsf/core'; +import { Theme as MuiTheme } from '@rjsf/material-ui'; +import CodeMirror from '@uiw/react-codemirror'; +import React, { useCallback, useMemo, useState } from 'react'; +import yaml from 'yaml'; +import { FieldExtensionOptions } from '../../extensions'; +import * as fieldOverrides from '../MultistepJsonForm/FieldOverrides'; +import { TemplateEditorForm } from './TemplateEditorForm'; + +const Form = withTheme(MuiTheme); + +const useStyles = makeStyles(theme => ({ + root: { + gridArea: 'pageContent', + display: 'grid', + gridTemplateAreas: ` + "controls controls" + "fieldForm preview" + `, + gridTemplateRows: 'auto 1fr', + gridTemplateColumns: '1fr 1fr', + }, + controls: { + gridArea: 'controls', + display: 'flex', + flexFlow: 'row nowrap', + alignItems: 'center', + margin: theme.spacing(1), + }, + fieldForm: { + gridArea: 'fieldForm', + }, + preview: { + gridArea: 'preview', + }, +})); + +export const CustomFieldExplorer = ({ + customFieldExtensions = [], + onClose, +}: { + customFieldExtensions?: FieldExtensionOptions[]; + onClose?: () => void; +}) => { + const classes = useStyles(); + const fieldOptions = customFieldExtensions.filter(field => !!field.schema); + const [selectedField, setSelectedField] = useState(fieldOptions[0]); + const [fieldFormState, setFieldFormState] = useState({}); + const [formState, setFormState] = useState({}); + const [refreshKey, setRefreshKey] = useState(Date.now()); + const sampleFieldTemplate = useMemo( + () => + yaml.stringify({ + parameters: [ + { + title: `${selectedField.name} Example`, + properties: { + [selectedField.name]: { + type: selectedField.schema?.returnValue?.type, + 'ui:field': selectedField.name, + 'ui:options': fieldFormState, + }, + }, + }, + ], + }), + [fieldFormState, selectedField], + ); + + const fieldComponents = useMemo(() => { + return Object.fromEntries( + customFieldExtensions.map(({ name, component }) => [name, component]), + ); + }, [customFieldExtensions]); + + const handleSelectionChange = useCallback( + selection => { + setSelectedField(selection); + setFieldFormState({}); + setFormState({}); + }, + [setFieldFormState, setFormState, setSelectedField], + ); + + const handleFieldConfigChange = useCallback( + state => { + setFieldFormState(state); + setFormState({}); + // Force TemplateEditorForm to re-render since some fields + // may not be responsive to ui:option changes + setRefreshKey(Date.now()); + }, + [setFieldFormState, setRefreshKey], + ); + + return ( +
+
+ + + Choose Custom Field Extension + + + + + + + +
+
+ + + +
handleFieldConfigChange(e.formData)} + schema={selectedField.schema?.uiOptions || {}} + > + +
+
+
+
+
+ + + + + + + null} + /> +
+
+ ); +}; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorIntro.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorIntro.tsx index dc411ee965..61e6c46964 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorIntro.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorIntro.tsx @@ -44,7 +44,7 @@ const useStyles = makeStyles(theme => ({ interface EditorIntroProps { style?: JSX.IntrinsicElements['div']['style']; - onSelect?: (option: 'local' | 'form') => void; + onSelect?: (option: 'local' | 'form' | 'field-explorer') => void; } export function TemplateEditorIntro(props: EditorIntroProps) { @@ -104,6 +104,22 @@ export function TemplateEditorIntro(props: EditorIntroProps) { ); + const cardFieldExplorer = ( + + props.onSelect?.('field-explorer')}> + + + Custom Field Explorer + + + View and play around with available installed custom field + extensions. + + + + + ); + return (
@@ -121,6 +137,7 @@ export function TemplateEditorIntro(props: EditorIntroProps) { {supportsLoad && cardLoadLocal} {cardFormEditor} {!supportsLoad && cardLoadLocal} + {cardFieldExplorer}
); diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx index b539b6403b..8847b85373 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx @@ -19,6 +19,7 @@ import { TemplateDirectoryAccess, WebFileSystemAccess, } from '../../lib/filesystem'; +import { CustomFieldExplorer } from './CustomFieldExplorer'; import { TemplateEditorIntro } from './TemplateEditorIntro'; import { TemplateEditor } from './TemplateEditor'; import { TemplateFormPreviewer } from './TemplateFormPreviewer'; @@ -32,6 +33,9 @@ type Selection = } | { type: 'form'; + } + | { + type: 'field-explorer'; }; interface TemplateEditorPageProps { @@ -62,6 +66,13 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { layouts={props.layouts} /> ); + } else if (selection?.type === 'field-explorer') { + content = ( + setSelection(undefined)} + /> + ); } else { content = ( @@ -73,6 +84,8 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { .catch(() => {}); } else if (option === 'form') { setSelection({ type: 'form' }); + } else if (option === 'field-explorer') { + setSelection({ type: 'field-explorer' }); } }} /> diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx index 802135a863..eba05cfb92 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -15,13 +15,16 @@ */ import React from 'react'; import { FieldExtensionComponentProps } from '../../../extensions'; +import { EntityNamePickerReturnValue } from './schema'; import { TextField } from '@material-ui/core'; +export { EntityNamePickerSchema } from './schema'; + /** * EntityName Picker */ export const EntityNamePicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps, ) => { const { onChange, diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts new file mode 100644 index 0000000000..45aa4502ce --- /dev/null +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts @@ -0,0 +1,30 @@ +/* + * 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 { JSONSchema7 } from 'json-schema'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +const EntityNamePickerReturnValueSchema = z.string(); + +export type EntityNamePickerReturnValue = z.infer< + typeof EntityNamePickerReturnValueSchema +>; + +export const EntityNamePickerSchema = { + returnValue: zodToJsonSchema( + EntityNamePickerReturnValueSchema, + ) as JSONSchema7, +}; diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index e7883a4721..8fcc412d0a 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -24,19 +24,9 @@ import Autocomplete from '@material-ui/lab/Autocomplete'; import React, { useCallback, useEffect } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { FieldExtensionComponentProps } from '../../../extensions'; +import { EntityPickerReturnValue, EntityPickerUiOptions } from './schema'; -/** - * The input props that can be specified under `ui:options` for the - * `EntityPicker` field extension. - * - * @public - */ -export interface EntityPickerUiOptions { - allowedKinds?: string[]; - defaultKind?: string; - allowArbitraryValues?: boolean; - defaultNamespace?: string | false; -} +export { EntityPickerSchema } from './schema'; /** * The underlying component that is rendered in the form for the `EntityPicker` @@ -45,7 +35,10 @@ export interface EntityPickerUiOptions { * @public */ export const EntityPicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps< + EntityPickerReturnValue, + EntityPickerUiOptions + >, ) => { const { onChange, diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/index.ts b/plugins/scaffolder/src/components/fields/EntityPicker/index.ts index 891b5bef16..d1044e4e67 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/EntityPicker/index.ts @@ -13,4 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export type { EntityPickerUiOptions } from './EntityPicker'; +export { + type EntityPickerUiOptions, + EntityPickerUiOptionsSchema, +} from './schema'; diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts new file mode 100644 index 0000000000..5f7d66fc4f --- /dev/null +++ b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts @@ -0,0 +1,63 @@ +/* + * 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 { JSONSchema7 } from 'json-schema'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +/** + * @public + */ +export const EntityPickerUiOptionsSchema = z.object({ + allowedKinds: z + .array(z.string()) + .optional() + .describe('List of kinds of entities to derive options from'), + defaultKind: z + .string() + .optional() + .describe( + 'The default entity kind. Options of this kind will not be prefixed.', + ), + allowArbitraryValues: z + .boolean() + .optional() + .describe('Whether to allow arbitrary user input. Defaults to true'), + defaultNamespace: z + .union([z.string(), z.literal(false)]) + .optional() + .describe( + 'The default namespace. Options with this namespace will not be prefixed.', + ), +}); + +const EntityPickerReturnValueSchema = z.string(); + +/** + * The input props that can be specified under `ui:options` for the + * `EntityPicker` field extension. + * + * @public + */ +export type EntityPickerUiOptions = z.infer; + +export type EntityPickerReturnValue = z.infer< + typeof EntityPickerReturnValueSchema +>; + +export const EntityPickerSchema = { + uiOptions: zodToJsonSchema(EntityPickerUiOptionsSchema) as JSONSchema7, + returnValue: zodToJsonSchema(EntityPickerReturnValueSchema) as JSONSchema7, +}; diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx index c0e3aab436..6d5404cee1 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx @@ -23,18 +23,12 @@ import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { FormControl, TextField } from '@material-ui/core'; import { Autocomplete } from '@material-ui/lab'; import { FieldExtensionComponentProps } from '../../../extensions'; +import { + EntityTagsPickerReturnValue, + EntityTagsPickerUiOptions, +} from './schema'; -/** - * The input props that can be specified under `ui:options` for the - * `EntityTagsPicker` field extension. - * - * @public - */ -export interface EntityTagsPickerUiOptions { - kinds?: string[]; - showCounts?: boolean; - helperText?: string; -} +export { EntityTagsPickerSchema } from './schema'; /** * The underlying component that is rendered in the form for the `EntityTagsPicker` @@ -43,7 +37,10 @@ export interface EntityTagsPickerUiOptions { * @public */ export const EntityTagsPicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps< + EntityTagsPickerReturnValue, + EntityTagsPickerUiOptions + >, ) => { const { formData, onChange, uiSchema } = props; const catalogApi = useApi(catalogApiRef); diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts b/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts index 9ff6e553a6..850fc17c8e 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts @@ -13,4 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export type { EntityTagsPickerUiOptions } from './EntityTagsPicker'; +export { + type EntityTagsPickerUiOptions, + EntityTagsPickerUiOptionsSchema, +} from './schema'; diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts new file mode 100644 index 0000000000..ff477903d7 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts @@ -0,0 +1,56 @@ +/* + * 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 { JSONSchema7 } from 'json-schema'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +/** + * @public + */ +export const EntityTagsPickerUiOptionsSchema = z.object({ + kinds: z + .array(z.string()) + .optional() + .describe('List of kinds of entities to derive tags from'), + showCounts: z + .boolean() + .optional() + .describe('Whether to show usage counts per tag'), + helperText: z.string().optional().describe('Helper text to display'), +}); + +const EntityTagsPickerReturnValueSchema = z.array(z.string()); + +/** + * The input props that can be specified under `ui:options` for the + * `EntityTagsPicker` field extension. + * + * @public + */ +export type EntityTagsPickerUiOptions = z.infer< + typeof EntityTagsPickerUiOptionsSchema +>; + +export type EntityTagsPickerReturnValue = z.infer< + typeof EntityTagsPickerReturnValueSchema +>; + +export const EntityTagsPickerSchema = { + uiOptions: zodToJsonSchema(EntityTagsPickerUiOptionsSchema) as JSONSchema7, + returnValue: zodToJsonSchema( + EntityTagsPickerReturnValueSchema, + ) as JSONSchema7, +}; diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx index 2446883a86..47bcb6c9b8 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx @@ -27,20 +27,12 @@ import React, { useMemo } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { FieldExtensionComponentProps } from '../../../extensions'; +import { + OwnedEntityPickerReturnValue, + OwnedEntityPickerUiOptions, +} from './schema'; -/** - * The input props that can be specified under `ui:options` for the - * `OwnedEntityPicker` field extension. - * - * @public - */ -export interface OwnedEntityPickerUiOptions { - allowedKinds?: string[]; - defaultKind?: string; - allowArbitraryValues?: boolean; - defaultNamespace?: string | false; -} - +export { OwnedEntityPickerSchema } from './schema'; /** * The underlying component that is rendered in the form for the `OwnedEntityPicker` * field extension. @@ -48,7 +40,10 @@ export interface OwnedEntityPickerUiOptions { * @public */ export const OwnedEntityPicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps< + OwnedEntityPickerReturnValue, + OwnedEntityPickerUiOptions + >, ) => { const { onChange, diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts index 2988ba8cdc..0101eb8845 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts @@ -13,4 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export type { OwnedEntityPickerUiOptions } from './OwnedEntityPicker'; +export { + type OwnedEntityPickerUiOptions, + OwnedEntityPickerUiOptionsSchema, +} from './schema'; diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts new file mode 100644 index 0000000000..c260fae3cd --- /dev/null +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts @@ -0,0 +1,67 @@ +/* + * 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. + * 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 { JSONSchema7 } from 'json-schema'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +/** + * @public + */ +export const OwnedEntityPickerUiOptionsSchema = z.object({ + allowedKinds: z + .array(z.string()) + .optional() + .describe('List of kinds of entities to derive options from'), + defaultKind: z + .string() + .optional() + .describe( + 'The default entity kind. Options of this kind will not be prefixed.', + ), + allowArbitraryValues: z + .boolean() + .optional() + .describe('Whether to allow arbitrary user input. Defaults to true'), + defaultNamespace: z + .union([z.string(), z.literal(false)]) + .optional() + .describe( + 'The default namespace. Options with this namespace will not be prefixed.', + ), +}); + +const OwnedEntityPickerReturnValueSchema = z.string(); + +/** + * The input props that can be specified under `ui:options` for the + * `OwnedEntityPicker` field extension. + * + * @public + */ +export type OwnedEntityPickerUiOptions = z.infer< + typeof OwnedEntityPickerUiOptionsSchema +>; + +export type OwnedEntityPickerReturnValue = z.infer< + typeof OwnedEntityPickerReturnValueSchema +>; + +export const OwnedEntityPickerSchema = { + uiOptions: zodToJsonSchema(OwnedEntityPickerUiOptionsSchema) as JSONSchema7, + returnValue: zodToJsonSchema( + OwnedEntityPickerReturnValueSchema, + ) as JSONSchema7, +}; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx index 3de2b12bd0..10333684d0 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx @@ -16,18 +16,9 @@ import React from 'react'; import { EntityPicker } from '../EntityPicker/EntityPicker'; import { FieldExtensionComponentProps } from '../../../extensions'; +import { OwnerPickerReturnValue, OwnerPickerUiOptions } from './schema'; -/** - * The input props that can be specified under `ui:options` for the - * `OwnerPicker` field extension. - * - * @public - */ -export interface OwnerPickerUiOptions { - allowedKinds?: string[]; - allowArbitraryValues?: boolean; - defaultNamespace?: string | false; -} +export { OwnerPickerSchema } from './schema'; /** * The underlying component that is rendered in the form for the `OwnerPicker` @@ -36,7 +27,10 @@ export interface OwnerPickerUiOptions { * @public */ export const OwnerPicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps< + OwnerPickerReturnValue, + OwnerPickerUiOptions + >, ) => { const { schema: { title = 'Owner', description = 'The owner of the component' }, diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts b/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts index aa26024b5b..5d436358b3 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts @@ -13,4 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export type { OwnerPickerUiOptions } from './OwnerPicker'; +export { + type OwnerPickerUiOptions, + OwnerPickerUiOptionsSchema, +} from './schema'; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts new file mode 100644 index 0000000000..634a39ddee --- /dev/null +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts @@ -0,0 +1,60 @@ +/* + * 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 { JSONSchema7 } from 'json-schema'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +/** + * @public + */ +export const OwnerPickerUiOptionsSchema = z.object({ + allowedKinds: z + .array(z.string()) + .default(['Group', 'User']) + .optional() + .describe( + 'List of kinds of entities to derive options from. Defaults to Group and User', + ), + allowArbitraryValues: z + .boolean() + .optional() + .describe('Whether to allow arbitrary user input. Defaults to true'), + defaultNamespace: z + .union([z.string(), z.literal(false)]) + .optional() + .describe( + 'The default namespace. Options with this namespace will not be prefixed.', + ), +}); + +const OwnerPickerReturnValueSchema = z.string(); + +/** + * The input props that can be specified under `ui:options` for the + * `OwnerPicker` field extension. + * + * @public + */ +export type OwnerPickerUiOptions = z.infer; + +export type OwnerPickerReturnValue = z.infer< + typeof OwnerPickerReturnValueSchema +>; + +export const OwnerPickerSchema = { + uiOptions: zodToJsonSchema(OwnerPickerUiOptionsSchema) as JSONSchema7, + returnValue: zodToJsonSchema(OwnerPickerReturnValueSchema) as JSONSchema7, +}; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 451df857e5..8aca07e995 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -28,32 +28,12 @@ import { FieldExtensionComponentProps } from '../../../extensions'; import { RepoUrlPickerHost } from './RepoUrlPickerHost'; import { RepoUrlPickerRepoName } from './RepoUrlPickerRepoName'; import { parseRepoPickerUrl, serializeRepoPickerUrl } from './utils'; +import { RepoUrlPickerReturnValue, RepoUrlPickerUiOptions } from './schema'; import { RepoUrlPickerState } from './types'; import useDebounce from 'react-use/lib/useDebounce'; import { useTemplateSecrets } from '../../secrets'; -/** - * The input props that can be specified under `ui:options` for the - * `RepoUrlPicker` field extension. - * - * @public - */ -export interface RepoUrlPickerUiOptions { - allowedHosts?: string[]; - allowedOrganizations?: string[]; - allowedOwners?: string[]; - allowedRepos?: string[]; - requestUserCredentials?: { - secretsKey: string; - additionalScopes?: { - gerrit?: string[]; - github?: string[]; - gitlab?: string[]; - bitbucket?: string[]; - azure?: string[]; - }; - }; -} +export { RepoUrlPickerSchema } from './schema'; /** * The underlying component that is rendered in the form for the `RepoUrlPicker` @@ -62,7 +42,10 @@ export interface RepoUrlPickerUiOptions { * @public */ export const RepoUrlPicker = ( - props: FieldExtensionComponentProps, + props: FieldExtensionComponentProps< + RepoUrlPickerReturnValue, + RepoUrlPickerUiOptions + >, ) => { const { uiSchema, onChange, rawErrors, formData } = props; const [state, setState] = useState( diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts index c5f596a786..34124d0ccd 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts @@ -13,5 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export type { RepoUrlPickerUiOptions } from './RepoUrlPicker'; +export { + type RepoUrlPickerUiOptions, + RepoUrlPickerUiOptionsSchema, +} from './schema'; export { repoPickerValidation } from './validation'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts new file mode 100644 index 0000000000..b870747fd4 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts @@ -0,0 +1,101 @@ +/* + * 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 { JSONSchema7 } from 'json-schema'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +/** + * @public + */ +export const RepoUrlPickerUiOptionsSchema = z.object({ + allowedHosts: z + .array(z.string()) + .optional() + .describe('List of allowed SCM platform hosts'), + allowedOrganizations: z + .array(z.string()) + .optional() + .describe('List of allowed organizations in the given SCM platform'), + allowedOwners: z + .array(z.string()) + .optional() + .describe('List of allowed owners in the given SCM platform'), + allowedRepos: z + .array(z.string()) + .optional() + .describe('List of allowed repos in the given SCM platform'), + requestUserCredentials: z + .object({ + secretsKey: z + .string() + .describe( + 'Key used within the template secrets context to store the credential', + ), + additionalScopes: z + .object({ + gerrit: z + .array(z.string()) + .optional() + .describe('Additional Gerrit scopes to request'), + github: z + .array(z.string()) + .optional() + .describe('Additional GitHub scopes to request'), + gitlab: z + .array(z.string()) + .optional() + .describe('Additional GitLab scopes to request'), + bitbucket: z + .array(z.string()) + .optional() + .describe('Additional BitBucket scopes to request'), + azure: z + .array(z.string()) + .optional() + .describe('Additional Azure scopes to request'), + }) + .optional() + .describe('Additional permission scopes to request'), + }) + .optional() + .describe( + 'If defined will request user credentials to auth against the given SCM platform', + ), +}); + +const RepoUrlPickerReturnValueSchema = z.string(); + +/** + * The input props that can be specified under `ui:options` for the + * `RepoUrlPicker` field extension. + * + * @public + */ +export type RepoUrlPickerUiOptions = z.infer< + typeof RepoUrlPickerUiOptionsSchema +>; + +export type RepoUrlPickerReturnValue = z.infer< + typeof RepoUrlPickerReturnValueSchema +>; + +// NOTE: There is a bug with this failing validation in the custom field explorer due +// to https://github.com/rjsf-team/react-jsonschema-form/issues/675 even if +// requestUserCredentials is not defined +export const RepoUrlPickerSchema = { + uiOptions: zodToJsonSchema(RepoUrlPickerUiOptionsSchema) as JSONSchema7, + returnValue: zodToJsonSchema(RepoUrlPickerReturnValueSchema) as JSONSchema7, +}; diff --git a/plugins/scaffolder/src/extensions/default.ts b/plugins/scaffolder/src/extensions/default.ts index 8081dcb0ca..c6ab5276d0 100644 --- a/plugins/scaffolder/src/extensions/default.ts +++ b/plugins/scaffolder/src/extensions/default.ts @@ -13,40 +13,64 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EntityPicker } from '../components/fields/EntityPicker/EntityPicker'; -import { EntityNamePicker } from '../components/fields/EntityNamePicker/EntityNamePicker'; +import { + EntityPicker, + EntityPickerSchema, +} from '../components/fields/EntityPicker/EntityPicker'; +import { + EntityNamePicker, + EntityNamePickerSchema, +} from '../components/fields/EntityNamePicker/EntityNamePicker'; import { entityNamePickerValidation } from '../components/fields/EntityNamePicker/validation'; -import { EntityTagsPicker } from '../components/fields/EntityTagsPicker/EntityTagsPicker'; -import { OwnerPicker } from '../components/fields/OwnerPicker/OwnerPicker'; -import { RepoUrlPicker } from '../components/fields/RepoUrlPicker/RepoUrlPicker'; +import { + EntityTagsPicker, + EntityTagsPickerSchema, +} from '../components/fields/EntityTagsPicker/EntityTagsPicker'; +import { + OwnerPicker, + OwnerPickerSchema, +} from '../components/fields/OwnerPicker/OwnerPicker'; +import { + RepoUrlPicker, + RepoUrlPickerSchema, +} from '../components/fields/RepoUrlPicker/RepoUrlPicker'; import { repoPickerValidation } from '../components/fields/RepoUrlPicker/validation'; -import { OwnedEntityPicker } from '../components/fields/OwnedEntityPicker/OwnedEntityPicker'; +import { + OwnedEntityPicker, + OwnedEntityPickerSchema, +} from '../components/fields/OwnedEntityPicker/OwnedEntityPicker'; export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [ { component: EntityPicker, name: 'EntityPicker', + schema: EntityPickerSchema, }, { component: EntityNamePicker, name: 'EntityNamePicker', validation: entityNamePickerValidation, + schema: EntityNamePickerSchema, }, { component: EntityTagsPicker, name: 'EntityTagsPicker', + schema: EntityTagsPickerSchema, }, { component: RepoUrlPicker, name: 'RepoUrlPicker', validation: repoPickerValidation, + schema: RepoUrlPickerSchema, }, { component: OwnerPicker, name: 'OwnerPicker', + schema: OwnerPickerSchema, }, { component: OwnedEntityPicker, name: 'OwnedEntityPicker', + schema: OwnedEntityPickerSchema, }, ]; diff --git a/plugins/scaffolder/src/extensions/index.tsx b/plugins/scaffolder/src/extensions/index.tsx index 08075b5b9d..72f17edc20 100644 --- a/plugins/scaffolder/src/extensions/index.tsx +++ b/plugins/scaffolder/src/extensions/index.tsx @@ -16,6 +16,7 @@ import React from 'react'; import { + CustomFieldExtensionSchema, CustomFieldValidator, FieldExtensionOptions, FieldExtensionComponentProps, @@ -103,6 +104,7 @@ attachComponentData( ); export type { + CustomFieldExtensionSchema, CustomFieldValidator, FieldExtensionOptions, FieldExtensionComponentProps, diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index 62f17dff15..b15de56fc1 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -15,14 +15,14 @@ */ import { ApiHolder } from '@backstage/core-plugin-api'; import { FieldValidation, FieldProps } from '@rjsf/core'; -import { PropsWithChildren } from 'react'; - import { UIOptionsType, FieldProps as FieldPropsV5, UiSchema as UiSchemaV5, FieldValidation as FieldValidationV5, } from '@rjsf/utils'; +import { PropsWithChildren } from 'react'; +import { JSONSchema7 } from 'json-schema'; /** * Field validation type for Custom Field Extensions. @@ -35,6 +35,16 @@ export type CustomFieldValidator = ( context: { apiHolder: ApiHolder }, ) => void | Promise; +/** + * Type for the Custom Field Extension schema. + * + * @public + */ +export type CustomFieldExtensionSchema = { + uiOptions?: JSONSchema7; + returnValue?: JSONSchema7; +}; + /** * Type for the Custom Field Extension with the * name and components and validation function. @@ -50,6 +60,7 @@ export type FieldExtensionOptions< props: FieldExtensionComponentProps, ) => JSX.Element | null; validation?: CustomFieldValidator; + schema?: CustomFieldExtensionSchema; }; /** @@ -107,4 +118,5 @@ export type NextFieldExtensionOptions< props: NextFieldExtensionComponentProps, ) => JSX.Element | null; validation?: NextCustomFieldValidator; + schema?: CustomFieldExtensionSchema; }; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 7853667101..0e5e2630ac 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -43,6 +43,7 @@ export { ScaffolderFieldExtensions, } from './extensions'; export type { + CustomFieldExtensionSchema, CustomFieldValidator, FieldExtensionOptions, FieldExtensionComponentProps, diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index f9b359d93a..a34105745f 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -16,12 +16,24 @@ import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { scaffolderApiRef, ScaffolderClient } from './api'; -import { EntityPicker } from './components/fields/EntityPicker/EntityPicker'; +import { + EntityPicker, + EntityPickerSchema, +} 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 { + EntityNamePicker, + EntityNamePickerSchema, +} from './components/fields/EntityNamePicker/EntityNamePicker'; +import { + OwnerPicker, + OwnerPickerSchema, +} from './components/fields/OwnerPicker/OwnerPicker'; import { repoPickerValidation } from './components/fields/RepoUrlPicker'; -import { RepoUrlPicker } from './components/fields/RepoUrlPicker/RepoUrlPicker'; +import { + RepoUrlPicker, + RepoUrlPickerSchema, +} from './components/fields/RepoUrlPicker/RepoUrlPicker'; import { createScaffolderFieldExtension } from './extensions'; import { nextRouteRef, @@ -37,8 +49,14 @@ import { fetchApiRef, identityApiRef, } from '@backstage/core-plugin-api'; -import { OwnedEntityPicker } from './components/fields/OwnedEntityPicker/OwnedEntityPicker'; -import { EntityTagsPicker } from './components/fields/EntityTagsPicker/EntityTagsPicker'; +import { + OwnedEntityPicker, + OwnedEntityPickerSchema, +} from './components/fields/OwnedEntityPicker/OwnedEntityPicker'; +import { + EntityTagsPicker, + EntityTagsPickerSchema, +} from './components/fields/EntityTagsPicker/EntityTagsPicker'; /** * The main plugin export for the scaffolder. @@ -82,6 +100,7 @@ export const EntityPickerFieldExtension = scaffolderPlugin.provide( createScaffolderFieldExtension({ component: EntityPicker, name: 'EntityPicker', + schema: EntityPickerSchema, }), ); @@ -95,6 +114,7 @@ export const EntityNamePickerFieldExtension = scaffolderPlugin.provide( component: EntityNamePicker, name: 'EntityNamePicker', validation: entityNamePickerValidation, + schema: EntityNamePickerSchema, }), ); @@ -109,6 +129,7 @@ export const RepoUrlPickerFieldExtension = scaffolderPlugin.provide( component: RepoUrlPicker, name: 'RepoUrlPicker', validation: repoPickerValidation, + schema: RepoUrlPickerSchema, }), ); @@ -121,6 +142,7 @@ export const OwnerPickerFieldExtension = scaffolderPlugin.provide( createScaffolderFieldExtension({ component: OwnerPicker, name: 'OwnerPicker', + schema: OwnerPickerSchema, }), ); @@ -146,6 +168,7 @@ export const OwnedEntityPickerFieldExtension = scaffolderPlugin.provide( createScaffolderFieldExtension({ component: OwnedEntityPicker, name: 'OwnedEntityPicker', + schema: OwnedEntityPickerSchema, }), ); @@ -157,6 +180,7 @@ export const EntityTagsPickerFieldExtension = scaffolderPlugin.provide( createScaffolderFieldExtension({ component: EntityTagsPicker, name: 'EntityTagsPicker', + schema: EntityTagsPickerSchema, }), ); diff --git a/yarn.lock b/yarn.lock index acc503697d..eb5d0ffe16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6998,6 +6998,8 @@ __metadata: use-immer: ^0.7.0 yaml: ^2.0.0 zen-observable: ^0.8.15 + zod: ^3.11.6 + zod-to-json-schema: ^3.18.1 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 From aa4438e3415b16c595b2c4ab032956dd1b032284 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Wed, 9 Nov 2022 14:43:13 -0500 Subject: [PATCH 2/4] refactor(scaffolder): abstract away zod schema types Signed-off-by: Phil Kuang --- plugins/scaffolder/api-report.md | 212 +++--------------- .../fields/EntityNamePicker/schema.ts | 14 +- .../components/fields/EntityPicker/schema.ts | 63 +++--- .../fields/EntityTagsPicker/schema.ts | 47 ++-- .../fields/OwnedEntityPicker/schema.ts | 67 +++--- .../components/fields/OwnerPicker/schema.ts | 56 ++--- .../components/fields/RepoUrlPicker/schema.ts | 131 ++++++----- .../scaffolder/src/components/fields/utils.ts | 34 +++ 8 files changed, 253 insertions(+), 371 deletions(-) create mode 100644 plugins/scaffolder/src/components/fields/utils.ts diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 69512e7af0..e56b280c05 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -35,7 +35,6 @@ 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 { z } from 'zod'; // @alpha export function createNextScaffolderFieldExtension< @@ -91,33 +90,19 @@ export const EntityPickerFieldExtension: FieldExtensionComponent< >; // @public -export type EntityPickerUiOptions = z.infer; +export type EntityPickerUiOptions = + typeof EntityPickerUiOptionsSchema.schemaType; // @public (undocumented) -export const EntityPickerUiOptionsSchema: z.ZodObject< - { - allowedKinds: z.ZodOptional>; - defaultKind: z.ZodOptional; - allowArbitraryValues: z.ZodOptional; - defaultNamespace: z.ZodOptional< - z.ZodUnion<[z.ZodString, z.ZodLiteral]> - >; - }, - 'strip', - z.ZodTypeAny, - { +export const EntityPickerUiOptionsSchema: { + jsonSchema: JSONSchema7; + schemaType: { defaultKind?: string | undefined; defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; - }, - { - defaultKind?: string | undefined; - defaultNamespace?: string | false | undefined; - allowedKinds?: string[] | undefined; - allowArbitraryValues?: boolean | undefined; - } ->; + }; +}; // @public export const EntityTagsPickerFieldExtension: FieldExtensionComponent< @@ -130,30 +115,18 @@ export const EntityTagsPickerFieldExtension: FieldExtensionComponent< >; // @public -export type EntityTagsPickerUiOptions = z.infer< - typeof EntityTagsPickerUiOptionsSchema ->; +export type EntityTagsPickerUiOptions = + typeof EntityTagsPickerUiOptionsSchema.schemaType; // @public (undocumented) -export const EntityTagsPickerUiOptionsSchema: z.ZodObject< - { - kinds: z.ZodOptional>; - showCounts: z.ZodOptional; - helperText: z.ZodOptional; - }, - 'strip', - z.ZodTypeAny, - { +export const EntityTagsPickerUiOptionsSchema: { + jsonSchema: JSONSchema7; + schemaType: { showCounts?: boolean | undefined; kinds?: string[] | undefined; helperText?: string | undefined; - }, - { - showCounts?: boolean | undefined; - kinds?: string[] | undefined; - helperText?: string | undefined; - } ->; + }; +}; // @public export type FieldExtensionComponent<_TReturnValue, _TInputProps> = () => null; @@ -288,35 +261,19 @@ export const OwnedEntityPickerFieldExtension: FieldExtensionComponent< >; // @public -export type OwnedEntityPickerUiOptions = z.infer< - typeof OwnedEntityPickerUiOptionsSchema ->; +export type OwnedEntityPickerUiOptions = + typeof OwnedEntityPickerUiOptionsSchema.schemaType; // @public (undocumented) -export const OwnedEntityPickerUiOptionsSchema: z.ZodObject< - { - allowedKinds: z.ZodOptional>; - defaultKind: z.ZodOptional; - allowArbitraryValues: z.ZodOptional; - defaultNamespace: z.ZodOptional< - z.ZodUnion<[z.ZodString, z.ZodLiteral]> - >; - }, - 'strip', - z.ZodTypeAny, - { +export const OwnedEntityPickerUiOptionsSchema: { + jsonSchema: JSONSchema7; + schemaType: { defaultKind?: string | undefined; defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; - }, - { - defaultKind?: string | undefined; - defaultNamespace?: string | false | undefined; - allowedKinds?: string[] | undefined; - allowArbitraryValues?: boolean | undefined; - } ->; + }; +}; // @public export const OwnerPickerFieldExtension: FieldExtensionComponent< @@ -329,30 +286,17 @@ export const OwnerPickerFieldExtension: FieldExtensionComponent< >; // @public -export type OwnerPickerUiOptions = z.infer; +export type OwnerPickerUiOptions = typeof OwnerPickerUiOptionsSchema.schemaType; // @public (undocumented) -export const OwnerPickerUiOptionsSchema: z.ZodObject< - { - allowedKinds: z.ZodOptional>>; - allowArbitraryValues: z.ZodOptional; - defaultNamespace: z.ZodOptional< - z.ZodUnion<[z.ZodString, z.ZodLiteral]> - >; - }, - 'strip', - z.ZodTypeAny, - { +export const OwnerPickerUiOptionsSchema: { + jsonSchema: JSONSchema7; + schemaType: { defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; - }, - { - defaultNamespace?: string | false | undefined; - allowedKinds?: string[] | undefined; - allowArbitraryValues?: boolean | undefined; - } ->; + }; +}; // @public export const repoPickerValidation: ( @@ -389,81 +333,13 @@ export const RepoUrlPickerFieldExtension: FieldExtensionComponent< >; // @public -export type RepoUrlPickerUiOptions = z.infer< - typeof RepoUrlPickerUiOptionsSchema ->; +export type RepoUrlPickerUiOptions = + typeof RepoUrlPickerUiOptionsSchema.schemaType; // @public (undocumented) -export const RepoUrlPickerUiOptionsSchema: z.ZodObject< - { - allowedHosts: z.ZodOptional>; - allowedOrganizations: z.ZodOptional>; - allowedOwners: z.ZodOptional>; - allowedRepos: z.ZodOptional>; - requestUserCredentials: z.ZodOptional< - z.ZodObject< - { - secretsKey: z.ZodString; - additionalScopes: z.ZodOptional< - z.ZodObject< - { - gerrit: z.ZodOptional>; - github: z.ZodOptional>; - gitlab: z.ZodOptional>; - bitbucket: z.ZodOptional>; - azure: z.ZodOptional>; - }, - 'strip', - z.ZodTypeAny, - { - azure?: string[] | undefined; - github?: string[] | undefined; - gitlab?: string[] | undefined; - bitbucket?: string[] | undefined; - gerrit?: string[] | undefined; - }, - { - azure?: string[] | undefined; - github?: string[] | undefined; - gitlab?: string[] | undefined; - bitbucket?: string[] | undefined; - gerrit?: string[] | undefined; - } - > - >; - }, - 'strip', - z.ZodTypeAny, - { - additionalScopes?: - | { - azure?: string[] | undefined; - github?: string[] | undefined; - gitlab?: string[] | undefined; - bitbucket?: string[] | undefined; - gerrit?: string[] | undefined; - } - | undefined; - secretsKey: string; - }, - { - additionalScopes?: - | { - azure?: string[] | undefined; - github?: string[] | undefined; - gitlab?: string[] | undefined; - bitbucket?: string[] | undefined; - gerrit?: string[] | undefined; - } - | undefined; - secretsKey: string; - } - > - >; - }, - 'strip', - z.ZodTypeAny, - { +export const RepoUrlPickerUiOptionsSchema: { + jsonSchema: JSONSchema7; + schemaType: { allowedOwners?: string[] | undefined; allowedOrganizations?: string[] | undefined; allowedRepos?: string[] | undefined; @@ -482,28 +358,8 @@ export const RepoUrlPickerUiOptionsSchema: z.ZodObject< secretsKey: string; } | undefined; - }, - { - allowedOwners?: string[] | undefined; - allowedOrganizations?: string[] | undefined; - allowedRepos?: string[] | undefined; - allowedHosts?: string[] | undefined; - requestUserCredentials?: - | { - additionalScopes?: - | { - azure?: string[] | undefined; - github?: string[] | undefined; - gitlab?: string[] | undefined; - bitbucket?: string[] | undefined; - gerrit?: string[] | undefined; - } - | undefined; - secretsKey: string; - } - | undefined; - } ->; + }; +}; // @public (undocumented) export const rootRouteRef: RouteRef; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts index 45aa4502ce..83e477b6aa 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts @@ -13,18 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JSONSchema7 } from 'json-schema'; import { z } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; +import { makeJsonSchemaFromZod } from '../utils'; -const EntityNamePickerReturnValueSchema = z.string(); +const EntityNamePickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); -export type EntityNamePickerReturnValue = z.infer< - typeof EntityNamePickerReturnValueSchema ->; +export type EntityNamePickerReturnValue = + typeof EntityNamePickerReturnValueSchema.schemaType; export const EntityNamePickerSchema = { - returnValue: zodToJsonSchema( - EntityNamePickerReturnValueSchema, - ) as JSONSchema7, + returnValue: EntityNamePickerReturnValueSchema.jsonSchema, }; diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts index 5f7d66fc4f..3e05264a14 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts @@ -13,37 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JSONSchema7 } from 'json-schema'; import { z } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; +import { makeJsonSchemaFromZod } from '../utils'; /** * @public */ -export const EntityPickerUiOptionsSchema = z.object({ - allowedKinds: z - .array(z.string()) - .optional() - .describe('List of kinds of entities to derive options from'), - defaultKind: z - .string() - .optional() - .describe( - 'The default entity kind. Options of this kind will not be prefixed.', - ), - allowArbitraryValues: z - .boolean() - .optional() - .describe('Whether to allow arbitrary user input. Defaults to true'), - defaultNamespace: z - .union([z.string(), z.literal(false)]) - .optional() - .describe( - 'The default namespace. Options with this namespace will not be prefixed.', - ), -}); +export const EntityPickerUiOptionsSchema = makeJsonSchemaFromZod( + z.object({ + allowedKinds: z + .array(z.string()) + .optional() + .describe('List of kinds of entities to derive options from'), + defaultKind: z + .string() + .optional() + .describe( + 'The default entity kind. Options of this kind will not be prefixed.', + ), + allowArbitraryValues: z + .boolean() + .optional() + .describe('Whether to allow arbitrary user input. Defaults to true'), + defaultNamespace: z + .union([z.string(), z.literal(false)]) + .optional() + .describe( + 'The default namespace. Options with this namespace will not be prefixed.', + ), + }), +); -const EntityPickerReturnValueSchema = z.string(); +const EntityPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); /** * The input props that can be specified under `ui:options` for the @@ -51,13 +52,13 @@ const EntityPickerReturnValueSchema = z.string(); * * @public */ -export type EntityPickerUiOptions = z.infer; +export type EntityPickerUiOptions = + typeof EntityPickerUiOptionsSchema.schemaType; -export type EntityPickerReturnValue = z.infer< - typeof EntityPickerReturnValueSchema ->; +export type EntityPickerReturnValue = + typeof EntityPickerReturnValueSchema.schemaType; export const EntityPickerSchema = { - uiOptions: zodToJsonSchema(EntityPickerUiOptionsSchema) as JSONSchema7, - returnValue: zodToJsonSchema(EntityPickerReturnValueSchema) as JSONSchema7, + uiOptions: EntityPickerUiOptionsSchema.jsonSchema, + returnValue: EntityPickerReturnValueSchema.jsonSchema, }; diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts index ff477903d7..e08918f838 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts @@ -13,26 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JSONSchema7 } from 'json-schema'; import { z } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; +import { makeJsonSchemaFromZod } from '../utils'; /** * @public */ -export const EntityTagsPickerUiOptionsSchema = z.object({ - kinds: z - .array(z.string()) - .optional() - .describe('List of kinds of entities to derive tags from'), - showCounts: z - .boolean() - .optional() - .describe('Whether to show usage counts per tag'), - helperText: z.string().optional().describe('Helper text to display'), -}); +export const EntityTagsPickerUiOptionsSchema = makeJsonSchemaFromZod( + z.object({ + kinds: z + .array(z.string()) + .optional() + .describe('List of kinds of entities to derive tags from'), + showCounts: z + .boolean() + .optional() + .describe('Whether to show usage counts per tag'), + helperText: z.string().optional().describe('Helper text to display'), + }), +); -const EntityTagsPickerReturnValueSchema = z.array(z.string()); +const EntityTagsPickerReturnValueSchema = makeJsonSchemaFromZod( + z.array(z.string()), +); /** * The input props that can be specified under `ui:options` for the @@ -40,17 +43,13 @@ const EntityTagsPickerReturnValueSchema = z.array(z.string()); * * @public */ -export type EntityTagsPickerUiOptions = z.infer< - typeof EntityTagsPickerUiOptionsSchema ->; +export type EntityTagsPickerUiOptions = + typeof EntityTagsPickerUiOptionsSchema.schemaType; -export type EntityTagsPickerReturnValue = z.infer< - typeof EntityTagsPickerReturnValueSchema ->; +export type EntityTagsPickerReturnValue = + typeof EntityTagsPickerReturnValueSchema.schemaType; export const EntityTagsPickerSchema = { - uiOptions: zodToJsonSchema(EntityTagsPickerUiOptionsSchema) as JSONSchema7, - returnValue: zodToJsonSchema( - EntityTagsPickerReturnValueSchema, - ) as JSONSchema7, + uiOptions: EntityTagsPickerUiOptionsSchema.jsonSchema, + returnValue: EntityTagsPickerReturnValueSchema.jsonSchema, }; diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts index c260fae3cd..e5bfcd9709 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts @@ -13,37 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JSONSchema7 } from 'json-schema'; import { z } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; +import { makeJsonSchemaFromZod } from '../utils'; /** * @public */ -export const OwnedEntityPickerUiOptionsSchema = z.object({ - allowedKinds: z - .array(z.string()) - .optional() - .describe('List of kinds of entities to derive options from'), - defaultKind: z - .string() - .optional() - .describe( - 'The default entity kind. Options of this kind will not be prefixed.', - ), - allowArbitraryValues: z - .boolean() - .optional() - .describe('Whether to allow arbitrary user input. Defaults to true'), - defaultNamespace: z - .union([z.string(), z.literal(false)]) - .optional() - .describe( - 'The default namespace. Options with this namespace will not be prefixed.', - ), -}); +export const OwnedEntityPickerUiOptionsSchema = makeJsonSchemaFromZod( + z.object({ + allowedKinds: z + .array(z.string()) + .optional() + .describe('List of kinds of entities to derive options from'), + defaultKind: z + .string() + .optional() + .describe( + 'The default entity kind. Options of this kind will not be prefixed.', + ), + allowArbitraryValues: z + .boolean() + .optional() + .describe('Whether to allow arbitrary user input. Defaults to true'), + defaultNamespace: z + .union([z.string(), z.literal(false)]) + .optional() + .describe( + 'The default namespace. Options with this namespace will not be prefixed.', + ), + }), +); -const OwnedEntityPickerReturnValueSchema = z.string(); +const OwnedEntityPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); /** * The input props that can be specified under `ui:options` for the @@ -51,17 +52,13 @@ const OwnedEntityPickerReturnValueSchema = z.string(); * * @public */ -export type OwnedEntityPickerUiOptions = z.infer< - typeof OwnedEntityPickerUiOptionsSchema ->; +export type OwnedEntityPickerUiOptions = + typeof OwnedEntityPickerUiOptionsSchema.schemaType; -export type OwnedEntityPickerReturnValue = z.infer< - typeof OwnedEntityPickerReturnValueSchema ->; +export type OwnedEntityPickerReturnValue = + typeof OwnedEntityPickerReturnValueSchema.schemaType; export const OwnedEntityPickerSchema = { - uiOptions: zodToJsonSchema(OwnedEntityPickerUiOptionsSchema) as JSONSchema7, - returnValue: zodToJsonSchema( - OwnedEntityPickerReturnValueSchema, - ) as JSONSchema7, + uiOptions: OwnedEntityPickerUiOptionsSchema.jsonSchema, + returnValue: OwnedEntityPickerReturnValueSchema.jsonSchema, }; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts index 634a39ddee..ccfe453651 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts @@ -13,34 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JSONSchema7 } from 'json-schema'; import { z } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; +import { makeJsonSchemaFromZod } from '../utils'; /** * @public */ -export const OwnerPickerUiOptionsSchema = z.object({ - allowedKinds: z - .array(z.string()) - .default(['Group', 'User']) - .optional() - .describe( - 'List of kinds of entities to derive options from. Defaults to Group and User', - ), - allowArbitraryValues: z - .boolean() - .optional() - .describe('Whether to allow arbitrary user input. Defaults to true'), - defaultNamespace: z - .union([z.string(), z.literal(false)]) - .optional() - .describe( - 'The default namespace. Options with this namespace will not be prefixed.', - ), -}); +export const OwnerPickerUiOptionsSchema = makeJsonSchemaFromZod( + z.object({ + allowedKinds: z + .array(z.string()) + .default(['Group', 'User']) + .optional() + .describe( + 'List of kinds of entities to derive options from. Defaults to Group and User', + ), + allowArbitraryValues: z + .boolean() + .optional() + .describe('Whether to allow arbitrary user input. Defaults to true'), + defaultNamespace: z + .union([z.string(), z.literal(false)]) + .optional() + .describe( + 'The default namespace. Options with this namespace will not be prefixed.', + ), + }), +); -const OwnerPickerReturnValueSchema = z.string(); +const OwnerPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); /** * The input props that can be specified under `ui:options` for the @@ -48,13 +49,12 @@ const OwnerPickerReturnValueSchema = z.string(); * * @public */ -export type OwnerPickerUiOptions = z.infer; +export type OwnerPickerUiOptions = typeof OwnerPickerUiOptionsSchema.schemaType; -export type OwnerPickerReturnValue = z.infer< - typeof OwnerPickerReturnValueSchema ->; +export type OwnerPickerReturnValue = + typeof OwnerPickerReturnValueSchema.schemaType; export const OwnerPickerSchema = { - uiOptions: zodToJsonSchema(OwnerPickerUiOptionsSchema) as JSONSchema7, - returnValue: zodToJsonSchema(OwnerPickerReturnValueSchema) as JSONSchema7, + uiOptions: OwnerPickerUiOptionsSchema.jsonSchema, + returnValue: OwnerPickerReturnValueSchema.jsonSchema, }; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts index b870747fd4..98d9f76a9c 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts @@ -13,70 +13,71 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JSONSchema7 } from 'json-schema'; import { z } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; +import { makeJsonSchemaFromZod } from '../utils'; /** * @public */ -export const RepoUrlPickerUiOptionsSchema = z.object({ - allowedHosts: z - .array(z.string()) - .optional() - .describe('List of allowed SCM platform hosts'), - allowedOrganizations: z - .array(z.string()) - .optional() - .describe('List of allowed organizations in the given SCM platform'), - allowedOwners: z - .array(z.string()) - .optional() - .describe('List of allowed owners in the given SCM platform'), - allowedRepos: z - .array(z.string()) - .optional() - .describe('List of allowed repos in the given SCM platform'), - requestUserCredentials: z - .object({ - secretsKey: z - .string() - .describe( - 'Key used within the template secrets context to store the credential', - ), - additionalScopes: z - .object({ - gerrit: z - .array(z.string()) - .optional() - .describe('Additional Gerrit scopes to request'), - github: z - .array(z.string()) - .optional() - .describe('Additional GitHub scopes to request'), - gitlab: z - .array(z.string()) - .optional() - .describe('Additional GitLab scopes to request'), - bitbucket: z - .array(z.string()) - .optional() - .describe('Additional BitBucket scopes to request'), - azure: z - .array(z.string()) - .optional() - .describe('Additional Azure scopes to request'), - }) - .optional() - .describe('Additional permission scopes to request'), - }) - .optional() - .describe( - 'If defined will request user credentials to auth against the given SCM platform', - ), -}); +export const RepoUrlPickerUiOptionsSchema = makeJsonSchemaFromZod( + z.object({ + allowedHosts: z + .array(z.string()) + .optional() + .describe('List of allowed SCM platform hosts'), + allowedOrganizations: z + .array(z.string()) + .optional() + .describe('List of allowed organizations in the given SCM platform'), + allowedOwners: z + .array(z.string()) + .optional() + .describe('List of allowed owners in the given SCM platform'), + allowedRepos: z + .array(z.string()) + .optional() + .describe('List of allowed repos in the given SCM platform'), + requestUserCredentials: z + .object({ + secretsKey: z + .string() + .describe( + 'Key used within the template secrets context to store the credential', + ), + additionalScopes: z + .object({ + gerrit: z + .array(z.string()) + .optional() + .describe('Additional Gerrit scopes to request'), + github: z + .array(z.string()) + .optional() + .describe('Additional GitHub scopes to request'), + gitlab: z + .array(z.string()) + .optional() + .describe('Additional GitLab scopes to request'), + bitbucket: z + .array(z.string()) + .optional() + .describe('Additional BitBucket scopes to request'), + azure: z + .array(z.string()) + .optional() + .describe('Additional Azure scopes to request'), + }) + .optional() + .describe('Additional permission scopes to request'), + }) + .optional() + .describe( + 'If defined will request user credentials to auth against the given SCM platform', + ), + }), +); -const RepoUrlPickerReturnValueSchema = z.string(); +const RepoUrlPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); /** * The input props that can be specified under `ui:options` for the @@ -84,18 +85,16 @@ const RepoUrlPickerReturnValueSchema = z.string(); * * @public */ -export type RepoUrlPickerUiOptions = z.infer< - typeof RepoUrlPickerUiOptionsSchema ->; +export type RepoUrlPickerUiOptions = + typeof RepoUrlPickerUiOptionsSchema.schemaType; -export type RepoUrlPickerReturnValue = z.infer< - typeof RepoUrlPickerReturnValueSchema ->; +export type RepoUrlPickerReturnValue = + typeof RepoUrlPickerReturnValueSchema.schemaType; // NOTE: There is a bug with this failing validation in the custom field explorer due // to https://github.com/rjsf-team/react-jsonschema-form/issues/675 even if // requestUserCredentials is not defined export const RepoUrlPickerSchema = { - uiOptions: zodToJsonSchema(RepoUrlPickerUiOptionsSchema) as JSONSchema7, - returnValue: zodToJsonSchema(RepoUrlPickerReturnValueSchema) as JSONSchema7, + uiOptions: RepoUrlPickerUiOptionsSchema.jsonSchema, + returnValue: RepoUrlPickerReturnValueSchema.jsonSchema, }; diff --git a/plugins/scaffolder/src/components/fields/utils.ts b/plugins/scaffolder/src/components/fields/utils.ts new file mode 100644 index 0000000000..b3ebf1e1b4 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/utils.ts @@ -0,0 +1,34 @@ +/* + * 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 { JSONSchema7 } from 'json-schema'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +/** + * Utility function to convert zod schemas to JSON schemas with + * type inference extraction that abstracts away zod typings + */ +export function makeJsonSchemaFromZod( + schema: T, +): { + jsonSchema: JSONSchema7; + schemaType: T extends z.ZodType ? I : never; +} { + return { + jsonSchema: zodToJsonSchema(schema) as JSONSchema7, + schemaType: null as any, + }; +} From 3e4a2ee77979919564e7f6bfd9a770966e995d5a Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Thu, 10 Nov 2022 13:11:03 -0500 Subject: [PATCH 3/4] docs(scaffolder): add info about custom field explorer Signed-off-by: Phil Kuang --- .../custom-field-explorer.png | Bin 0 -> 151063 bytes .../writing-custom-field-extensions.md | 106 ++++++++++++++++++ plugins/scaffolder/api-report.md | 41 ++++--- .../fields/EntityNamePicker/schema.ts | 4 +- .../components/fields/EntityPicker/schema.ts | 10 +- .../fields/EntityTagsPicker/schema.ts | 8 +- .../fields/OwnedEntityPicker/schema.ts | 8 +- .../components/fields/OwnerPicker/schema.ts | 9 +- .../components/fields/RepoUrlPicker/schema.ts | 9 +- .../scaffolder/src/components/fields/index.ts | 1 + .../scaffolder/src/components/fields/utils.ts | 9 +- 11 files changed, 158 insertions(+), 47 deletions(-) create mode 100644 docs/assets/software-templates/custom-field-explorer.png diff --git a/docs/assets/software-templates/custom-field-explorer.png b/docs/assets/software-templates/custom-field-explorer.png new file mode 100644 index 0000000000000000000000000000000000000000..1af897ac8c4c693f23a679527d41bf9917604bb8 GIT binary patch literal 151063 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

y)iSUXrP?ZG`Sk7BbS+)!D8YhJ>47T|85pz6i z^CD22=exA+T@^kB!XGJzOd4g&g$Qe;&U~Cn;T{j;Fgo+*ci-7tUxTwL^03fN(Zl=| z;=kn%Q#d3Rm_SLX;=^}gz7u#&FcP$4?jxA&qZh&X-1DCwlO?BdeRKn5`Y=*Sp_b&-chz=Ue+Bz&K;!$Bp|?^!9)MYWnzNXZ{Rl7>;cURb}>lh9sT1zgdgo zamkQjuSM1GaB-LOiCs=QQqVgTHkZyTLYieuqj%-jNzU!J3V3K`hF5>XdIzx!ikG8I z6r6ZdPl{pkvyL3wbqlw)rv|5`)#v7139o6{eeM6ePjTeZ?AQtq3RQE24hOlPqQQ*F z>>&ieXR4YkBcCJV?TA`RkTNM}fojYRE@x^v_Q^$OQ%3)%}-LR$EkLG52id0rwD z3Ys%-eoUIx&<&;xm91qs;txLygs?5$8XG^+C?%$WHZ!;0sPBS)=fb@na0r)Qn&&Rf z>(BJle`MLt^L01_Q5u*914$io42)^NY1^O;;I_9N# z&(ftH<0wCBZw7Sz{Yh%-8Rel`{pG&+Y=koT%i+h&RP(~hMInEV@fPbJBSprs5NW;I z4vET?CuRdB=oRK-#ETr_Lm-pI!@m=_5FA$l?lx0fVBE7rixk~gvni_#*Bdq?RC7|WDKqYu@F0Ey&M-%NPewlZ5bnN;WCrS21E5+&o zetRno3o&6DA?69!t`Xp94O)G@mLpccotumkN+Y$s0kV~3D_U=v;Q*NxDntG0N8PGQ zq$|wtnEf=>q#4*FJNM8$l7YJYEh&kFgXNq{PKL0)I62Yl^ZE4Ko5bwZ3Y_U=H|v3p zw^z%l9=2JMiDaXB7yTsx@|_1)a=E&;^or6-v+d=klM@|b>S0s;c zu6nU`2M5h@&wGTP=Ce=)t)ni#8a8KY@x#KxT>SINNI^!EtGSnfLF%$Az<1;6)RgP# z1c>p4;+SGY#zyFLG0v5?3qWz_$Z^;A6d1Vcyi#X8#cX9}QiThb#cR*k^UyOaOo0_X zESJUjgFusZi!bi@6OHd@q&N}};B)6qfn*OsqG}OZ({(!8-ckhp$wA1VnN?Bzlc8~l z2xLkMc#3AJ`fG9KtEj!KslJ?obb&;==%%Qm3gL$}O7oxIa`e%xQigft_`(gK68K+m z6Mgw>xn$DM1O9EahLh26ecYfATBm0o2s+s@?ZC~AGuQeki38R4u18v&T1PQR~I6eXQAV1lk ztDN4>3OdYcIJ!hky6+xk?eezLndqj&=bxwXR*&X8ea_8V0PU%2x~i5(QKcFuDWpg? z<^nn*O$&gNXp}o)g4RIw@6HT4lI73;B1bnK)26U$1L>c~W5N-vbQM@E#y%6;^Oyyl zW2qmaX$T5%b*%?hz}=hc(!4oWya4R0bRt>_E=o&PzBamoM`;RT2%D-}N!k$U^qFj& z&@^R03NfqQ6DAF!D+~$9ALWQ@lkd|y+g}pO4gWEbO7N=!5?CnSrzEg$L8xRZ*CXTv zP{6k}D;E2li3Q^oWFY|UOmlXkg})*Tr8#;xW{gCQ_(C@gvW)bv_oEw5F~sJLjJ-C7 zYP|MA-K-qzB~6i%8uiYJY?DZEO7KydoTV@C;3^; zclH*A$@?n6S+P$gLv6ru30sm&cIx$B;V4IOaq;C8UiJ3o=BL>}NcB9C`4y4hUmq@c z+LRQ}399)<@!_N&`zC;*P)pmAg7RaaO?v(%Gp?qNUKvUtpZA$TIxH=i0J6U&Tb5H{ zHz`i8JXuvGH2g@BI2^ZQQbox6Yi>(XC#cz6Mv|+HOhufV@|)Vl{*+~DRxpwsPEy!4 z{}oOl1aI6rton_$_WB~X+aSxDW-+2zo?0&#zQl>7dRwW1LvHy*R`?uq6;!c)%4Dss z&gs0O<)O4??5}6Sy1}&9_LR2V_ zAZT86Njy(Vavhb>`~y@hqk5V^=!DgNTkU3oU}cf$&l7=n{G}e8V(aq6;*OKv6w}`8 zgZIn%n20mI#5#H95(7AozfmNbh>qq1(>|pXy;1bXiYTD9jQ2^d$nlq#hQL}i4$TP{ z&fM17{2cNR6LR<25(c-Uw*LGGc}i(I*;<9^Eg9y&BF8@ihnehZRL61cOjo0VzRHLO{v@X{2ia=^S!~ zZUqaFMp|L$h8bc=k?xLR29)BpX$cl@i$QlB=@A0GUPnE?JbTOPn+MD?oh{fI{kW`3qZ%{yzTyP_bHnK%b)W( z`s0oNFdt`OCX713+>~BT{%`lCw4JWu9WIK0a>oA%+u2VNrNG=++z|V(_eBAqm)u7> z9RHZ5|2$Mb3t(<~@0~GU|HpkCw*a7*X{p-h|1fW7jK~GNUSMugU+(|c`_xY%X8(T? z#8)5n>;Fv-5rK97EIDOOmQM)I?+pnS;!=y~*Wak`NNV2|BmJ*yv~~4*%7ai!?7edn z{qIsL@$OWN-iHSKS9yDi*CDA*&$W97gmJ0{GWq<{W+P>Da=uegXc@iCnQ}e9xBMOv z@%R^iR$dMKZ&E-(1&E8obF2R>P5*3Y;EBL*V1L#NMrm9_1XOej2TfZ zq2ms+?#~Z^tQ56th&gpW1sOf#%1QjGbb)_+$zkx!qij6zN)-{+=52K6--arcFft0q zpsZ6&&EUnmbVzXjo{qMlpndZdsL^D4lsX;e>?XW`M{)C2c26(upE6#jLD|whX_9=6 z#t5=vs#VgFo9DSJKJN@4w-k(-9%x*PnUzvbzaKLpT~R*MZf^zsBl-OR*byCStLHpx zAWvF-m1H8L0%8|trsPx6vb!==rz4PSTM}(ss9CFPl`~|wMMAWnD9G4e$yZ7!rxnSnYsU|pjWh2oNt(zW?_jzVMA8uZww_4EO z$YQ(Eg~_wlh~t-6D!$~wx6_>4F={f|tu+&aQ8ITS%h>p8X&aw5RypoTBV-DzK4^G^ zn#~8zeSEH~YbI5EEwS*&d|nIu7SztlmL_9N*Eo6|n4jWhD2lauWprz>64*bX@>h^Vg;zeq(^?YE)O?s4uv_8dOGfZc=* zAt5ZZg>#8-MZkt|C#qCQ2Bl&(jDW6*j25Wgk1o~yil{)cmrky(UK41+-zHB;sC7&| ze&Uh0w(M_5ur|;l7GKpzKo?)>3o@pn^|Y2pcs+7>huP?>y9tu8KMcQClRvkzwHoto zWo=|aA@6w!KpU)S_}se!D8xFyRTSda70$gL}C0x%8AJa?QyW_8iX? zhquTO6PF)8^ut}z`CTQ_(hqk08WTavs?F>zv8(fmPYs&`qX!uqm(%aZdDslGiH``D*Fagc(Hj#(t>@%OvppCwfxB)rDZ>O@=?HuGpI!48&Iofi_BgkD$ zS?JA#aJ$1EpSfE;SqObkwcqhdb;WK7vCfdw!dSIeU^TzH_TAx*d9GZg$?69=FXeQ3 zeDdeOBSz{LtYT-kf&`o@QKC%Hlp7SJm=@U4#8Gvguc)4ik8a~U98C!msTMNUJMGIB_( zIU-#oG>w; zdaL8g>kOWYXTSDq(%`-jn|3#nw~Z%;nqPxmi4Grsz7H|43)c5z5LzmRRfdJZk6T)> zitEamhw9wEu_<(I%6ye7S;h=v)yChd`SH}4cpxY7v^=39fpLt{Mmo`_+{Xg@LOD5EcJ)b zmO}Bhi2*xJd&kg_J7Vg2`6VH8!`TBxKB6hG0ws~9F)tfz8TNfmj92Br-o;jD%@ao| z9`!gaSVr)A`*^8#ItEozy9V#})+o>q6c;}^yXWe5S(UA)>iit8d65vXG_<1_?z)_} zL_j3F$=0^%sJ-#bw(jW2Q%*KpOC^bgbNuMDL!_|flEC6rDi^OaE{Zc6^Q!0Wh zh+NAk%s5Iw-AT-Yo_y`ZnayVJCR-~nSCBX*#S1RpYc@rF<$f>Z)p`ZF zw5AQkd28cF>>I@^eZNbAuE5fS=eZ{hu^S^j8Cu8f#Qm$$#$yL_D-*Gnp^1Z|-jHp% z?0gWqDrTWlh@FB~?UGL_F^RVj1YV6hR$*Wss0arwkJetZvJ}-F##mbL8_W}c6+6OI zH4f#3tcIJQ@*DTPC*<*YAO&%9+qd?t^u-(3#j>i&ejX;G#i_&GW^_xA#$$C{Jd@L* z?`cG{H~N1K(s(iM5X8_6#@0T5!Yx{ze~s4p{Rz%<>o+z!ri^#w=RYSGfGHVZ^qapH z!RZWl01{>wWw>%K-c(9OJk#u|KjTK6F2)?CG7YyyGd=;3_sEzzX}Mr(kB&Of*Yn-- zd$Vn$2n82q^u2F%&-%c-VESM;CG#4v0kUl(lV&dkQFCeQDb0?7ozqm>^C$PQRpIq> z7G4qVtq6g#I!DLtfI(At%?o^N81#EZSR%wAB9>*pX368lS!cm7j$OI;g6X?TWHxWu z7v<>(Mf|EsIp-Zos}zZz`qGpX-;i{cr?u(a$}zv@ZB~17Lm0k3@oIG!ujD!W;wczu z4hik#J{{v0Gf3jJp2yj!M_($?)ZmW8^@@ef5B9t{BHh@e_Yf5{3*LYLXTZP zXJgIY^xWVs;PJmSRGnywbIj6X{X=2i^_zW`+=lhL5jjU;>~sFz|NfTuxvN{@UiuIccuS`;8UoHm|2I4$7J}n+Yc>(3To9MP}#w;)vzM`?d2K~)KS3v zmA@hcKP%=h$(CnwheT(2X!n6$a#=r)U6`PwnM zL;Vz^4@=zSRz}>Ut@}zV0RO6cTv49Eb8a zi>Y?8q!N23YXrRO<1xLa54@{_`u#&g$g=$rAvy#j_pi*jO?i__Po8-6%U=j_bJ*b zb=IvgdZ|sCwd1urhODLYT)e@%;&srtVAI!q%Lt5R(&&m8Y@TpifmoM4_gLvKn`Cex zOt|%zm}Ux`ran4Tr=I;0hqc`+86UmeIQfrI-IBQr*}ujJ*O{Z_&~oHz{ym*XEA)2$C52#>$&aFxPO&lrYNCHpi* zKhU-@ClPBFquVXw(&Cv^bV$9{uQz4yR%D)6b!}z1pbWWQKG;R;nNR#Gq!=K?(w3&; zTdUoWP~F_>R}$lPE-n%eX5XpEWf-dH%{YkPYCrPoVY3ac$?tds^hL2b4#Xguld!RP z;gX|vQx|di0o_p;{V)&ZFYa?~6bN@rw#vhddkgGvN9f<-Z?+$hxUt&2UVi!9-n!sd zOx*F6Kd+~YNNdP}sT=_hpYj*NI@_9nbO+#pV6_=Z(fC7My&~@Jnq(ZdrB<=Ce+@5Y zLj8`rw0s+4&s&f78=Ey<*rku!uN<6olSUr4?=JVXEW_@Per6Hc-C1xt_SpY&|o2Ly}P>%$Oa$0cH`28?;`2k_rr$1JvXP}U*<$P6?i>h zXK$@J+;YO=Pe$tCdeO$r-)!G`_VII9vkSgBQ`l7zuM!7gan%aQsJNO ztEDBiq`^Pb?i~Kc*3?Wwx9!~ZGm^wcg2&`b8Jdr#k~&}2ilusciL7p>=6MB$;6x5r z1`NjEkGR;bjd5MX9A6ehIE#eyLY^=k#=E}2pM*3p&>Ui(QtVhf;6^OGuZ z8nKRD$8LpTUfX3gtmmx#i5IDXlT5qpC6AUj(puZOl!$u#xaKGAH}Vya1^+?lAy_V& zNlZp3-QPR!W80YHwYIVknFWOixa>yPwIZCctuk;u`$uRX04*Yk@rE0lRr)|$L5jtK&s{m0=#*-iFnBHJ?k* zeXXVpvR3nHt+ai8dT>q$JdGz#^qtOi^P2z~Ka2db+QHU#822j;RAisiqVxtf>;nO^ zE(}kS1F|x>eNjC4x>})p{flaHS0XCdV=rvyaBh)q0i$_2+l9;<@q~ngViL0}_I`El z`QWH`VCFQHz-3|bG?hEs4KCbIqRnTTaf<42XfPkIx^U0sN|kV| zEiIWRt*LZ&6$={p00irskVp&zzB>iqxb=pYPYLxKai#I_2~2!JZRs3mu@{f~Op{)` zkW(d0h|?=HnoD6uOk`cwO4X)C_0z+25my{=Bq!sfv4qUSCSk|j-$amQ65 zJi$3TgypAU?XqadC!i`velb;L7_kiuHEi;t zw#U)t#JsM3*8QC6rmEg`&SCuUqpNj;IbIlQ!q^2oKc+w*qgwf|tHYS}{^9Pu%o~B9 zD7g#E^bD6G`>!DD23%!)iU13I zbp_d+1Wp5`GvH^|W%gBaTur|)>%&kfZiX(X+VM20_BSFbw^DnPiU`)L{MDFVH|YYd zQJ}zo)BG7X(H)W1P4rRUtuq)`iP3&}Q3#)crra>sKT$~xXWub^p#G5IG1yXt#;(6< zK)>otnVp^0QtYx3R4H9d0L}~re>1V3?9HYMI5j!@v>drPp<4!=Y`oMiKr@m=kSUI2 z9$O!zZQjvbq=}?MZkWK)nVq3xw{L#kQ%QYzvQ^fetDYkuhOr5eyOrZyPJI!Z;Ne|j z-nkQ`^i}VO_PgZqva^b)T5`2Ik&M&dSH!E4nH$eTYfWvzSo3p z5Ml7!mC1+I**)&CWNq9yA#z-(!S;^E%CLh66y7@Ho`peUS++a%W>S&n`&Ln zj$uXh4YClt>g!w4iT2>$Ug(5wFWBC(z0(b`Rzoi^1!60hk$a1l8#TvLUvoyQD(b?= z$3IZ8*O10Mf|q~4FlVZ%w^C4G+}yQ``%?A^1#fZ3Zve!~$78x81LV7{S?jd`azBXt zUf&fLq?)B3<){YpKE5J0XHy8;I5>!3<&6nZ^OJ;?t}WmtUDB(5D0U+6DkyIB=(dB7 z#dCHBzGdZ$!Z5mP1n(&JKP8dP0#fI43xtr_8Nb$mZYC?p` z?}v(_?sR8=hAl6h^VHq+-q0BkwJrtzjnJouuGpbq&&WKL@+(Xf{Bb29B;BgFOXA^r z)&TqcCrg5q+o3kaI;EdG+~TICZmo>IlXjwX$~5457<@&8$mq-h5c3esA#iUi#LxMy zhBUVnWTr9nZ5F10*o%WBk={=^KeL$M3P*r>7>MmK(bE@1HxX|bdT1Zzbpp;7zsS&Jt z%)dILQWe;~?m^1a`TUOctc7c>R(m}gUo}C&7#tvk;261GBVZ7U64xW2eO)M1e-A$h z>f)CP^1y_w-W;H#&FStnU5kSc)x!)15%b)l3w4)Bo!Mn{rJ<_V<=C~9#4sd4$n=_5 z8i9|oN;53XCF1*Tmxu+kiRR?LX2S7Jnw490H7AK|SeYk5l2FjtM4xG(1oqybMWs_0 zvZq@q^EHI%Czo#JK=AD&ugk{viY~j)7RZvNvHQbzlW|kG*GgJ~EfNIn^66Y#-V6b4 zxP7!@xY4WO?F9mzx2U-f@DFwaoMPAeB9dIUEW-|1_*Gt8s$$oQ%6vkIx~!6#QbU9$ zkXIo4+m+4N^^^*0ayM}F=&t(T?!!;NO>cBfOViJ&a+1*;eiPl^nMrrfMy^)Ii@%e4 zzC1=AIq-g5zZyG|k8IpKVGpABPE!)et&k5D-gQlmcIjIE8m3E_|C;kh`{7fEA6nu* z=_)5z_EPvN;+j<^DgKcGF2VuoC-zetKGo5V=8`%j)tm}}IL7p|Imy%1nfyiA(}B>f z!@GJct`FD11NR56W`9HwQau=bRAy+o@M%KKuxFavaLdD25k=LJY-|upOI^Jp@LNcz zC>rGX%420TJxLCv-A?(S8@}hQW5dL1iVTY)+}*Db(Wav+C!q?Ct(uQAuo>#(O1&+Y zooZQ4jy(Cip6>O-bUvIGc^JjD#G%V45C3!p4k!I;iGV(@f!${vS-b<*uYFBy^E-RI z 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Νu$ULnuIF2KsUoE%*sM+(Q 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 => { + return ( + 0 && !formData} + onChange={onChange} + focused={focused} + /> + ); +}; +``` + +```tsx +// packages/app/src/scaffolder/MyCustomExtensionWithOptions/extensions.ts +... +import { MyCustomExtensionWithOptions, MyCustomExtensionWithOptionsSchema } from './MyCustomExtensionWithOptions'; + +export const MyCustomFieldWithOptionsExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + name: 'MyCustomExtensionWithOptions', + component: MyCustomExtensionWithOptions, + schema: MyCustomExtensionWithOptionsSchema, + }), +); +``` + +We recommend using a library like [zod](https://github.com/colinhacks/zod) to define your schema +and the provided `makeJsonSchemaFromZod` helper utility function to generate both the JSON schema +and types for your field input/output to preventing having to duplicate the definitions: + +```tsx +//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx +... +import { z } from 'zod'; +import { makeJsonSchemaFromZod } from '@backstage/plugin-scaffolder'; + +const MyCustomExtensionWithOptionsUiOptionsSchema = makeJsonSchemaFromZod( + z.object({ + focused: z + .boolean() + .optional() + .describe('Whether to focus this field'), + }), +); + +const MyCustomExtensionWithOptionsReturnValueSchema = makeJsonSchemaFromZod( + z.string(), +); + +export const MyCustomExtensionWithOptionsSchema = { + uiOptions: MyCustomExtensionWithOptionsUiOptionsSchema.schema, + returnValue: MyCustomExtensionWithOptionsReturnValueSchema.schema, +}; + +export const MyCustomExtensionWithOptions = ({ + onChange, + rawErrors, + required, + formData, +}: FieldProps< + typeof MyCustomExtensionWithOptionsReturnValueSchema.type, + typeof MyCustomExtensionWithOptionsUiOptionsSchema.type +>) => { + return ( + 0 && !formData} + onChange={onChange} + focused={focused} + /> + ); +}; +``` diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index e56b280c05..1ef699f462 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -35,6 +35,7 @@ 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 { z } from 'zod'; // @alpha export function createNextScaffolderFieldExtension< @@ -90,13 +91,12 @@ export const EntityPickerFieldExtension: FieldExtensionComponent< >; // @public -export type EntityPickerUiOptions = - typeof EntityPickerUiOptionsSchema.schemaType; +export type EntityPickerUiOptions = typeof EntityPickerUiOptionsSchema.type; // @public (undocumented) export const EntityPickerUiOptionsSchema: { - jsonSchema: JSONSchema7; - schemaType: { + schema: JSONSchema7; + type: { defaultKind?: string | undefined; defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; @@ -116,12 +116,12 @@ export const EntityTagsPickerFieldExtension: FieldExtensionComponent< // @public export type EntityTagsPickerUiOptions = - typeof EntityTagsPickerUiOptionsSchema.schemaType; + typeof EntityTagsPickerUiOptionsSchema.type; // @public (undocumented) export const EntityTagsPickerUiOptionsSchema: { - jsonSchema: JSONSchema7; - schemaType: { + schema: JSONSchema7; + type: { showCounts?: boolean | undefined; kinds?: string[] | undefined; helperText?: string | undefined; @@ -192,6 +192,14 @@ export type LogEvent = { taskId: string; }; +// @public +export function makeJsonSchemaFromZod( + schema: T, +): { + schema: JSONSchema7; + type: T extends z.ZodType ? I : never; +}; + // @alpha export type NextCustomFieldValidator = ( data: TFieldReturnValue, @@ -262,12 +270,12 @@ export const OwnedEntityPickerFieldExtension: FieldExtensionComponent< // @public export type OwnedEntityPickerUiOptions = - typeof OwnedEntityPickerUiOptionsSchema.schemaType; + typeof OwnedEntityPickerUiOptionsSchema.type; // @public (undocumented) export const OwnedEntityPickerUiOptionsSchema: { - jsonSchema: JSONSchema7; - schemaType: { + schema: JSONSchema7; + type: { defaultKind?: string | undefined; defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; @@ -286,12 +294,12 @@ export const OwnerPickerFieldExtension: FieldExtensionComponent< >; // @public -export type OwnerPickerUiOptions = typeof OwnerPickerUiOptionsSchema.schemaType; +export type OwnerPickerUiOptions = typeof OwnerPickerUiOptionsSchema.type; // @public (undocumented) export const OwnerPickerUiOptionsSchema: { - jsonSchema: JSONSchema7; - schemaType: { + schema: JSONSchema7; + type: { defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; @@ -333,13 +341,12 @@ export const RepoUrlPickerFieldExtension: FieldExtensionComponent< >; // @public -export type RepoUrlPickerUiOptions = - typeof RepoUrlPickerUiOptionsSchema.schemaType; +export type RepoUrlPickerUiOptions = typeof RepoUrlPickerUiOptionsSchema.type; // @public (undocumented) export const RepoUrlPickerUiOptionsSchema: { - jsonSchema: JSONSchema7; - schemaType: { + schema: JSONSchema7; + type: { allowedOwners?: string[] | undefined; allowedOrganizations?: string[] | undefined; allowedRepos?: string[] | undefined; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts index 83e477b6aa..3139839bf4 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts @@ -19,8 +19,8 @@ import { makeJsonSchemaFromZod } from '../utils'; const EntityNamePickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); export type EntityNamePickerReturnValue = - typeof EntityNamePickerReturnValueSchema.schemaType; + typeof EntityNamePickerReturnValueSchema.type; export const EntityNamePickerSchema = { - returnValue: EntityNamePickerReturnValueSchema.jsonSchema, + returnValue: EntityNamePickerReturnValueSchema.schema, }; diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts index 3e05264a14..3d14a2bf3e 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts @@ -52,13 +52,11 @@ const EntityPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); * * @public */ -export type EntityPickerUiOptions = - typeof EntityPickerUiOptionsSchema.schemaType; +export type EntityPickerUiOptions = typeof EntityPickerUiOptionsSchema.type; -export type EntityPickerReturnValue = - typeof EntityPickerReturnValueSchema.schemaType; +export type EntityPickerReturnValue = typeof EntityPickerReturnValueSchema.type; export const EntityPickerSchema = { - uiOptions: EntityPickerUiOptionsSchema.jsonSchema, - returnValue: EntityPickerReturnValueSchema.jsonSchema, + uiOptions: EntityPickerUiOptionsSchema.schema, + returnValue: EntityPickerReturnValueSchema.schema, }; diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts index e08918f838..d3a5391385 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts @@ -44,12 +44,12 @@ const EntityTagsPickerReturnValueSchema = makeJsonSchemaFromZod( * @public */ export type EntityTagsPickerUiOptions = - typeof EntityTagsPickerUiOptionsSchema.schemaType; + typeof EntityTagsPickerUiOptionsSchema.type; export type EntityTagsPickerReturnValue = - typeof EntityTagsPickerReturnValueSchema.schemaType; + typeof EntityTagsPickerReturnValueSchema.type; export const EntityTagsPickerSchema = { - uiOptions: EntityTagsPickerUiOptionsSchema.jsonSchema, - returnValue: EntityTagsPickerReturnValueSchema.jsonSchema, + uiOptions: EntityTagsPickerUiOptionsSchema.schema, + returnValue: EntityTagsPickerReturnValueSchema.schema, }; diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts index e5bfcd9709..c967448729 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts @@ -53,12 +53,12 @@ const OwnedEntityPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); * @public */ export type OwnedEntityPickerUiOptions = - typeof OwnedEntityPickerUiOptionsSchema.schemaType; + typeof OwnedEntityPickerUiOptionsSchema.type; export type OwnedEntityPickerReturnValue = - typeof OwnedEntityPickerReturnValueSchema.schemaType; + typeof OwnedEntityPickerReturnValueSchema.type; export const OwnedEntityPickerSchema = { - uiOptions: OwnedEntityPickerUiOptionsSchema.jsonSchema, - returnValue: OwnedEntityPickerReturnValueSchema.jsonSchema, + uiOptions: OwnedEntityPickerUiOptionsSchema.schema, + returnValue: OwnedEntityPickerReturnValueSchema.schema, }; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts index ccfe453651..accc8f80fc 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts @@ -49,12 +49,11 @@ const OwnerPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); * * @public */ -export type OwnerPickerUiOptions = typeof OwnerPickerUiOptionsSchema.schemaType; +export type OwnerPickerUiOptions = typeof OwnerPickerUiOptionsSchema.type; -export type OwnerPickerReturnValue = - typeof OwnerPickerReturnValueSchema.schemaType; +export type OwnerPickerReturnValue = typeof OwnerPickerReturnValueSchema.type; export const OwnerPickerSchema = { - uiOptions: OwnerPickerUiOptionsSchema.jsonSchema, - returnValue: OwnerPickerReturnValueSchema.jsonSchema, + uiOptions: OwnerPickerUiOptionsSchema.schema, + returnValue: OwnerPickerReturnValueSchema.schema, }; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts index 98d9f76a9c..bf8fa3cf5c 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts @@ -85,16 +85,15 @@ const RepoUrlPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); * * @public */ -export type RepoUrlPickerUiOptions = - typeof RepoUrlPickerUiOptionsSchema.schemaType; +export type RepoUrlPickerUiOptions = typeof RepoUrlPickerUiOptionsSchema.type; export type RepoUrlPickerReturnValue = - typeof RepoUrlPickerReturnValueSchema.schemaType; + typeof RepoUrlPickerReturnValueSchema.type; // NOTE: There is a bug with this failing validation in the custom field explorer due // to https://github.com/rjsf-team/react-jsonschema-form/issues/675 even if // requestUserCredentials is not defined export const RepoUrlPickerSchema = { - uiOptions: RepoUrlPickerUiOptionsSchema.jsonSchema, - returnValue: RepoUrlPickerReturnValueSchema.jsonSchema, + uiOptions: RepoUrlPickerUiOptionsSchema.schema, + returnValue: RepoUrlPickerReturnValueSchema.schema, }; diff --git a/plugins/scaffolder/src/components/fields/index.ts b/plugins/scaffolder/src/components/fields/index.ts index 6358934025..e78b92da00 100644 --- a/plugins/scaffolder/src/components/fields/index.ts +++ b/plugins/scaffolder/src/components/fields/index.ts @@ -18,3 +18,4 @@ export * from './OwnerPicker'; export * from './RepoUrlPicker'; export * from './OwnedEntityPicker'; export * from './EntityTagsPicker'; +export { makeJsonSchemaFromZod } from './utils'; diff --git a/plugins/scaffolder/src/components/fields/utils.ts b/plugins/scaffolder/src/components/fields/utils.ts index b3ebf1e1b4..2f56e31db5 100644 --- a/plugins/scaffolder/src/components/fields/utils.ts +++ b/plugins/scaffolder/src/components/fields/utils.ts @@ -18,17 +18,18 @@ import { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; /** + * @public * Utility function to convert zod schemas to JSON schemas with * type inference extraction that abstracts away zod typings */ export function makeJsonSchemaFromZod( schema: T, ): { - jsonSchema: JSONSchema7; - schemaType: T extends z.ZodType ? I : never; + schema: JSONSchema7; + type: T extends z.ZodType ? I : never; } { return { - jsonSchema: zodToJsonSchema(schema) as JSONSchema7, - schemaType: null as any, + schema: zodToJsonSchema(schema) as JSONSchema7, + type: null as any, }; } From da25293910f208d00296af7c7de497a8f8ff7278 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Fri, 11 Nov 2022 14:11:42 -0500 Subject: [PATCH 4/4] refactor(scaffolder): wrap full field schema and prop type def in helper Signed-off-by: Phil Kuang --- .../writing-custom-field-extensions.md | 25 ++-- plugins/scaffolder/api-report.md | 116 ++++++++++-------- .../EntityNamePicker/EntityNamePicker.tsx | 7 +- .../fields/EntityNamePicker/schema.ts | 11 +- .../fields/EntityPicker/EntityPicker.tsx | 10 +- .../components/fields/EntityPicker/index.ts | 5 +- .../components/fields/EntityPicker/schema.ts | 17 ++- .../EntityTagsPicker/EntityTagsPicker.tsx | 13 +- .../fields/EntityTagsPicker/index.ts | 2 +- .../fields/EntityTagsPicker/schema.ts | 21 ++-- .../OwnedEntityPicker/OwnedEntityPicker.tsx | 14 +-- .../fields/OwnedEntityPicker/index.ts | 2 +- .../fields/OwnedEntityPicker/schema.ts | 17 +-- .../fields/OwnerPicker/OwnerPicker.tsx | 10 +- .../components/fields/OwnerPicker/index.ts | 5 +- .../components/fields/OwnerPicker/schema.ts | 16 +-- .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 10 +- .../components/fields/RepoUrlPicker/index.ts | 2 +- .../components/fields/RepoUrlPicker/schema.ts | 18 ++- .../scaffolder/src/components/fields/index.ts | 2 +- .../scaffolder/src/components/fields/utils.ts | 45 +++++-- plugins/scaffolder/src/extensions/types.ts | 2 +- 22 files changed, 169 insertions(+), 201 deletions(-) diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md index d0a36ecaf7..1d28bd5db9 100644 --- a/docs/features/software-templates/writing-custom-field-extensions.md +++ b/docs/features/software-templates/writing-custom-field-extensions.md @@ -212,7 +212,7 @@ export const MyCustomExtensionWithOptions = ({ rawErrors, required, formData, -}: FieldProps) => { +}: FieldExtensionComponentProps) => { return ( ) => { +}: MyCustomExtensionWithOptionsProps) => { return ( ( // @public export type CustomFieldExtensionSchema = { + returnValue: JSONSchema7; uiOptions?: JSONSchema7; - returnValue?: JSONSchema7; }; // @public @@ -90,19 +90,20 @@ export const EntityPickerFieldExtension: FieldExtensionComponent< } >; -// @public -export type EntityPickerUiOptions = typeof EntityPickerUiOptionsSchema.type; - // @public (undocumented) -export const EntityPickerUiOptionsSchema: { - schema: JSONSchema7; - type: { +export const EntityPickerFieldSchema: FieldSchema< + string, + { defaultKind?: string | undefined; defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; - }; -}; + } +>; + +// @public +export type EntityPickerUiOptions = + typeof EntityPickerFieldSchema.uiOptionsType; // @public export const EntityTagsPickerFieldExtension: FieldExtensionComponent< @@ -114,19 +115,19 @@ export const EntityTagsPickerFieldExtension: FieldExtensionComponent< } >; -// @public -export type EntityTagsPickerUiOptions = - typeof EntityTagsPickerUiOptionsSchema.type; - // @public (undocumented) -export const EntityTagsPickerUiOptionsSchema: { - schema: JSONSchema7; - type: { +export const EntityTagsPickerFieldSchema: FieldSchema< + string[], + { showCounts?: boolean | undefined; kinds?: string[] | undefined; helperText?: string | undefined; - }; -}; + } +>; + +// @public +export type EntityTagsPickerUiOptions = + typeof EntityTagsPickerFieldSchema.uiOptionsType; // @public export type FieldExtensionComponent<_TReturnValue, _TInputProps> = () => null; @@ -155,6 +156,16 @@ export type FieldExtensionOptions< schema?: CustomFieldExtensionSchema; }; +// @public +export interface FieldSchema { + // (undocumented) + readonly schema: CustomFieldExtensionSchema; + // (undocumented) + readonly type: FieldExtensionComponentProps; + // (undocumented) + readonly uiOptionsType: TUiOptions; +} + // @public export type LayoutComponent<_TInputProps> = () => null; @@ -193,12 +204,18 @@ export type LogEvent = { }; // @public -export function makeJsonSchemaFromZod( - schema: T, -): { - schema: JSONSchema7; - type: T extends z.ZodType ? I : never; -}; +export function makeFieldSchemaFromZod< + TReturnSchema extends z.ZodType, + TUiOptionsSchema extends z.ZodType = z.ZodType, +>( + returnSchema: TReturnSchema, + uiOptionsSchema?: TUiOptionsSchema, +): FieldSchema< + TReturnSchema extends z.ZodType ? IReturn : never, + TUiOptionsSchema extends z.ZodType + ? IUiOptions + : never +>; // @alpha export type NextCustomFieldValidator = ( @@ -268,20 +285,20 @@ export const OwnedEntityPickerFieldExtension: FieldExtensionComponent< } >; -// @public -export type OwnedEntityPickerUiOptions = - typeof OwnedEntityPickerUiOptionsSchema.type; - // @public (undocumented) -export const OwnedEntityPickerUiOptionsSchema: { - schema: JSONSchema7; - type: { +export const OwnedEntityPickerFieldSchema: FieldSchema< + string, + { defaultKind?: string | undefined; defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; - }; -}; + } +>; + +// @public +export type OwnedEntityPickerUiOptions = + typeof OwnedEntityPickerFieldSchema.uiOptionsType; // @public export const OwnerPickerFieldExtension: FieldExtensionComponent< @@ -293,18 +310,18 @@ export const OwnerPickerFieldExtension: FieldExtensionComponent< } >; -// @public -export type OwnerPickerUiOptions = typeof OwnerPickerUiOptionsSchema.type; - // @public (undocumented) -export const OwnerPickerUiOptionsSchema: { - schema: JSONSchema7; - type: { +export const OwnerPickerFieldSchema: FieldSchema< + string, + { defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; - }; -}; + } +>; + +// @public +export type OwnerPickerUiOptions = typeof OwnerPickerFieldSchema.uiOptionsType; // @public export const repoPickerValidation: ( @@ -340,13 +357,10 @@ export const RepoUrlPickerFieldExtension: FieldExtensionComponent< } >; -// @public -export type RepoUrlPickerUiOptions = typeof RepoUrlPickerUiOptionsSchema.type; - // @public (undocumented) -export const RepoUrlPickerUiOptionsSchema: { - schema: JSONSchema7; - type: { +export const RepoUrlPickerFieldSchema: FieldSchema< + string, + { allowedOwners?: string[] | undefined; allowedOrganizations?: string[] | undefined; allowedRepos?: string[] | undefined; @@ -365,8 +379,12 @@ export const RepoUrlPickerUiOptionsSchema: { secretsKey: string; } | undefined; - }; -}; + } +>; + +// @public +export type RepoUrlPickerUiOptions = + typeof RepoUrlPickerFieldSchema.uiOptionsType; // @public (undocumented) export const rootRouteRef: RouteRef; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx index eba05cfb92..555b726917 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -14,8 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { FieldExtensionComponentProps } from '../../../extensions'; -import { EntityNamePickerReturnValue } from './schema'; +import { EntityNamePickerProps } from './schema'; import { TextField } from '@material-ui/core'; export { EntityNamePickerSchema } from './schema'; @@ -23,9 +22,7 @@ export { EntityNamePickerSchema } from './schema'; /** * EntityName Picker */ -export const EntityNamePicker = ( - props: FieldExtensionComponentProps, -) => { +export const EntityNamePicker = (props: EntityNamePickerProps) => { const { onChange, required, diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts index 3139839bf4..27e33befb5 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts @@ -14,13 +14,10 @@ * limitations under the License. */ import { z } from 'zod'; -import { makeJsonSchemaFromZod } from '../utils'; +import { makeFieldSchemaFromZod } from '../utils'; -const EntityNamePickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); +const EntityNamePickerFieldSchema = makeFieldSchemaFromZod(z.string()); -export type EntityNamePickerReturnValue = - typeof EntityNamePickerReturnValueSchema.type; +export const EntityNamePickerSchema = EntityNamePickerFieldSchema.schema; -export const EntityNamePickerSchema = { - returnValue: EntityNamePickerReturnValueSchema.schema, -}; +export type EntityNamePickerProps = typeof EntityNamePickerFieldSchema.type; diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index 8fcc412d0a..bc78e00576 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -23,8 +23,7 @@ import FormControl from '@material-ui/core/FormControl'; import Autocomplete from '@material-ui/lab/Autocomplete'; import React, { useCallback, useEffect } from 'react'; import useAsync from 'react-use/lib/useAsync'; -import { FieldExtensionComponentProps } from '../../../extensions'; -import { EntityPickerReturnValue, EntityPickerUiOptions } from './schema'; +import { EntityPickerProps } from './schema'; export { EntityPickerSchema } from './schema'; @@ -34,12 +33,7 @@ export { EntityPickerSchema } from './schema'; * * @public */ -export const EntityPicker = ( - props: FieldExtensionComponentProps< - EntityPickerReturnValue, - EntityPickerUiOptions - >, -) => { +export const EntityPicker = (props: EntityPickerProps) => { const { onChange, schema: { title = 'Entity', description = 'An entity from the catalog' }, diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/index.ts b/plugins/scaffolder/src/components/fields/EntityPicker/index.ts index d1044e4e67..b8df596c98 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/EntityPicker/index.ts @@ -13,7 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { - type EntityPickerUiOptions, - EntityPickerUiOptionsSchema, -} from './schema'; +export { EntityPickerFieldSchema, type EntityPickerUiOptions } from './schema'; diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts index 3d14a2bf3e..9236457fc0 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts @@ -14,12 +14,13 @@ * limitations under the License. */ import { z } from 'zod'; -import { makeJsonSchemaFromZod } from '../utils'; +import { makeFieldSchemaFromZod } from '../utils'; /** * @public */ -export const EntityPickerUiOptionsSchema = makeJsonSchemaFromZod( +export const EntityPickerFieldSchema = makeFieldSchemaFromZod( + z.string(), z.object({ allowedKinds: z .array(z.string()) @@ -44,19 +45,15 @@ export const EntityPickerUiOptionsSchema = makeJsonSchemaFromZod( }), ); -const EntityPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); - /** * The input props that can be specified under `ui:options` for the * `EntityPicker` field extension. * * @public */ -export type EntityPickerUiOptions = typeof EntityPickerUiOptionsSchema.type; +export type EntityPickerUiOptions = + typeof EntityPickerFieldSchema.uiOptionsType; -export type EntityPickerReturnValue = typeof EntityPickerReturnValueSchema.type; +export type EntityPickerProps = typeof EntityPickerFieldSchema.type; -export const EntityPickerSchema = { - uiOptions: EntityPickerUiOptionsSchema.schema, - returnValue: EntityPickerReturnValueSchema.schema, -}; +export const EntityPickerSchema = EntityPickerFieldSchema.schema; diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx index 6d5404cee1..549ccdb8aa 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx @@ -22,11 +22,7 @@ import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { FormControl, TextField } from '@material-ui/core'; import { Autocomplete } from '@material-ui/lab'; -import { FieldExtensionComponentProps } from '../../../extensions'; -import { - EntityTagsPickerReturnValue, - EntityTagsPickerUiOptions, -} from './schema'; +import { EntityTagsPickerProps } from './schema'; export { EntityTagsPickerSchema } from './schema'; @@ -36,12 +32,7 @@ export { EntityTagsPickerSchema } from './schema'; * * @public */ -export const EntityTagsPicker = ( - props: FieldExtensionComponentProps< - EntityTagsPickerReturnValue, - EntityTagsPickerUiOptions - >, -) => { +export const EntityTagsPicker = (props: EntityTagsPickerProps) => { const { formData, onChange, uiSchema } = props; const catalogApi = useApi(catalogApiRef); const [tagOptions, setTagOptions] = useState([]); diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts b/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts index 850fc17c8e..52bad9a80d 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts @@ -14,6 +14,6 @@ * limitations under the License. */ export { + EntityTagsPickerFieldSchema, type EntityTagsPickerUiOptions, - EntityTagsPickerUiOptionsSchema, } from './schema'; diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts index d3a5391385..6677d16e98 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts @@ -14,12 +14,13 @@ * limitations under the License. */ import { z } from 'zod'; -import { makeJsonSchemaFromZod } from '../utils'; +import { makeFieldSchemaFromZod } from '../utils'; /** * @public */ -export const EntityTagsPickerUiOptionsSchema = makeJsonSchemaFromZod( +export const EntityTagsPickerFieldSchema = makeFieldSchemaFromZod( + z.array(z.string()), z.object({ kinds: z .array(z.string()) @@ -33,9 +34,9 @@ export const EntityTagsPickerUiOptionsSchema = makeJsonSchemaFromZod( }), ); -const EntityTagsPickerReturnValueSchema = makeJsonSchemaFromZod( - z.array(z.string()), -); +export const EntityTagsPickerSchema = EntityTagsPickerFieldSchema.schema; + +export type EntityTagsPickerProps = typeof EntityTagsPickerFieldSchema.type; /** * The input props that can be specified under `ui:options` for the @@ -44,12 +45,4 @@ const EntityTagsPickerReturnValueSchema = makeJsonSchemaFromZod( * @public */ export type EntityTagsPickerUiOptions = - typeof EntityTagsPickerUiOptionsSchema.type; - -export type EntityTagsPickerReturnValue = - typeof EntityTagsPickerReturnValueSchema.type; - -export const EntityTagsPickerSchema = { - uiOptions: EntityTagsPickerUiOptionsSchema.schema, - returnValue: EntityTagsPickerReturnValueSchema.schema, -}; + typeof EntityTagsPickerFieldSchema.uiOptionsType; diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx index 47bcb6c9b8..01a174a6a3 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx @@ -26,25 +26,17 @@ import Autocomplete from '@material-ui/lab/Autocomplete'; import React, { useMemo } from 'react'; import useAsync from 'react-use/lib/useAsync'; -import { FieldExtensionComponentProps } from '../../../extensions'; -import { - OwnedEntityPickerReturnValue, - OwnedEntityPickerUiOptions, -} from './schema'; +import { OwnedEntityPickerProps } from './schema'; export { OwnedEntityPickerSchema } from './schema'; + /** * The underlying component that is rendered in the form for the `OwnedEntityPicker` * field extension. * * @public */ -export const OwnedEntityPicker = ( - props: FieldExtensionComponentProps< - OwnedEntityPickerReturnValue, - OwnedEntityPickerUiOptions - >, -) => { +export const OwnedEntityPicker = (props: OwnedEntityPickerProps) => { const { onChange, schema: { title = 'Entity', description = 'An entity from the catalog' }, diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts index 0101eb8845..985dae1d3c 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts @@ -14,6 +14,6 @@ * limitations under the License. */ export { + OwnedEntityPickerFieldSchema, type OwnedEntityPickerUiOptions, - OwnedEntityPickerUiOptionsSchema, } from './schema'; diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts index c967448729..4190b89f95 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts @@ -14,12 +14,13 @@ * limitations under the License. */ import { z } from 'zod'; -import { makeJsonSchemaFromZod } from '../utils'; +import { makeFieldSchemaFromZod } from '../utils'; /** * @public */ -export const OwnedEntityPickerUiOptionsSchema = makeJsonSchemaFromZod( +export const OwnedEntityPickerFieldSchema = makeFieldSchemaFromZod( + z.string(), z.object({ allowedKinds: z .array(z.string()) @@ -44,8 +45,6 @@ export const OwnedEntityPickerUiOptionsSchema = makeJsonSchemaFromZod( }), ); -const OwnedEntityPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); - /** * The input props that can be specified under `ui:options` for the * `OwnedEntityPicker` field extension. @@ -53,12 +52,8 @@ const OwnedEntityPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); * @public */ export type OwnedEntityPickerUiOptions = - typeof OwnedEntityPickerUiOptionsSchema.type; + typeof OwnedEntityPickerFieldSchema.uiOptionsType; -export type OwnedEntityPickerReturnValue = - typeof OwnedEntityPickerReturnValueSchema.type; +export type OwnedEntityPickerProps = typeof OwnedEntityPickerFieldSchema.type; -export const OwnedEntityPickerSchema = { - uiOptions: OwnedEntityPickerUiOptionsSchema.schema, - returnValue: OwnedEntityPickerReturnValueSchema.schema, -}; +export const OwnedEntityPickerSchema = OwnedEntityPickerFieldSchema.schema; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx index 10333684d0..1c4c356906 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx @@ -15,8 +15,7 @@ */ import React from 'react'; import { EntityPicker } from '../EntityPicker/EntityPicker'; -import { FieldExtensionComponentProps } from '../../../extensions'; -import { OwnerPickerReturnValue, OwnerPickerUiOptions } from './schema'; +import { OwnerPickerProps } from './schema'; export { OwnerPickerSchema } from './schema'; @@ -26,12 +25,7 @@ export { OwnerPickerSchema } from './schema'; * * @public */ -export const OwnerPicker = ( - props: FieldExtensionComponentProps< - OwnerPickerReturnValue, - OwnerPickerUiOptions - >, -) => { +export const OwnerPicker = (props: OwnerPickerProps) => { const { schema: { title = 'Owner', description = 'The owner of the component' }, uiSchema, diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts b/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts index 5d436358b3..9d94650e04 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts @@ -13,7 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { - type OwnerPickerUiOptions, - OwnerPickerUiOptionsSchema, -} from './schema'; +export { OwnerPickerFieldSchema, type OwnerPickerUiOptions } from './schema'; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts index accc8f80fc..56edef1343 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts @@ -14,12 +14,13 @@ * limitations under the License. */ import { z } from 'zod'; -import { makeJsonSchemaFromZod } from '../utils'; +import { makeFieldSchemaFromZod } from '../utils'; /** * @public */ -export const OwnerPickerUiOptionsSchema = makeJsonSchemaFromZod( +export const OwnerPickerFieldSchema = makeFieldSchemaFromZod( + z.string(), z.object({ allowedKinds: z .array(z.string()) @@ -41,19 +42,14 @@ export const OwnerPickerUiOptionsSchema = makeJsonSchemaFromZod( }), ); -const OwnerPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); - /** * The input props that can be specified under `ui:options` for the * `OwnerPicker` field extension. * * @public */ -export type OwnerPickerUiOptions = typeof OwnerPickerUiOptionsSchema.type; +export type OwnerPickerUiOptions = typeof OwnerPickerFieldSchema.uiOptionsType; -export type OwnerPickerReturnValue = typeof OwnerPickerReturnValueSchema.type; +export type OwnerPickerProps = typeof OwnerPickerFieldSchema.type; -export const OwnerPickerSchema = { - uiOptions: OwnerPickerUiOptionsSchema.schema, - returnValue: OwnerPickerReturnValueSchema.schema, -}; +export const OwnerPickerSchema = OwnerPickerFieldSchema.schema; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 8aca07e995..827eff6ea2 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -24,11 +24,10 @@ import { GitlabRepoPicker } from './GitlabRepoPicker'; import { AzureRepoPicker } from './AzureRepoPicker'; import { BitbucketRepoPicker } from './BitbucketRepoPicker'; import { GerritRepoPicker } from './GerritRepoPicker'; -import { FieldExtensionComponentProps } from '../../../extensions'; import { RepoUrlPickerHost } from './RepoUrlPickerHost'; import { RepoUrlPickerRepoName } from './RepoUrlPickerRepoName'; import { parseRepoPickerUrl, serializeRepoPickerUrl } from './utils'; -import { RepoUrlPickerReturnValue, RepoUrlPickerUiOptions } from './schema'; +import { RepoUrlPickerProps } from './schema'; import { RepoUrlPickerState } from './types'; import useDebounce from 'react-use/lib/useDebounce'; import { useTemplateSecrets } from '../../secrets'; @@ -41,12 +40,7 @@ export { RepoUrlPickerSchema } from './schema'; * * @public */ -export const RepoUrlPicker = ( - props: FieldExtensionComponentProps< - RepoUrlPickerReturnValue, - RepoUrlPickerUiOptions - >, -) => { +export const RepoUrlPicker = (props: RepoUrlPickerProps) => { const { uiSchema, onChange, rawErrors, formData } = props; const [state, setState] = useState( parseRepoPickerUrl(formData), diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts index 34124d0ccd..33e5ef1715 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ export { + RepoUrlPickerFieldSchema, type RepoUrlPickerUiOptions, - RepoUrlPickerUiOptionsSchema, } from './schema'; export { repoPickerValidation } from './validation'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts index bf8fa3cf5c..8f8674a0a7 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts @@ -14,12 +14,13 @@ * limitations under the License. */ import { z } from 'zod'; -import { makeJsonSchemaFromZod } from '../utils'; +import { makeFieldSchemaFromZod } from '../utils'; /** * @public */ -export const RepoUrlPickerUiOptionsSchema = makeJsonSchemaFromZod( +export const RepoUrlPickerFieldSchema = makeFieldSchemaFromZod( + z.string(), z.object({ allowedHosts: z .array(z.string()) @@ -77,23 +78,18 @@ export const RepoUrlPickerUiOptionsSchema = makeJsonSchemaFromZod( }), ); -const RepoUrlPickerReturnValueSchema = makeJsonSchemaFromZod(z.string()); - /** * The input props that can be specified under `ui:options` for the * `RepoUrlPicker` field extension. * * @public */ -export type RepoUrlPickerUiOptions = typeof RepoUrlPickerUiOptionsSchema.type; +export type RepoUrlPickerUiOptions = + typeof RepoUrlPickerFieldSchema.uiOptionsType; -export type RepoUrlPickerReturnValue = - typeof RepoUrlPickerReturnValueSchema.type; +export type RepoUrlPickerProps = typeof RepoUrlPickerFieldSchema.type; // NOTE: There is a bug with this failing validation in the custom field explorer due // to https://github.com/rjsf-team/react-jsonschema-form/issues/675 even if // requestUserCredentials is not defined -export const RepoUrlPickerSchema = { - uiOptions: RepoUrlPickerUiOptionsSchema.schema, - returnValue: RepoUrlPickerReturnValueSchema.schema, -}; +export const RepoUrlPickerSchema = RepoUrlPickerFieldSchema.schema; diff --git a/plugins/scaffolder/src/components/fields/index.ts b/plugins/scaffolder/src/components/fields/index.ts index e78b92da00..8d86f601ce 100644 --- a/plugins/scaffolder/src/components/fields/index.ts +++ b/plugins/scaffolder/src/components/fields/index.ts @@ -18,4 +18,4 @@ export * from './OwnerPicker'; export * from './RepoUrlPicker'; export * from './OwnedEntityPicker'; export * from './EntityTagsPicker'; -export { makeJsonSchemaFromZod } from './utils'; +export { type FieldSchema, makeFieldSchemaFromZod } from './utils'; diff --git a/plugins/scaffolder/src/components/fields/utils.ts b/plugins/scaffolder/src/components/fields/utils.ts index 2f56e31db5..2d3e2aab1e 100644 --- a/plugins/scaffolder/src/components/fields/utils.ts +++ b/plugins/scaffolder/src/components/fields/utils.ts @@ -16,20 +16,47 @@ import { JSONSchema7 } from 'json-schema'; import { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; +import { + CustomFieldExtensionSchema, + FieldExtensionComponentProps, +} from '../../extensions'; /** * @public - * Utility function to convert zod schemas to JSON schemas with - * type inference extraction that abstracts away zod typings + * FieldSchema encapsulates a JSONSchema7 along with the + * matching FieldExtensionComponentProps type for a field extension. */ -export function makeJsonSchemaFromZod( - schema: T, -): { - schema: JSONSchema7; - type: T extends z.ZodType ? I : never; -} { +export interface FieldSchema { + readonly schema: CustomFieldExtensionSchema; + readonly type: FieldExtensionComponentProps; + readonly uiOptionsType: TUiOptions; +} + +/** + * @public + * Utility function to convert zod return and UI options schemas to a + * CustomFieldExtensionSchema with FieldExtensionComponentProps type inference + */ +export function makeFieldSchemaFromZod< + TReturnSchema extends z.ZodType, + TUiOptionsSchema extends z.ZodType = z.ZodType, +>( + returnSchema: TReturnSchema, + uiOptionsSchema?: TUiOptionsSchema, +): FieldSchema< + TReturnSchema extends z.ZodType ? IReturn : never, + TUiOptionsSchema extends z.ZodType + ? IUiOptions + : never +> { return { - schema: zodToJsonSchema(schema) as JSONSchema7, + schema: { + returnValue: zodToJsonSchema(returnSchema) as JSONSchema7, + uiOptions: uiOptionsSchema + ? (zodToJsonSchema(uiOptionsSchema) as JSONSchema7) + : undefined, + }, type: null as any, + uiOptionsType: null as any, }; } diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index b15de56fc1..3834b39c0a 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -41,8 +41,8 @@ export type CustomFieldValidator = ( * @public */ export type CustomFieldExtensionSchema = { + returnValue: JSONSchema7; uiOptions?: JSONSchema7; - returnValue?: JSONSchema7; }; /**