chore: initial approach for field extensions

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-09-23 14:28:52 +02:00
parent b304f78a31
commit bf6eaf35c8
78 changed files with 924 additions and 268 deletions
+1
View File
@@ -62,6 +62,7 @@
"@backstage/catalog-model": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
"@backstage/plugin-scaffolder-common": "workspace:^",
@@ -7,10 +7,15 @@
import { ApiHolder } from '@backstage/core-plugin-api';
import { ComponentType } from 'react';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { CustomFieldValidator } from '@backstage/plugin-scaffolder-react';
import { Dispatch } from 'react';
import { ExtensionBlueprint } from '@backstage/frontend-plugin-api';
import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react';
import { FieldSchema } from '@backstage/plugin-scaffolder-react';
import { FieldValidation } from '@rjsf/utils';
import { FormField } from '@internal/scaffolder';
import { FormProps } from '@backstage/plugin-scaffolder-react';
import { IconComponent } from '@backstage/core-plugin-api';
import { JsonObject } from '@backstage/types';
@@ -34,6 +39,7 @@ import { TemplateParameterSchema } from '@backstage/plugin-scaffolder-react';
import { TemplatePresentationV1beta3 } from '@backstage/plugin-scaffolder-common';
import { UiSchema } from '@rjsf/utils';
import { WidgetProps } from '@rjsf/utils';
import { z } from 'zod';
// @alpha (undocumented)
export type BackstageOverrides = Overrides & {
@@ -57,6 +63,12 @@ export const createAsyncValidators: (
// @alpha
export const createFieldValidation: () => FieldValidation;
// @alpha
export function createFormField<
TReturnValue extends z.ZodType,
TUiOptions extends z.ZodType,
>(opts: FormFieldExtensionData<TReturnValue, TUiOptions>): FormField;
// @alpha
export const DefaultTemplateOutputs: (props: {
output?: ScaffolderTaskOutput;
@@ -76,6 +88,49 @@ export const Form: (
props: PropsWithChildren<ScaffolderRJSFFormProps>,
) => React_2.JSX.Element;
// @alpha
export const FormFieldBlueprint: ExtensionBlueprint<{
kind: 'scaffolder-form-field';
name: undefined;
params: {
field: () => Promise<FormField>;
};
output: ConfigurableExtensionDataRef<
() => Promise<FormField>,
'scaffolder.form-field-loader',
{}
>;
inputs: {};
config: {};
configInput: {};
dataRefs: {
formFieldLoader: ConfigurableExtensionDataRef<
() => Promise<FormField>,
'scaffolder.form-field-loader',
{}
>;
};
}>;
// @alpha (undocumented)
export type FormFieldExtensionData<
TReturnValue extends z.ZodType = z.ZodType,
TUiOptions extends z.ZodType = z.ZodType,
> = {
name: string;
component: (
props: FieldExtensionComponentProps<
z.output<TReturnValue>,
z.output<TUiOptions>
>,
) => JSX.Element | null;
validation?: CustomFieldValidator<
z.output<TReturnValue>,
z.output<TUiOptions>
>;
schema?: FieldSchema<z.output<TReturnValue>, z.output<TUiOptions>>;
};
// @alpha (undocumented)
export type FormValidation = {
[name: string]: FieldValidation | FormValidation;
@@ -320,6 +375,7 @@ export type WorkflowProps = {
// Warnings were encountered during analysis:
//
// src/next/blueprints/types.d.ts:5:1 - (ae-undocumented) Missing documentation for "FormFieldExtensionData".
// src/next/components/ScaffolderField/ScaffolderField.d.ts:7:5 - (ae-undocumented) Missing documentation for "rawDescription".
// src/next/components/ScaffolderField/ScaffolderField.d.ts:8:5 - (ae-undocumented) Missing documentation for "errors".
// src/next/components/ScaffolderField/ScaffolderField.d.ts:9:5 - (ae-undocumented) Missing documentation for "rawErrors".
+28 -1
View File
@@ -6,7 +6,7 @@
/// <reference types="react" />
import { ApiHolder } from '@backstage/core-plugin-api';
import { ApiRef } from '@backstage/core-plugin-api';
import { ApiRef } from '@backstage/frontend-plugin-api';
import { ComponentType } from 'react';
import { CustomValidator } from '@rjsf/utils';
import { ElementType } from 'react';
@@ -44,6 +44,7 @@ import { TemplatesType } from '@rjsf/utils';
import { UIOptionsType } from '@rjsf/utils';
import { UiSchema } from '@rjsf/utils';
import { ValidatorType } from '@rjsf/utils';
import { z } from 'zod';
// @public
export type Action = {
@@ -125,6 +126,18 @@ export interface FieldExtensionUiSchema<TFieldReturnValue, TUiOptions>
'ui:options'?: TUiOptions & UIOptionsType<TFieldReturnValue>;
}
// @public
export interface FieldSchema<TReturn, TUiOptions> {
// (undocumented)
readonly schema: CustomFieldExtensionSchema;
// (undocumented)
readonly TProps: FieldExtensionComponentProps<TReturn, TUiOptions>;
// @deprecated (undocumented)
readonly type: FieldExtensionComponentProps<TReturn, TUiOptions>;
// @deprecated (undocumented)
readonly uiOptionsType: TUiOptions;
}
// @public
export type FormProps = Pick<
FormProps_2,
@@ -168,6 +181,15 @@ export type LogEvent = {
taskId: string;
};
// @public (undocumented)
export function makeFieldSchema<
TReturnType extends z.ZodType,
TUiOptions extends z.ZodType,
>(options: {
output: (zImpl: typeof z) => TReturnType;
uiOptions?: (zImpl: typeof z) => TUiOptions;
}): FieldSchema<z.output<TReturnType>, z.output<TUiOptions>>;
// @public
export type ReviewStepProps = {
disableButtons: boolean;
@@ -566,6 +588,11 @@ export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets;
// src/layouts/createScaffolderLayout.d.ts:17:5 - (ae-undocumented) Missing documentation for "component".
// src/secrets/SecretsContext.d.ts:14:5 - (ae-undocumented) Missing documentation for "setSecrets".
// src/secrets/SecretsContext.d.ts:15:5 - (ae-undocumented) Missing documentation for "secrets".
// src/utils.d.ts:4:1 - (ae-undocumented) Missing documentation for "makeFieldSchema".
// src/utils.d.ts:15:5 - (ae-undocumented) Missing documentation for "type".
// src/utils.d.ts:17:5 - (ae-undocumented) Missing documentation for "uiOptionsType".
// src/utils.d.ts:18:5 - (ae-undocumented) Missing documentation for "schema".
// src/utils.d.ts:19:5 - (ae-undocumented) Missing documentation for "TProps".
// (No @packageDocumentation comment for this package)
```
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { createApiRef } from '@backstage/core-plugin-api';
import { createApiRef } from '@backstage/frontend-plugin-api';
import { ScaffolderApi } from './types';
/** @public */
+1
View File
@@ -21,3 +21,4 @@ export * from './secrets';
export * from './api';
export * from './hooks';
export * from './layouts';
export * from './utils';
@@ -0,0 +1,56 @@
/*
* Copyright 2024 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 {
createExtensionBlueprint,
createExtensionDataRef,
} from '@backstage/frontend-plugin-api';
import { z } from 'zod';
import { OpaqueFormField, FormField } from '@internal/scaffolder';
import { FormFieldExtensionData } from './types';
const formFieldExtensionDataRef = createExtensionDataRef<
() => Promise<FormField>
>().with({
id: 'scaffolder.form-field-loader',
});
/**
* @alpha
* Creates extensions that are Field Extensions for the Scaffolder
* */
export const FormFieldBlueprint = createExtensionBlueprint({
kind: 'scaffolder-form-field',
attachTo: { id: 'api:scaffolder/form-fields', input: 'formFields' },
dataRefs: {
formFieldLoader: formFieldExtensionDataRef,
},
output: [formFieldExtensionDataRef],
*factory(params: { field: () => Promise<FormField> }) {
yield formFieldExtensionDataRef(params.field);
},
});
/**
* @alpha
* Used to create a form field binding with typechecking for compliance
*/
export function createFormField<
TReturnValue extends z.ZodType,
TUiOptions extends z.ZodType,
>(opts: FormFieldExtensionData<TReturnValue, TUiOptions>): FormField {
return OpaqueFormField.createInstance('v1', opts);
}
@@ -0,0 +1,18 @@
/*
* Copyright 2024 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.
*/
export * from './FormFieldBlueprint';
export * from './types';
@@ -0,0 +1,40 @@
/*
* Copyright 2024 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 { z } from 'zod';
import {
CustomFieldValidator,
FieldExtensionComponentProps,
FieldSchema,
} from '@backstage/plugin-scaffolder-react';
/** @alpha */
export type FormFieldExtensionData<
TReturnValue extends z.ZodType = z.ZodType,
TUiOptions extends z.ZodType = z.ZodType,
> = {
name: string;
component: (
props: FieldExtensionComponentProps<
z.output<TReturnValue>,
z.output<TUiOptions>
>,
) => JSX.Element | null;
validation?: CustomFieldValidator<
z.output<TReturnValue>,
z.output<TUiOptions>
>;
schema?: FieldSchema<z.output<TReturnValue>, z.output<TUiOptions>>;
};
@@ -17,3 +17,4 @@ export * from './components';
export * from './lib';
export * from './hooks';
export * from './overridableComponents';
export * from './blueprints';
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright 2024 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 zodToJsonSchema from 'zod-to-json-schema';
import { JSONSchema7 } from 'json-schema';
import { z } from 'zod';
import {
CustomFieldExtensionSchema,
FieldExtensionComponentProps,
} from './extensions';
/** @public */
export function makeFieldSchema<
TReturnType extends z.ZodType,
TUiOptions extends z.ZodType,
>(options: {
output: (zImpl: typeof z) => TReturnType;
uiOptions?: (zImpl: typeof z) => TUiOptions;
}): FieldSchema<z.output<TReturnType>, z.output<TUiOptions>> {
const { output, uiOptions } = options;
return {
TProps: undefined as any,
schema: {
returnValue: zodToJsonSchema(output(z)) as JSONSchema7,
uiOptions: uiOptions && (zodToJsonSchema(uiOptions(z)) as JSONSchema7),
},
// These will be removed - just here for backwards compat whilst we're moving across
type: undefined as any,
uiOptionsType: undefined as any,
};
}
/**
* @public
* FieldSchema encapsulates a JSONSchema7 along with the
* matching FieldExtensionComponentProps type for a field extension.
*/
export interface FieldSchema<TReturn, TUiOptions> {
/** @deprecated use TProps instead */
readonly type: FieldExtensionComponentProps<TReturn, TUiOptions>;
/** @deprecated will be removed */
readonly uiOptionsType: TUiOptions;
readonly schema: CustomFieldExtensionSchema;
readonly TProps: FieldExtensionComponentProps<TReturn, TUiOptions>;
}