From ddd1c3308d4d838a78342d5167365a5c6e054d57 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Wed, 5 Oct 2022 13:58:43 -0400 Subject: [PATCH 01/20] 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 02/20] 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 03/20] 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 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 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 04/20] 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; }; /** From 9fc963a9144a0fd00b7450efe7ead90a6b7315df Mon Sep 17 00:00:00 2001 From: Jorge Lainfiesta Date: Tue, 15 Nov 2022 14:09:31 +0100 Subject: [PATCH 05/20] microsite: Add blog entry about LF course Signed-off-by: Jorge Lainfiesta --- ...undation-introduction-to-backstage-course.md | 14 ++++++++++++++ ...ge-Developer-Portals-Made-Easy-2-768x432.png | Bin 0 -> 53738 bytes 2 files changed, 14 insertions(+) create mode 100644 microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md create mode 100644 microsite/blog/assets/22-11-15/Introduction-to-Backstage-Developer-Portals-Made-Easy-2-768x432.png diff --git a/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md b/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md new file mode 100644 index 0000000000..ec2b133003 --- /dev/null +++ b/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md @@ -0,0 +1,14 @@ +--- +title: The Linux Foundation launches its Introduction to Backstage course +description: The new Linux Foundation course is available for free on edX and is designed to help individuals understand how to map Backstage to their organization needs +author: Jorge Lainfiesta, Roadie +authorURL: https://www.linkedin.com/in/jrlainfiesta/ +--- + +Backstage continues to grow in [popularity and maturity](https://roadie.io/blog/backstage-consolidating-its-role-in-the-cloud-native-ecosystem/), with industry leaders not only adopting the framework but actively participating in the community through contributions and commercial offerings. One of the most recent contributions comes from the Linux Foundation, [launching an introductory course](https://training.linuxfoundation.org/blog/23107/) aimed at DevOps engineers or professionals working on Developer Productivity or Developer Experience. The course is available for free on edX, with a paid option for the certified track. + +[![Introduction to Backstage: Developer Portals Made Easy (LFS142x): Enroll!](assets/22-11-15/Introduction-to-Backstage-Developer-Portals-Made-Easy-2-768x432.png)](https://www.edx.org/course/introduction-to-backstage-developer-portals-made-easy) + +The course, [Introduction to Backstage: Developer Portals Made Easy (LFS142x)](https://www.edx.org/course/introduction-to-backstage-developer-portals-made-easy), starts discussing the benefits of adopting a Developer Portal. Then, it dives into what is Backstage, its main features (Catalog, Scaffolder, TechDocs), and how to map its capabilities to your organization. At last, it provides tips on how to connect with the community to ease up your adoption journey. + +The course author, [Jorge Lainfiesta](https://www.linkedin.com/in/jrlainfiesta/), is a Technical Marketing Manager at [Roadie](https://roadie.io). Jorge has a background in software engineering (PayPal) and digital communication (UCLA). He’s been working around Backstage since it was open sourced by Spotify and co-hosts community initiatives like the Backstage Users Unconference. \ No newline at end of file diff --git a/microsite/blog/assets/22-11-15/Introduction-to-Backstage-Developer-Portals-Made-Easy-2-768x432.png b/microsite/blog/assets/22-11-15/Introduction-to-Backstage-Developer-Portals-Made-Easy-2-768x432.png new file mode 100644 index 0000000000000000000000000000000000000000..a789762cc136513584f05d3dd14b30db95a84831 GIT binary patch literal 53738 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 Date: Tue, 15 Nov 2022 14:28:14 +0100 Subject: [PATCH 06/20] Add blog entry about LF course Signed-off-by: Jorge Lainfiesta --- ...-15-linux-foundation-introduction-to-backstage-course.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md b/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md index ec2b133003..ea0d5e04dd 100644 --- a/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md +++ b/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md @@ -5,10 +5,10 @@ author: Jorge Lainfiesta, Roadie authorURL: https://www.linkedin.com/in/jrlainfiesta/ --- -Backstage continues to grow in [popularity and maturity](https://roadie.io/blog/backstage-consolidating-its-role-in-the-cloud-native-ecosystem/), with industry leaders not only adopting the framework but actively participating in the community through contributions and commercial offerings. One of the most recent contributions comes from the Linux Foundation, [launching an introductory course](https://training.linuxfoundation.org/blog/23107/) aimed at DevOps engineers or professionals working on Developer Productivity or Developer Experience. The course is available for free on edX, with a paid option for the certified track. +Backstage continues to grow in [popularity and maturity](https://roadie.io/blog/backstage-consolidating-its-role-in-the-cloud-native-ecosystem/), with industry leaders not only adopting the framework but actively participating in the community through contributions and commercial offerings. One of the most recent contributions comes from the Linux Foundation, [launching an introductory course](https://training.linuxfoundation.org/blog/23107/) aimed at DevOps engineers or professionals working on Developer Productivity or Developer Experience. The course is available for free on edX, with a paid option for the certified track. [![Introduction to Backstage: Developer Portals Made Easy (LFS142x): Enroll!](assets/22-11-15/Introduction-to-Backstage-Developer-Portals-Made-Easy-2-768x432.png)](https://www.edx.org/course/introduction-to-backstage-developer-portals-made-easy) -The course, [Introduction to Backstage: Developer Portals Made Easy (LFS142x)](https://www.edx.org/course/introduction-to-backstage-developer-portals-made-easy), starts discussing the benefits of adopting a Developer Portal. Then, it dives into what is Backstage, its main features (Catalog, Scaffolder, TechDocs), and how to map its capabilities to your organization. At last, it provides tips on how to connect with the community to ease up your adoption journey. +The course, [Introduction to Backstage: Developer Portals Made Easy (LFS142x)](https://www.edx.org/course/introduction-to-backstage-developer-portals-made-easy), starts discussing the benefits of adopting a Developer Portal. Then, it dives into what is Backstage, its main features (Catalog, Scaffolder, TechDocs), and how to map its capabilities to your organization. At last, it provides tips on how to connect with the community to ease up your adoption journey. -The course author, [Jorge Lainfiesta](https://www.linkedin.com/in/jrlainfiesta/), is a Technical Marketing Manager at [Roadie](https://roadie.io). Jorge has a background in software engineering (PayPal) and digital communication (UCLA). He’s been working around Backstage since it was open sourced by Spotify and co-hosts community initiatives like the Backstage Users Unconference. \ No newline at end of file +The course author, [Jorge Lainfiesta](https://www.linkedin.com/in/jrlainfiesta/), is a Technical Marketing Manager at [Roadie](https://roadie.io). Jorge has a background in software engineering (PayPal) and digital communication (UCLA). He’s been working around Backstage since it was open sourced by Spotify and co-hosts community initiatives like the Backstage Users Unconference. From 79f3005c0b0d0f1d3837cddef7348fe72b4b58ed Mon Sep 17 00:00:00 2001 From: Jorge Lainfiesta Date: Tue, 15 Nov 2022 14:31:42 +0100 Subject: [PATCH 07/20] Add truncate tag to blog post Signed-off-by: Jorge Lainfiesta --- ...2-11-15-linux-foundation-introduction-to-backstage-course.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md b/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md index ea0d5e04dd..b762bd7e97 100644 --- a/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md +++ b/microsite/blog/2022-11-15-linux-foundation-introduction-to-backstage-course.md @@ -9,6 +9,8 @@ Backstage continues to grow in [popularity and maturity](https://roadie.io/blog/ [![Introduction to Backstage: Developer Portals Made Easy (LFS142x): Enroll!](assets/22-11-15/Introduction-to-Backstage-Developer-Portals-Made-Easy-2-768x432.png)](https://www.edx.org/course/introduction-to-backstage-developer-portals-made-easy) + + The course, [Introduction to Backstage: Developer Portals Made Easy (LFS142x)](https://www.edx.org/course/introduction-to-backstage-developer-portals-made-easy), starts discussing the benefits of adopting a Developer Portal. Then, it dives into what is Backstage, its main features (Catalog, Scaffolder, TechDocs), and how to map its capabilities to your organization. At last, it provides tips on how to connect with the community to ease up your adoption journey. The course author, [Jorge Lainfiesta](https://www.linkedin.com/in/jrlainfiesta/), is a Technical Marketing Manager at [Roadie](https://roadie.io). Jorge has a background in software engineering (PayPal) and digital communication (UCLA). He’s been working around Backstage since it was open sourced by Spotify and co-hosts community initiatives like the Backstage Users Unconference. From cce755b5af900e99dbd1c1ca38509a183c279e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 16 Nov 2022 12:10:15 +0100 Subject: [PATCH 08/20] Update versioning policy to treat utility/service API additions as non-breaking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Johan Haals Signed-off-by: Fredrik Adelöw --- REVIEWING.md | 31 ++++++++++++++++++++++++++++++ docs/overview/versioning-policy.md | 21 +++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 469ac249f7..8611bf15a4 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -171,6 +171,37 @@ Exported types can be marked with either `@public`, `@alpha` or `@beta` release If a package does not have this configuration, then all exported types are considered stable, even if they are marked as `@alpha` or `@beta`. +#### Changes that are not Considered Breaking + +There are a few exceptions that are not considered breaking in the [versioning policy](https://backstage.io/docs/overview/versioning-policy), +primarily regarding Utility APIs and Backend Service interfaces (referred to "contracts" below) that are supplied by the +Backstage core packages. + +Example of a non-breaking change to a contract which has a default +implementation, since consumers typically only interact with that contract as +callers of existing methods: + +```diff + export interface MyService { + oldMethod(): void; ++ newMethod(): void; + } +``` + +Changes such as these must still be marked with `**BREAKING PRODUCERS**:` in the +changelog, to highlight them for those who might be implementing custom +implementations of those contracts or putting mocks of the contract in tests. + +Example of a breaking change to a contract, which affects existing consumers and +therefore makes it NOT fall under these exceptions: + +```diff + export interface MyService { +- oldMethod(): void; ++ oldMethod(): Promise; + } +``` + #### Type Contract Direction An important distinction to make when looking at changes to an API Report is the direction of the contract of a changed type, that is, whether it's used as input or output from the user's point of view. In the next two sections we'll dive into the different directions of a type contract, and how it affects whether a change is breaking or not. diff --git a/docs/overview/versioning-policy.md b/docs/overview/versioning-policy.md index a989f104b5..7e26ad19ea 100644 --- a/docs/overview/versioning-policy.md +++ b/docs/overview/versioning-policy.md @@ -99,7 +99,7 @@ This versioning is completely decoupled from the Backstage release versioning, meaning you might for example have `@backstage/core-plugin-api` version `3.1.4` be part of the `1.12` Backstage release. -Following versioning policy applies to all packages: +The following versioning policy applies to all packages: - Breaking changes are noted in the changelog, and documentation is updated. - Breaking changes are prefixed with `**BREAKING**: ` in the changelog. @@ -120,6 +120,25 @@ For packages at version `1.0.0` or above, the following policy also applies: changes without a deprecation period, but the changes must still adhere to semver. +### Changes that are not Considered Breaking + +There are a few changes that would typically be considered breaking changes, but +that we make exceptions for. This is both to be able to evolve the project more +rapidly, also because the alternative ends up having a bigger impact on users. + +For all Utility APIs and Backend Services that _have_ a built-in implementation, +we only consider the API stability for consumers of those interfaces. This means +that it is not considered a breaking change to break the contract for producers +of the interface. + +Changes that fall under the above rule, must be marked with +`**BREAKING PRODUCERS**:` in the changelog. + +For any case of dependency injection, it is not considered a breaking change to +add a dependency on a Utility API or Backend Service that is provided by the +framework. This includes any dependency that is provided by the +`@backstage/app-defaults` and `@backstage/backend-defaults` packages. + ### Release Stages The release stages(`@alpha`, `@beta` `@public`) refers to the From 4b4bf895c5eb05298f4309d6214471070a24d141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 16 Nov 2022 14:37:17 +0100 Subject: [PATCH 09/20] review updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- REVIEWING.md | 4 ++-- docs/overview/versioning-policy.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/REVIEWING.md b/REVIEWING.md index 8611bf15a4..500e15701d 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -171,9 +171,9 @@ Exported types can be marked with either `@public`, `@alpha` or `@beta` release If a package does not have this configuration, then all exported types are considered stable, even if they are marked as `@alpha` or `@beta`. -#### Changes that are not Considered Breaking +#### Changes that are Not Considered Breaking -There are a few exceptions that are not considered breaking in the [versioning policy](https://backstage.io/docs/overview/versioning-policy), +There are a few exceptions that are not considered breaking in the [versioning policy](https://backstage.io/docs/overview/versioning-policy#changes-that-are-not-considered-breaking), primarily regarding Utility APIs and Backend Service interfaces (referred to "contracts" below) that are supplied by the Backstage core packages. diff --git a/docs/overview/versioning-policy.md b/docs/overview/versioning-policy.md index 7e26ad19ea..10ffc82188 100644 --- a/docs/overview/versioning-policy.md +++ b/docs/overview/versioning-policy.md @@ -120,7 +120,7 @@ For packages at version `1.0.0` or above, the following policy also applies: changes without a deprecation period, but the changes must still adhere to semver. -### Changes that are not Considered Breaking +### Changes that are Not Considered Breaking There are a few changes that would typically be considered breaking changes, but that we make exceptions for. This is both to be able to evolve the project more From a962ce0551ca437bde93b1f69a881d8fea9f045b Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Wed, 16 Nov 2022 14:03:56 +0000 Subject: [PATCH 10/20] wait for initialize to complete before finalize Signed-off-by: Brian Fletcher --- .changeset/gorgeous-hairs-applaud.md | 5 +++++ .../src/indexing/BatchSearchEngineIndexer.ts | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/gorgeous-hairs-applaud.md diff --git a/.changeset/gorgeous-hairs-applaud.md b/.changeset/gorgeous-hairs-applaud.md new file mode 100644 index 0000000000..7793c6591e --- /dev/null +++ b/.changeset/gorgeous-hairs-applaud.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-node': patch +--- + +Wait for indexer initialization before finalizing indexing. diff --git a/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts b/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts index 24b90c2a59..0824f0ab48 100644 --- a/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts +++ b/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts @@ -110,6 +110,11 @@ export abstract class BatchSearchEngineIndexer extends Writable { * @internal */ async _final(done: (error?: Error | null) => void) { + const maybeError = await this.initialized; + if (maybeError) { + done(maybeError); + return; + } try { // Index any remaining documents. if (this.currentBatch.length) { From 92ec306d7ca3e98724347c9658fcd2c827d00e52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Nov 2022 15:42:11 +0000 Subject: [PATCH 11/20] build(deps): bump loader-utils from 1.4.1 to 1.4.2 in /storybook Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/webpack/loader-utils/releases) - [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md) - [Commits](https://github.com/webpack/loader-utils/compare/v1.4.1...v1.4.2) --- updated-dependencies: - dependency-name: loader-utils dependency-type: indirect ... Signed-off-by: dependabot[bot] --- storybook/yarn.lock | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/storybook/yarn.lock b/storybook/yarn.lock index b0789605a4..3317acf774 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -7875,28 +7875,17 @@ __metadata: linkType: hard "loader-utils@npm:^1.2.3": - version: 1.4.1 - resolution: "loader-utils@npm:1.4.1" + version: 1.4.2 + resolution: "loader-utils@npm:1.4.2" dependencies: big.js: ^5.2.2 emojis-list: ^3.0.0 json5: ^1.0.1 - checksum: ea0b648cba0194e04a90aab6270619f0e35be009e33a443d9e642e93056cd49e6ca4c9678bd1c777a2392551bc5f4d0f24a87f5040608da1274aa84c6eebb502 + checksum: eb6fb622efc0ffd1abdf68a2022f9eac62bef8ec599cf8adb75e94d1d338381780be6278534170e99edc03380a6d29bc7eb1563c89ce17c5fed3a0b17f1ad804 languageName: node linkType: hard -"loader-utils@npm:^2.0.0": - version: 2.0.2 - resolution: "loader-utils@npm:2.0.2" - dependencies: - big.js: ^5.2.2 - emojis-list: ^3.0.0 - json5: ^2.1.2 - checksum: 9078d1ed47cadc57f4c6ddbdb2add324ee7da544cea41de3b7f1128e8108fcd41cd3443a85b7ee8d7d8ac439148aa221922774efe4cf87506d4fb054d5889303 - languageName: node - linkType: hard - -"loader-utils@npm:^2.0.3": +"loader-utils@npm:^2.0.0, loader-utils@npm:^2.0.3": version: 2.0.4 resolution: "loader-utils@npm:2.0.4" dependencies: From 3c151a7525b9e171cfdec656f9294812127786a1 Mon Sep 17 00:00:00 2001 From: Jorge Lainfiesta Date: Wed, 16 Nov 2022 17:26:31 +0100 Subject: [PATCH 12/20] Adds surname to accepted vocab Signed-off-by: Jorge Lainfiesta --- .github/vale/Vocab/Backstage/accept.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index 58f5b57004..35832b734d 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -406,3 +406,4 @@ zod Zolotusky zoomable zsh +Lainfiesta From 19356df560deb7354c3cc7204b35672a73e70732 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Nov 2022 09:08:42 +0000 Subject: [PATCH 13/20] Update dependency zen-observable to ^0.9.0 Signed-off-by: Renovate Bot --- .changeset/renovate-4bb70f3.md | 17 ++++++++++++ packages/app/package.json | 2 +- packages/core-app-api/package.json | 2 +- packages/core-components/package.json | 2 +- packages/core-plugin-api/package.json | 2 +- packages/dev-utils/package.json | 2 +- packages/test-utils/package.json | 2 +- packages/types/package.json | 2 +- plugins/catalog-react/package.json | 2 +- plugins/catalog/package.json | 2 +- plugins/config-schema/package.json | 2 +- plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder/package.json | 2 +- plugins/shortcuts/package.json | 2 +- plugins/user-settings/package.json | 2 +- yarn.lock | 35 +++++++++++++++---------- 16 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 .changeset/renovate-4bb70f3.md diff --git a/.changeset/renovate-4bb70f3.md b/.changeset/renovate-4bb70f3.md new file mode 100644 index 0000000000..3a9054011f --- /dev/null +++ b/.changeset/renovate-4bb70f3.md @@ -0,0 +1,17 @@ +--- +'@backstage/core-app-api': patch +'@backstage/core-components': patch +'@backstage/core-plugin-api': patch +'@backstage/dev-utils': patch +'@backstage/test-utils': patch +'@backstage/types': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-config-schema': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-shortcuts': patch +'@backstage/plugin-user-settings': patch +--- + +Updated dependency `zen-observable` to `^0.9.0`. diff --git a/packages/app/package.json b/packages/app/package.json index 1d4735441a..9978a0d515 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -80,7 +80,7 @@ "react-router": "^6.3.0", "react-router-dom": "^6.3.0", "react-use": "^17.2.4", - "zen-observable": "^0.8.15" + "zen-observable": "^0.9.0" }, "devDependencies": { "@backstage/test-utils": "workspace:^", diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 95e76b4782..b5972bf308 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -39,7 +39,7 @@ "@types/prop-types": "^15.7.3", "prop-types": "^15.7.2", "react-use": "^17.2.4", - "zen-observable": "^0.8.15", + "zen-observable": "^0.9.0", "zod": "^3.11.6" }, "peerDependencies": { diff --git a/packages/core-components/package.json b/packages/core-components/package.json index b47473819d..4a7fec9ef1 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -67,7 +67,7 @@ "react-virtualized-auto-sizer": "^1.0.6", "react-window": "^1.8.6", "remark-gfm": "^3.0.1", - "zen-observable": "^0.8.15", + "zen-observable": "^0.9.0", "zod": "^3.11.6" }, "peerDependencies": { diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 099b259ba7..ae9914fc77 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -38,7 +38,7 @@ "@backstage/version-bridge": "workspace:^", "history": "^5.0.0", "prop-types": "^15.7.2", - "zen-observable": "^0.8.15" + "zen-observable": "^0.9.0" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 7f567e61a9..08c2a2c305 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -47,7 +47,7 @@ "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^14.0.0", "react-use": "^17.2.4", - "zen-observable": "^0.8.15" + "zen-observable": "^0.9.0" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 757eddd449..fb8dff4192 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -45,7 +45,7 @@ "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^14.0.0", "cross-fetch": "^3.1.5", - "zen-observable": "^0.8.15" + "zen-observable": "^0.9.0" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/packages/types/package.json b/packages/types/package.json index 2cc3b0e5b8..eaf2723155 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -35,7 +35,7 @@ "@backstage/cli": "workspace:^", "@types/zen-observable": "^0.8.0", "luxon": "^3.0.0", - "zen-observable": "^0.8.15" + "zen-observable": "^0.9.0" }, "files": [ "dist" diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 9b8de0ce69..b4e40d8546 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -54,7 +54,7 @@ "qs": "^6.9.4", "react-use": "^17.2.4", "yaml": "^2.0.0", - "zen-observable": "^0.8.15" + "zen-observable": "^0.9.0" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 0220e5c244..1528737aac 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -51,7 +51,7 @@ "lodash": "^4.17.21", "react-helmet": "6.1.0", "react-use": "^17.2.4", - "zen-observable": "^0.8.15" + "zen-observable": "^0.9.0" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index a7dc17deb3..c2ed4e49f7 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -34,7 +34,7 @@ "@material-ui/lab": "4.0.0-alpha.57", "jsonschema": "^1.2.6", "react-use": "^17.2.4", - "zen-observable": "^0.8.15" + "zen-observable": "^0.9.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0" diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index dcca4d382e..d5371053ca 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -77,7 +77,7 @@ "vm2": "^3.9.11", "winston": "^3.2.1", "yaml": "^2.0.0", - "zen-observable": "^0.8.15", + "zen-observable": "^0.9.0", "zod": "^3.11.6" }, "devDependencies": { diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 18c522b91e..adb7461dd9 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -74,7 +74,7 @@ "react-use": "^17.2.4", "use-immer": "^0.7.0", "yaml": "^2.0.0", - "zen-observable": "^0.8.15", + "zen-observable": "^0.9.0", "zod": "^3.11.6", "zod-to-json-schema": "^3.18.1" }, diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 40ef908779..773c6e019a 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -34,7 +34,7 @@ "react-hook-form": "^7.12.2", "react-use": "^17.2.4", "uuid": "^8.3.2", - "zen-observable": "^0.8.15" + "zen-observable": "^0.9.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0", diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 1fcbed7fde..b8db3a5b03 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -43,7 +43,7 @@ "@material-ui/lab": "4.0.0-alpha.57", "@types/react": "^16.13.1 || ^17.0.0", "react-use": "^17.2.4", - "zen-observable": "^0.8.15" + "zen-observable": "^0.9.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0", diff --git a/yarn.lock b/yarn.lock index f64ac93992..b1531e5db9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3269,7 +3269,7 @@ __metadata: react-router-dom-stable: "npm:react-router-dom@^6.3.0" react-router-stable: "npm:react-router@^6.3.0" react-use: ^17.2.4 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 zod: ^3.11.6 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 @@ -3390,7 +3390,7 @@ __metadata: react-virtualized-auto-sizer: ^1.0.6 react-window: ^1.8.6 remark-gfm: ^3.0.1 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 zod: ^3.11.6 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 @@ -3422,7 +3422,7 @@ __metadata: history: ^5.0.0 msw: ^0.48.0 prop-types: ^15.7.2 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 @@ -3477,7 +3477,7 @@ __metadata: "@testing-library/user-event": ^14.0.0 "@types/node": ^16.0.0 react-use: ^17.2.4 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 @@ -4712,7 +4712,7 @@ __metadata: react-test-renderer: ^16.13.1 react-use: ^17.2.4 yaml: ^2.0.0 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 @@ -4752,7 +4752,7 @@ __metadata: lodash: ^4.17.21 react-helmet: 6.1.0 react-use: ^17.2.4 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 @@ -5020,7 +5020,7 @@ __metadata: jsonschema: ^1.2.6 msw: ^0.48.0 react-use: ^17.2.4 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 peerDependencies: react: ^16.13.1 || ^17.0.0 languageName: unknown @@ -6571,7 +6571,7 @@ __metadata: vm2: ^3.9.11 winston: ^3.2.1 yaml: ^2.0.0 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 zod: ^3.11.6 languageName: unknown linkType: soft @@ -6645,7 +6645,7 @@ __metadata: react-use: ^17.2.4 use-immer: ^0.7.0 yaml: ^2.0.0 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 zod: ^3.11.6 zod-to-json-schema: ^3.18.1 peerDependencies: @@ -6882,7 +6882,7 @@ __metadata: react-hook-form: ^7.12.2 react-use: ^17.2.4 uuid: ^8.3.2 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 peerDependencies: react: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 @@ -7476,7 +7476,7 @@ __metadata: cross-fetch: ^3.1.5 msw: ^0.48.0 react-use: ^17.2.4 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 peerDependencies: react: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 @@ -7602,7 +7602,7 @@ __metadata: "@types/node": ^16.11.26 cross-fetch: ^3.1.5 msw: ^0.48.0 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 @@ -7628,7 +7628,7 @@ __metadata: "@backstage/cli": "workspace:^" "@types/zen-observable": ^0.8.0 luxon: ^3.0.0 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 languageName: unknown linkType: soft @@ -20886,7 +20886,7 @@ __metadata: react-router-dom: ^6.3.0 react-use: ^17.2.4 start-server-and-test: ^1.10.11 - zen-observable: ^0.8.15 + zen-observable: ^0.9.0 languageName: unknown linkType: soft @@ -37627,6 +37627,13 @@ __metadata: languageName: node linkType: hard +"zen-observable@npm:^0.9.0": + version: 0.9.0 + resolution: "zen-observable@npm:0.9.0" + checksum: d770639f7cb47c89e442b0b87e6955079b9d0f3f7da220f081c3a478ee7b837ee792be4ae8764f2d49fe0afaaa3fbcdc7a88042c3ed471df9ae551de90e82ea2 + languageName: node + linkType: hard + "zenscroll@npm:^4.0.2": version: 4.0.2 resolution: "zenscroll@npm:4.0.2" From 6574d52e6f4978d63e496d9b53d8a8e61c0e7277 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 15 Nov 2022 09:06:47 +0000 Subject: [PATCH 14/20] improve the Custom ScmAuthApi Implementation section of docs/auth Signed-off-by: Paul Cowan --- docs/auth/index.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/auth/index.md b/docs/auth/index.md index ab04d3ed87..bdf053c57c 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -260,7 +260,18 @@ Passport-supported authentication method. If you are using any custom authentication providers, like for example one for GitHub Enterprise, then you are likely to need a custom implementation of the [`ScmAuthApi`](https://backstage.io/docs/reference/integration-react.scmauthapi). It is an API used to authenticate towards different SCM systems in a generic way, based on what resource is being accessed, and is used for example by the Scaffolder (Software Templates) and Catalog Import plugins. -To set up a custom `ScmAuthApi` implementation, you'll need to add an API factory entry to `packages/app/src/apis.ts`. The following example shows an implementation that supports both public GitHub via `githubAuthApi` as well as a GitHub Enterprise installation hosted at `ghe.example.com` via `gheAuthApi`: +To set up a custom `ScmAuthApi` implementation, you'll will need to complete the following steps which creates an example entry for a github enterprise installation: + +1. Create an additional `xxxAuthApiRef` which can be defined either inside the app itself if it's only used for this purpose, or inside an internal common package for APIs, such as `@internal/apis`: + + ```ts + const gheAuthApiRef: ApiRef = + createApiRef({ + id: 'internal.auth.ghe', + }); + ``` + +2. Add an API factory entry to `packages/app/src/apis.ts`. The following example shows an implementation that supports both public GitHub via `githubAuthApi` as well as a GitHub Enterprise installation hosted at `ghe.example.com` via `gheAuthApi`: ```ts createApiFactory({ @@ -278,3 +289,16 @@ createApiFactory({ ), }); ``` +_**Warning:** You will need to remove the default `ScmAuth.createDefaultApiFactory();` with this approach. + +3. Finally you also need to add and configure another GitHub provider to the `auth-backend` using the provider ID, which in this example is `ghe`: + + + ```ts + import { providers } from '@backstage/plugin-auth-backend'; + + // Add the following options to `createRouter` in packages/backend/src/plugins/auth.ts + providerFactories: { + ghe: providers.github.create(), + }, + ``` \ No newline at end of file From 1a15f11d5f1c979da5b69fc7d11d205403d4512c Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 15 Nov 2022 09:37:43 +0000 Subject: [PATCH 15/20] run prettier Signed-off-by: Paul Cowan --- docs/auth/index.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/auth/index.md b/docs/auth/index.md index bdf053c57c..c77d42a86c 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -264,12 +264,12 @@ To set up a custom `ScmAuthApi` implementation, you'll will need to complete the 1. Create an additional `xxxAuthApiRef` which can be defined either inside the app itself if it's only used for this purpose, or inside an internal common package for APIs, such as `@internal/apis`: - ```ts - const gheAuthApiRef: ApiRef = - createApiRef({ - id: 'internal.auth.ghe', - }); - ``` +```ts +const gheAuthApiRef: ApiRef = + createApiRef({ + id: 'internal.auth.ghe', + }); +``` 2. Add an API factory entry to `packages/app/src/apis.ts`. The following example shows an implementation that supports both public GitHub via `githubAuthApi` as well as a GitHub Enterprise installation hosted at `ghe.example.com` via `gheAuthApi`: @@ -289,16 +289,16 @@ createApiFactory({ ), }); ``` -_**Warning:** You will need to remove the default `ScmAuth.createDefaultApiFactory();` with this approach. -3. Finally you also need to add and configure another GitHub provider to the `auth-backend` using the provider ID, which in this example is `ghe`: +\_**Warning:** You will need to remove the default `ScmAuth.createDefaultApiFactory();` with this approach. +3. Finally you also need to add and configure another GitHub provider to the `auth-backend` using the provider ID, which in this example is `ghe`: - ```ts - import { providers } from '@backstage/plugin-auth-backend'; - - // Add the following options to `createRouter` in packages/backend/src/plugins/auth.ts - providerFactories: { - ghe: providers.github.create(), - }, - ``` \ No newline at end of file +```ts +import { providers } from '@backstage/plugin-auth-backend'; + +// Add the following options to `createRouter` in packages/backend/src/plugins/auth.ts +providerFactories: { + ghe: providers.github.create(), +}, +``` From 1e0a64a5247a9e67c96024f1825ddb3b385a67f9 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 17 Nov 2022 10:38:32 +0000 Subject: [PATCH 16/20] update custom ScmAuthApi Implementation docs Signed-off-by: Paul Cowan --- docs/auth/index.md | 50 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/docs/auth/index.md b/docs/auth/index.md index c77d42a86c..8f973a3611 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -258,11 +258,49 @@ Passport-supported authentication method. ## Custom ScmAuthApi Implementation -If you are using any custom authentication providers, like for example one for GitHub Enterprise, then you are likely to need a custom implementation of the [`ScmAuthApi`](https://backstage.io/docs/reference/integration-react.scmauthapi). It is an API used to authenticate towards different SCM systems in a generic way, based on what resource is being accessed, and is used for example by the Scaffolder (Software Templates) and Catalog Import plugins. +The default `ScmAuthAPi` provides integrations for `github`, `gitlab`, `azure` and `bitbucket` and is created by the following code in `packages/app/src/apis.ts`: -To set up a custom `ScmAuthApi` implementation, you'll will need to complete the following steps which creates an example entry for a github enterprise installation: +```ts +ScmAuth.createDefaultApiFactory(); +``` -1. Create an additional `xxxAuthApiRef` which can be defined either inside the app itself if it's only used for this purpose, or inside an internal common package for APIs, such as `@internal/apis`: +If you only require only a subset of these integrations then you will need a custom implementation of the [`ScmAuthApi`](https://backstage.io/docs/reference/integration-react.scmauthapi). It is an API used to authenticate towards different SCM systems in a generic way, based on what resource is being accessed, and is used for example by the Scaffolder (Software Templates) and Catalog Import plugins. + +The first step is to remove the code that creates the default providers. + +```diff + import { + ScmIntegrationsApi, + scmIntegrationsApiRef, ++ ScmAuth, + } from '@backstage/integration-react'; + + export const apis: AnyApiFactory[] = [ +... ++ ScmAuth.createDefaultApiFactory(), +... + ]; +``` + +Then replace it with something like this which will create an `ApiFactory` with + +```ts +export const apis: AnyApiFactory[] = [ + createApiFactory({ + api: scmAuthApiRef, + deps: { + githubAuthApi: githubAuthApiRef, + }, + factory: ({ githubAuthApi }) => + ScmAuth.merge( + ScmAuth.forGithub(githubAuthApi), + ), + }); +``` + +If you are using any custom authentication integrations, a new provider can be added to the `ApiFactory`. + +The first step is to create a new authentication ref which follows the naming convention of `xxxAuthApiRef`. The example below is for a new github enterprise integration which can be defined either inside the app itself if it's only used for this purpose, or inside an internal common package for APIs, such as `@internal/apis`: ```ts const gheAuthApiRef: ApiRef = @@ -271,7 +309,7 @@ const gheAuthApiRef: ApiRef = }); ``` -2. Add an API factory entry to `packages/app/src/apis.ts`. The following example shows an implementation that supports both public GitHub via `githubAuthApi` as well as a GitHub Enterprise installation hosted at `ghe.example.com` via `gheAuthApi`: +The new ref is then used to add a new provider to the ApiFactory: ```ts createApiFactory({ @@ -290,9 +328,7 @@ createApiFactory({ }); ``` -\_**Warning:** You will need to remove the default `ScmAuth.createDefaultApiFactory();` with this approach. - -3. Finally you also need to add and configure another GitHub provider to the `auth-backend` using the provider ID, which in this example is `ghe`: +Finally you also need to add and configure another provider to the `auth-backend` using the provider ID, which in this example is `ghe`: ```ts import { providers } from '@backstage/plugin-auth-backend'; From cd03a88ea973b7b8e54396b361f7cf68261b1004 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 17 Nov 2022 10:53:04 +0000 Subject: [PATCH 17/20] run prettier Signed-off-by: Paul Cowan --- docs/auth/index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/auth/index.md b/docs/auth/index.md index 8f973a3611..5702e96cee 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -264,7 +264,7 @@ The default `ScmAuthAPi` provides integrations for `github`, `gitlab`, `azure` a ScmAuth.createDefaultApiFactory(); ``` -If you only require only a subset of these integrations then you will need a custom implementation of the [`ScmAuthApi`](https://backstage.io/docs/reference/integration-react.scmauthapi). It is an API used to authenticate towards different SCM systems in a generic way, based on what resource is being accessed, and is used for example by the Scaffolder (Software Templates) and Catalog Import plugins. +If you require only a subset of these integrations, then you will need a custom implementation of the [`ScmAuthApi`](https://backstage.io/docs/reference/integration-react.scmauthapi). It is an API used to authenticate different SCM systems generically, based on what resource is being accessed, and is used for example, by the Scaffolder (Software Templates) and Catalog Import plugins. The first step is to remove the code that creates the default providers. @@ -282,7 +282,7 @@ The first step is to remove the code that creates the default providers. ]; ``` -Then replace it with something like this which will create an `ApiFactory` with +Then replace it with something like this, which will create an `ApiFactory` with only a github provider. ```ts export const apis: AnyApiFactory[] = [ @@ -298,9 +298,9 @@ export const apis: AnyApiFactory[] = [ }); ``` -If you are using any custom authentication integrations, a new provider can be added to the `ApiFactory`. +If you use any custom authentication integrations, a new provider can be added to the `ApiFactory`. -The first step is to create a new authentication ref which follows the naming convention of `xxxAuthApiRef`. The example below is for a new github enterprise integration which can be defined either inside the app itself if it's only used for this purpose, or inside an internal common package for APIs, such as `@internal/apis`: +The first step is to create a new authentication ref, which follows the naming convention of `xxxAuthApiRef`. The example below is for a new GitHub enterprise integration which can be defined either inside the app itself if it's only used for this purpose or inside a common internal package for APIs, such as `@internal/apis`: ```ts const gheAuthApiRef: ApiRef = @@ -328,7 +328,7 @@ createApiFactory({ }); ``` -Finally you also need to add and configure another provider to the `auth-backend` using the provider ID, which in this example is `ghe`: +Finally, you also need to add and configure another provider to the `auth-backend` using the provider ID, which in this example is `ghe`: ```ts import { providers } from '@backstage/plugin-auth-backend'; From 1bbe66c27b04f09a6832c4d997e1ab0f9b4fea5b Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Thu, 17 Nov 2022 11:08:10 +0000 Subject: [PATCH 18/20] move wait into try block Signed-off-by: Brian Fletcher --- .../src/indexing/BatchSearchEngineIndexer.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts b/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts index 0824f0ab48..043e2c1953 100644 --- a/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts +++ b/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts @@ -110,12 +110,13 @@ export abstract class BatchSearchEngineIndexer extends Writable { * @internal */ async _final(done: (error?: Error | null) => void) { - const maybeError = await this.initialized; - if (maybeError) { - done(maybeError); - return; - } try { + const maybeError = await this.initialized; + if (maybeError) { + done(maybeError); + return; + } + // Index any remaining documents. if (this.currentBatch.length) { await this.index(this.currentBatch); From 34b039ca9f35898354bc17c751a7a334a2b0a3e6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 17 Nov 2022 13:33:45 +0100 Subject: [PATCH 19/20] integrations: add allowedInstallationOwners to config schema Signed-off-by: Patrik Oldsberg --- .changeset/weak-ears-jam.md | 5 +++++ packages/integration/config.d.ts | 8 ++++++++ 2 files changed, 13 insertions(+) create mode 100644 .changeset/weak-ears-jam.md diff --git a/.changeset/weak-ears-jam.md b/.changeset/weak-ears-jam.md new file mode 100644 index 0000000000..8f20937998 --- /dev/null +++ b/.changeset/weak-ears-jam.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +Added `integrations.github.apps.allowedInstallationOwners` to the configuration schema. diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 49a121aff3..a1eb7aceb9 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -189,6 +189,14 @@ export interface Config { * @visibility secret */ clientSecret: string; + /** + * List of installation owners allowed to be used by this GitHub app. The GitHub UI does not provide a way to list the installations. + * However you can list the installations with the GitHub API. You can find the list of installations here: + * https://api.github.com/app/installations + * The relevant documentation for this is here. + * https://docs.github.com/en/rest/reference/apps#list-installations-for-the-authenticated-app--code-samples + */ + allowedInstallationOwners?: string[]; }>; }>; From a531879958ac25984cfcaab3e426ec7e03010602 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 17 Nov 2022 14:03:01 +0100 Subject: [PATCH 20/20] docs/getting-started: clarify yarn version Signed-off-by: Patrik Oldsberg --- docs/getting-started/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index 477467bb88..16e361a19f 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -34,6 +34,7 @@ guide to do a repository-based installation. - [Package manager](https://nodejs.org/en/download/package-manager/) - [Using NodeSource packages](https://github.com/nodesource/distributions/blob/master/README.md) - `yarn` [Installation](https://classic.yarnpkg.com/en/docs/install) + - You will need to use Yarn classic to create a new project, but it can then be [migrated to Yarn 3](../tutorials/yarn-migration.md) - `docker` [installation](https://docs.docker.com/engine/install/) - `git` [installation](https://github.com/git-guides/install-git) - If the system is not directly accessible over your network the following ports