编写自定义字段扩展
收集用户的输入是脚手架过程和整个软件模板的重要组成部分。 有时,内置组件和字段不够好,有时你想用更好的输入来丰富用户看到的表单。
这就是Custom Field Extensions
进来
通过它们,您可以展示自己的React
组件,并用它们来控制 JSON 模式的状态,以及提供自己的验证函数来验证数据。
创建字段扩展名
字段扩展是将一个 ID、一个React
组件和一个validation
函数的模块化组合,然后将其传递给Scaffolder
前端插件中的App.tsx
.
您可以使用创建脚本夹字段扩展名API
就像下面这样
例如,我们将创建一个组件,用于验证一个字符串是否位于Kebab-case
模式:
//packages/app/src/scaffolder/ValidateKebabCase/ValidateKebabCaseExtension.tsx
import React from 'react';
import { FieldProps, FieldValidation } from '@rjsf/core';
import FormControl from '@material-ui/core/FormControl';
/*
This is the actual component that will get rendered in the form
*/
export const ValidateKebabCase = ({
onChange,
rawErrors,
required,
formData,
}: FieldProps<string>) => {
return (
<FormControl
margin="normal"
required={required}
error={rawErrors?.length > 0 && !formData}
>
<InputLabel htmlFor="validateName">Name</InputLabel>
<Input
id="validateName"
aria-describedby="entityName"
onChange={e => onChange(e.target?.value)}
/>
<FormHelperText id="entityName">
Use only letters, numbers, hyphens and underscores
</FormHelperText>
</FormControl>
);
};
/*
This is a validation function that will run when the form is submitted.
You will get the value from the `onChange` handler before as the value here to make sure that the types are aligned\
*/
export const validateKebabCaseValidation = (
value: string,
validation: FieldValidation,
) => {
const kebabCase = /^[a-z0-9-_]+$/g.test(value);
if (kebabCase === false) {
validation.addError(
`Only use letters, numbers, hyphen ("-") and underscore ("_").`,
);
}
};
// packages/app/src/scaffolder/ValidateKebabCase/extensions.ts
/*
This is where the magic happens and creates the custom field extension.
Note that if you're writing extensions part of a separate plugin,
then please use `scaffolderPlugin.provide` from there instead and export it part of your `plugin.ts` rather than re-using the `scaffolder.plugin`.
*/
import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react';
import {
ValidateKebabCase,
validateKebabCaseValidation,
} from './ValidateKebabCase/ValidateKebabCaseExtension';
export const ValidateKebabCaseFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
name: 'ValidateKebabCase',
component: ValidateKebabCase,
validation: validateKebabCaseValidation,
}),
);
// packages/app/src/scaffolder/ValidateKebabCase/index.ts
export { ValidateKebabCaseFieldExtension } from './extensions';
所有这些文件就位后,您就需要将自定义扩展名提供给scaffolder
插件。
您可以在packages/app/src/App.tsx
您需要提供customFieldExtensions
作为子女ScaffolderPage
.
const routes = (
<FlatRoutes>
...
<Route path="/create" element={<ScaffolderPage />} />
...
</FlatRoutes>
);
应该是这样的
import { ValidateKebabCaseFieldExtension } from './scaffolder/ValidateKebabCase';
import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder-react';
const routes = (
<FlatRoutes>
...
<Route path="/create" element={<ScaffolderPage />}>
<ScaffolderFieldExtensions>
<ValidateKebabCaseFieldExtension />
</ScaffolderFieldExtensions>
</Route>
...
</FlatRoutes>
);
使用自定义字段扩展
一旦传递到ScaffolderPage
现在您应该可以使用ui:field
属性,将其指向模板中的customFieldExtension
您注册的
差不多是这样
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: Test template
title: Test template with custom extension
description: Test template
spec:
parameters:
- title: Fill in some steps
required:
- name
properties:
name:
title: Name
type: string
description: My custom name for the component
ui:field: ValidateKebabCase
steps:
[...]
从其他字段访问数据
自定义字段扩展可以通过表单上下文从表单中的其他字段读取数据。 由于会产生耦合,我们不鼓励这样做,但有时这仍然是最明智的解决方案。
const CustomFieldExtensionComponent = (props: FieldExtensionComponentProps<string[]>) => {
const { formData } = props.formContext;
...
};
const CustomFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
name: ...,
component: CustomFieldExtensionComponent,
validation: ...
})
);
预览自定义字段扩展
您可以使用自定义字段资源管理器预览您在Backstage用户界面中编写的自定义字段扩展(可通过/create/edit
默认路由):
为了在资源管理器中使用新的自定义字段扩展,您必须定义一个 JSON 模式,以描述字 段的输入/输出类型,如下例所示:
//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx
export const MyCustomExtensionWithOptionsSchema = {
uiOptions: {
type: 'object',
properties: {
focused: {
description: 'Whether to focus this field',
type: 'boolean',
},
},
},
returnValue: { type: 'string' },
};
export const MyCustomExtensionWithOptions = ({
onChange,
rawErrors,
required,
formData,
}: FieldExtensionComponentProps<string, { focused?: boolean }>) => {
return (
<FormControl
margin="normal"
required={required}
error={rawErrors?.length > 0 && !formData}
onChange={onChange}
focused={focused}
/>
);
};
// packages/app/src/scaffolder/MyCustomExtensionWithOptions/extensions.ts
...
import { MyCustomExtensionWithOptions, MyCustomExtensionWithOptionsSchema } from './MyCustomExtensionWithOptions';
export const MyCustomFieldWithOptionsExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
name: 'MyCustomExtensionWithOptions',
component: MyCustomExtensionWithOptions,
schema: MyCustomExtensionWithOptionsSchema,
}),
);
我们建议使用一个库,如日蚀来定义您的模式,并使用所提供的makeFieldSchemaFromZod
辅助实用程序为字段道具生成 JSON 模式和类型,以避免重复定义:
//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx
...
import { z } from 'zod';
import { makeFieldSchemaFromZod } from '@backstage/plugin-scaffolder';
const MyCustomExtensionWithOptionsFieldSchema = makeFieldSchemaFromZod(
z.string(),
z.object({
focused: z
.boolean()
.optional()
.describe('Whether to focus this field'),
}),
);
export const MyCustomExtensionWithOptionsSchema = MyCustomExtensionWithOptionsFieldSchema.schema;
type MyCustomExtensionWithOptionsProps = typeof MyCustomExtensionWithOptionsFieldSchema.type;
export const MyCustomExtensionWithOptions = ({
onChange,
rawErrors,
required,
formData,
}: MyCustomExtensionWithOptionsProps) => {
return (
<FormControl
margin="normal"
required={required}
error={rawErrors?.length > 0 && !formData}
onChange={onChange}
focused={focused}
/>
);
};