Merge remote-tracking branch 'origin/master' into configure-template
# Conflicts: # plugins/scaffolder-react/api-report.md # plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx
This commit is contained in:
@@ -15,4 +15,4 @@
|
||||
*/
|
||||
|
||||
export * from './next';
|
||||
export type { FormProps } from './next';
|
||||
export * from './legacy';
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2023 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 './types';
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2023 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 { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import type { FormProps as SchemaFormProps } from '@rjsf/core';
|
||||
import { UiSchema } from '@rjsf/utils';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
|
||||
/** @public */
|
||||
export type TemplateGroupFilter = {
|
||||
title?: React.ReactNode;
|
||||
filter: (entity: TemplateEntityV1beta3) => boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Any `@rjsf/core` form properties that are publicly exposed to the `ScaffolderPage`
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FormProps = Pick<
|
||||
SchemaFormProps,
|
||||
'transformErrors' | 'noHtml5Validate'
|
||||
>;
|
||||
|
||||
/**
|
||||
* The props for the Last Step in scaffolder template form.
|
||||
* Which represents the summary of the input provided by the end user.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ReviewStepProps = {
|
||||
disableButtons: boolean;
|
||||
formData: JsonObject;
|
||||
handleBack: () => void;
|
||||
handleReset: () => void;
|
||||
handleCreate: () => void;
|
||||
steps: {
|
||||
uiSchema: UiSchema;
|
||||
mergedSchema: JsonObject;
|
||||
schema: JsonObject;
|
||||
}[];
|
||||
};
|
||||
@@ -14,23 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
CustomFieldExtensionSchema,
|
||||
CustomFieldValidator,
|
||||
FieldExtensionOptions,
|
||||
FieldExtensionComponentProps,
|
||||
FieldExtensionUiSchema,
|
||||
CustomFieldExtensionSchema,
|
||||
} from './types';
|
||||
import { Extension, attachComponentData } from '@backstage/core-plugin-api';
|
||||
import { UIOptionsType } from '@rjsf/utils';
|
||||
import { FIELD_EXTENSION_KEY, FIELD_EXTENSION_WRAPPER_KEY } from './keys';
|
||||
|
||||
/**
|
||||
* The type used to wrap up the Layout and embed the input props
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FieldExtensionComponent<_TReturnValue, _TInputProps> = () => null;
|
||||
|
||||
/**
|
||||
* Method for creating field extensions that can be used in the scaffolder
|
||||
* frontend form.
|
||||
@@ -38,7 +32,7 @@ export type FieldExtensionComponent<_TReturnValue, _TInputProps> = () => null;
|
||||
*/
|
||||
export function createScaffolderFieldExtension<
|
||||
TReturnValue = unknown,
|
||||
TInputProps = unknown,
|
||||
TInputProps extends UIOptionsType = {},
|
||||
>(
|
||||
options: FieldExtensionOptions<TReturnValue, TInputProps>,
|
||||
): Extension<FieldExtensionComponent<TReturnValue, TInputProps>> {
|
||||
@@ -72,9 +66,19 @@ attachComponentData(
|
||||
true,
|
||||
);
|
||||
|
||||
/**
|
||||
* The type used to wrap up the Layout and embed the input props
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FieldExtensionComponent<_TReturnValue, _TInputProps> = () => null;
|
||||
|
||||
export type {
|
||||
CustomFieldExtensionSchema,
|
||||
CustomFieldValidator,
|
||||
FieldExtensionOptions,
|
||||
FieldExtensionComponentProps,
|
||||
FieldExtensionUiSchema,
|
||||
CustomFieldExtensionSchema,
|
||||
};
|
||||
|
||||
export * from './rjsf';
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright 2023 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 { ComponentType, ElementType, FormEvent, ReactNode, Ref } from 'react';
|
||||
import {
|
||||
ErrorSchema,
|
||||
FormContextType,
|
||||
GenericObjectType,
|
||||
IdSchema,
|
||||
Registry,
|
||||
RJSFSchema,
|
||||
StrictRJSFSchema,
|
||||
UiSchema,
|
||||
ValidatorType,
|
||||
TemplatesType,
|
||||
RegistryWidgetsType,
|
||||
RJSFValidationError,
|
||||
CustomValidator,
|
||||
Experimental_DefaultFormStateBehavior,
|
||||
ErrorTransformer,
|
||||
} from '@rjsf/utils';
|
||||
import { HTMLAttributes } from 'react';
|
||||
import Form, { IChangeEvent } from '@rjsf/core';
|
||||
|
||||
/**
|
||||
* The props for the `Field` components
|
||||
* @public
|
||||
*/
|
||||
export interface ScaffolderRJSFFieldProps<
|
||||
T = any,
|
||||
S extends StrictRJSFSchema = RJSFSchema,
|
||||
F extends FormContextType = any,
|
||||
> extends GenericObjectType,
|
||||
Pick<
|
||||
HTMLAttributes<HTMLElement>,
|
||||
Exclude<
|
||||
keyof HTMLAttributes<HTMLElement>,
|
||||
'onBlur' | 'onFocus' | 'onChange'
|
||||
>
|
||||
> {
|
||||
/** The JSON subschema object for this field */
|
||||
schema: S;
|
||||
/** The uiSchema for this field */
|
||||
uiSchema: UiSchema<T, S, F>;
|
||||
/** The tree of unique ids for every child field */
|
||||
idSchema: IdSchema<T>;
|
||||
/** The data for this field */
|
||||
formData: T;
|
||||
/** The tree of errors for this field and its children */
|
||||
errorSchema?: ErrorSchema<T>;
|
||||
/** The field change event handler; called with the updated form data and an optional `ErrorSchema` */
|
||||
onChange: (
|
||||
newFormData: T | undefined,
|
||||
es?: ErrorSchema<T>,
|
||||
id?: string,
|
||||
) => any;
|
||||
/** The input blur event handler; call it with the field id and value */
|
||||
onBlur: (id: string, value: any) => void;
|
||||
/** The input focus event handler; call it with the field id and value */
|
||||
onFocus: (id: string, value: any) => void;
|
||||
/** The `formContext` object that you passed to `Form` */
|
||||
formContext?: F;
|
||||
/** A boolean value stating if the field should autofocus */
|
||||
autofocus?: boolean;
|
||||
/** A boolean value stating if the field is disabled */
|
||||
disabled: boolean;
|
||||
/** A boolean value stating if the field is hiding its errors */
|
||||
hideError?: boolean;
|
||||
/** A boolean value stating if the field is read-only */
|
||||
readonly: boolean;
|
||||
/** The required status of this field */
|
||||
required?: boolean;
|
||||
/** The unique name of the field, usually derived from the name of the property in the JSONSchema */
|
||||
name: string;
|
||||
/** To avoid collisions with existing ids in the DOM, it is possible to change the prefix used for ids;
|
||||
* Default is `root`
|
||||
*/
|
||||
idPrefix?: string;
|
||||
/** To avoid using a path separator that is present in field names, it is possible to change the separator used for
|
||||
* ids (Default is `_`)
|
||||
*/
|
||||
idSeparator?: string;
|
||||
/** An array of strings listing all generated error messages from encountered errors for this field */
|
||||
rawErrors: string[];
|
||||
/** The `registry` object */
|
||||
registry: Registry<T, S, F>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The properties that are passed to the `Form`
|
||||
* @public
|
||||
*/
|
||||
export interface ScaffolderRJSFFormProps<
|
||||
T = any,
|
||||
S extends StrictRJSFSchema = RJSFSchema,
|
||||
F extends FormContextType = any,
|
||||
> {
|
||||
/** The JSON schema object for the form */
|
||||
schema: S;
|
||||
/** An implementation of the `ValidatorType` interface that is needed for form validation to work */
|
||||
validator: ValidatorType<T, S, F>;
|
||||
/** The optional children for the form, if provided, it will replace the default `SubmitButton` */
|
||||
children?: ReactNode;
|
||||
/** The uiSchema for the form */
|
||||
uiSchema?: UiSchema<T, S, F>;
|
||||
/** The data for the form, used to prefill a form with existing data */
|
||||
formData?: T;
|
||||
/** You can provide a `formContext` object to the form, which is passed down to all fields and widgets. Useful for
|
||||
* implementing context aware fields and widgets.
|
||||
*
|
||||
* NOTE: Setting `{readonlyAsDisabled: false}` on the formContext will make the antd theme treat readOnly fields as
|
||||
* disabled.
|
||||
*/
|
||||
formContext?: F;
|
||||
/** To avoid collisions with existing ids in the DOM, it is possible to change the prefix used for ids;
|
||||
* Default is `root`
|
||||
*/
|
||||
idPrefix?: string;
|
||||
/** To avoid using a path separator that is present in field names, it is possible to change the separator used for
|
||||
* ids (Default is `_`)
|
||||
*/
|
||||
idSeparator?: string;
|
||||
/** It's possible to disable the whole form by setting the `disabled` prop. The `disabled` prop is then forwarded down
|
||||
* to each field of the form. If you just want to disable some fields, see the `ui:disabled` parameter in `uiSchema`
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/** It's possible to make the whole form read-only by setting the `readonly` prop. The `readonly` prop is then
|
||||
* forwarded down to each field of the form. If you just want to make some fields read-only, see the `ui:readonly`
|
||||
* parameter in `uiSchema`
|
||||
*/
|
||||
readonly?: boolean;
|
||||
/** The dictionary of registered fields in the form */
|
||||
fields?: ScaffolderRJSFRegistryFieldsType<T, S, F>;
|
||||
/** The dictionary of registered templates in the form; Partial allows a subset to be provided beyond the defaults */
|
||||
templates?: Partial<Omit<TemplatesType<T, S, F>, 'ButtonTemplates'>> & {
|
||||
ButtonTemplates?: Partial<TemplatesType<T, S, F>['ButtonTemplates']>;
|
||||
};
|
||||
/** The dictionary of registered widgets in the form */
|
||||
widgets?: RegistryWidgetsType<T, S, F>;
|
||||
/** If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will
|
||||
* receive the same args as `onSubmit` any time a value is updated in the form. Can also return the `id` of the field
|
||||
* that caused the change
|
||||
*/
|
||||
onChange?: (data: IChangeEvent<T, S, F>, id?: string) => void;
|
||||
/** To react when submitted form data are invalid, pass an `onError` handler. It will be passed the list of
|
||||
* encountered errors
|
||||
*/
|
||||
onError?: (errors: RJSFValidationError[]) => void;
|
||||
/** You can pass a function as the `onSubmit` prop of your `Form` component to listen to when the form is submitted
|
||||
* and its data are valid. It will be passed a result object having a `formData` attribute, which is the valid form
|
||||
* data you're usually after. The original event will also be passed as a second parameter
|
||||
*/
|
||||
onSubmit?: (data: IChangeEvent<T, S, F>, event: FormEvent<any>) => void;
|
||||
/** Sometimes you may want to trigger events or modify external state when a field has been touched, so you can pass
|
||||
* an `onBlur` handler, which will receive the id of the input that was blurred and the field value
|
||||
*/
|
||||
onBlur?: (id: string, data: any) => void;
|
||||
/** Sometimes you may want to trigger events or modify external state when a field has been focused, so you can pass
|
||||
* an `onFocus` handler, which will receive the id of the input that is focused and the field value
|
||||
*/
|
||||
onFocus?: (id: string, data: any) => void;
|
||||
/** The value of this prop will be passed to the `accept-charset` HTML attribute on the form */
|
||||
acceptcharset?: string;
|
||||
/** The value of this prop will be passed to the `action` HTML attribute on the form
|
||||
*
|
||||
* NOTE: this just renders the `action` attribute in the HTML markup. There is no real network request being sent to
|
||||
* this `action` on submit. Instead, react-jsonschema-form catches the submit event with `event.preventDefault()`
|
||||
* and then calls the `onSubmit` function, where you could send a request programmatically with `fetch` or similar.
|
||||
*/
|
||||
action?: string;
|
||||
/** The value of this prop will be passed to the `autocomplete` HTML attribute on the form */
|
||||
autoComplete?: string;
|
||||
/** The value of this prop will be passed to the `class` HTML attribute on the form */
|
||||
className?: string;
|
||||
/** The value of this prop will be passed to the `enctype` HTML attribute on the form */
|
||||
enctype?: string;
|
||||
/** The value of this prop will be passed to the `id` HTML attribute on the form */
|
||||
id?: string;
|
||||
/** The value of this prop will be passed to the `name` HTML attribute on the form */
|
||||
name?: string;
|
||||
/** The value of this prop will be passed to the `method` HTML attribute on the form */
|
||||
method?: string;
|
||||
/** It's possible to change the default `form` tag name to a different HTML tag, which can be helpful if you are
|
||||
* nesting forms. However, native browser form behaviour, such as submitting when the `Enter` key is pressed, may no
|
||||
* longer work
|
||||
*/
|
||||
tagName?: ElementType;
|
||||
/** The value of this prop will be passed to the `target` HTML attribute on the form */
|
||||
target?: string;
|
||||
/** Formerly the `validate` prop; Takes a function that specifies custom validation rules for the form */
|
||||
customValidate?: CustomValidator<T, S, F>;
|
||||
/** This prop allows passing in custom errors that are augmented with the existing JSON Schema errors on the form; it
|
||||
* can be used to implement asynchronous validation
|
||||
*/
|
||||
extraErrors?: ErrorSchema<T>;
|
||||
/** If set to true, turns off HTML5 validation on the form; Set to `false` by default */
|
||||
noHtml5Validate?: boolean;
|
||||
/** If set to true, turns off all validation. Set to `false` by default
|
||||
*
|
||||
* @deprecated - In a future release, this switch may be replaced by making `validator` prop optional
|
||||
*/
|
||||
noValidate?: boolean;
|
||||
/** If set to true, the form will perform validation and show any validation errors whenever the form data is changed,
|
||||
* rather than just on submit
|
||||
*/
|
||||
liveValidate?: boolean;
|
||||
/** If `omitExtraData` and `liveOmit` are both set to true, then extra form data values that are not in any form field
|
||||
* will be removed whenever `onChange` is called. Set to `false` by default
|
||||
*/
|
||||
liveOmit?: boolean;
|
||||
/** If set to true, then extra form data values that are not in any form field will be removed whenever `onSubmit` is
|
||||
* called. Set to `false` by default.
|
||||
*/
|
||||
omitExtraData?: boolean;
|
||||
/** When this prop is set to `top` or 'bottom', a list of errors (or the custom error list defined in the `ErrorList`) will also
|
||||
* show. When set to false, only inline input validation errors will be shown. Set to `top` by default
|
||||
*/
|
||||
showErrorList?: false | 'top' | 'bottom';
|
||||
/** A function can be passed to this prop in order to make modifications to the default errors resulting from JSON
|
||||
* Schema validation
|
||||
*/
|
||||
transformErrors?: ErrorTransformer<T, S, F>;
|
||||
/** If set to true, then the first field with an error will receive the focus when the form is submitted with errors
|
||||
*/
|
||||
focusOnFirstError?: boolean | ((error: RJSFValidationError) => void);
|
||||
/** Optional string translation function, if provided, allows users to change the translation of the RJSF internal
|
||||
* strings. Some strings contain replaceable parameter values as indicated by `%1`, `%2`, etc. The number after the
|
||||
* `%` indicates the order of the parameter. The ordering of parameters is important because some languages may choose
|
||||
* to put the second parameter before the first in its translation.
|
||||
*/
|
||||
translateString?: Registry['translateString'];
|
||||
/** Optional configuration object with flags, if provided, allows users to override default form state behavior
|
||||
* Currently only affecting minItems on array fields and handling of setting defaults based on the value of
|
||||
* `emptyObjectFields`
|
||||
*/
|
||||
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior;
|
||||
/**
|
||||
* _internalFormWrapper is currently used by the semantic-ui theme to provide a custom wrapper around `<Form />`
|
||||
* that supports the proper rendering of those themes. To use this prop, one must pass a component that takes two
|
||||
* props: `children` and `as`. That component, at minimum, should render the `children` inside of a <form /> tag
|
||||
* unless `as` is provided, in which case, use the `as` prop in place of `<form />`.
|
||||
* i.e.:
|
||||
* ```
|
||||
* export default function InternalForm({ children, as }) {
|
||||
* const FormTag = as || 'form';
|
||||
* return <FormTag>{children}</FormTag>;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Use at your own risk as this prop is private and may change at any time without notice.
|
||||
*/
|
||||
_internalFormWrapper?: ElementType;
|
||||
/** Support receiving a React ref to the Form
|
||||
*/
|
||||
ref?: Ref<Form<T, S, F>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of `Fields` stored in the `Registry`
|
||||
* @public
|
||||
*/
|
||||
export type ScaffolderRJSFRegistryFieldsType<
|
||||
T = any,
|
||||
S extends StrictRJSFSchema = RJSFSchema,
|
||||
F extends FormContextType = any,
|
||||
> = {
|
||||
/** A `Field` indexed by `name` */
|
||||
[name: string]: ScaffolderRJSFField<T, S, F>;
|
||||
};
|
||||
|
||||
/**
|
||||
* The `Field` type for Field Extensions
|
||||
* @public
|
||||
*/
|
||||
export type ScaffolderRJSFField<
|
||||
T = any,
|
||||
S extends StrictRJSFSchema = RJSFSchema,
|
||||
F extends FormContextType = any,
|
||||
> = ComponentType<ScaffolderRJSFFieldProps<T, S, F>>;
|
||||
@@ -14,19 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ApiHolder } from '@backstage/core-plugin-api';
|
||||
import { FieldValidation, FieldProps } from '@rjsf/core';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { UiSchema, UIOptionsType, FieldValidation } from '@rjsf/utils';
|
||||
import { ScaffolderRJSFFieldProps } from './rjsf';
|
||||
|
||||
/**
|
||||
* Field validation type for Custom Field Extensions.
|
||||
* Type for Field Extension Props for RJSF v5
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CustomFieldValidator<TFieldReturnValue> = (
|
||||
data: TFieldReturnValue,
|
||||
field: FieldValidation,
|
||||
context: { apiHolder: ApiHolder },
|
||||
) => void | Promise<void>;
|
||||
export interface FieldExtensionComponentProps<
|
||||
TFieldReturnValue,
|
||||
TUiOptions = {},
|
||||
> extends PropsWithChildren<ScaffolderRJSFFieldProps<TFieldReturnValue>> {
|
||||
uiSchema: FieldExtensionUiSchema<TFieldReturnValue, TUiOptions>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for the Custom Field Extension schema.
|
||||
@@ -38,6 +42,32 @@ export type CustomFieldExtensionSchema = {
|
||||
uiOptions?: JSONSchema7;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type for Field Extension UiSchema
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface FieldExtensionUiSchema<TFieldReturnValue, TUiOptions>
|
||||
extends UiSchema<TFieldReturnValue> {
|
||||
'ui:options'?: TUiOptions & UIOptionsType<TFieldReturnValue>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Field validation type for Custom Field Extensions.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CustomFieldValidator<TFieldReturnValue, TUiOptions = unknown> = (
|
||||
data: TFieldReturnValue,
|
||||
field: FieldValidation,
|
||||
context: {
|
||||
apiHolder: ApiHolder;
|
||||
formData: JsonObject;
|
||||
schema: JsonObject;
|
||||
uiSchema?: FieldExtensionUiSchema<TFieldReturnValue, TUiOptions>;
|
||||
},
|
||||
) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Type for the Custom Field Extension with the
|
||||
* name and components and validation function.
|
||||
@@ -46,27 +76,12 @@ export type CustomFieldExtensionSchema = {
|
||||
*/
|
||||
export type FieldExtensionOptions<
|
||||
TFieldReturnValue = unknown,
|
||||
TInputProps = unknown,
|
||||
TUiOptions = unknown,
|
||||
> = {
|
||||
name: string;
|
||||
component: (
|
||||
props: FieldExtensionComponentProps<TFieldReturnValue, TInputProps>,
|
||||
props: FieldExtensionComponentProps<TFieldReturnValue, TUiOptions>,
|
||||
) => JSX.Element | null;
|
||||
validation?: CustomFieldValidator<TFieldReturnValue>;
|
||||
validation?: CustomFieldValidator<TFieldReturnValue, TUiOptions>;
|
||||
schema?: CustomFieldExtensionSchema;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type for field extensions and being able to type
|
||||
* incoming props easier.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface FieldExtensionComponentProps<
|
||||
TFieldReturnValue,
|
||||
TUiOptions = unknown,
|
||||
> extends FieldProps<TFieldReturnValue> {
|
||||
uiSchema: FieldProps['uiSchema'] & {
|
||||
'ui:options'?: TUiOptions;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
export * from './extensions';
|
||||
export * from './components';
|
||||
export * from './types';
|
||||
export * from './secrets';
|
||||
export * from './api';
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { LAYOUTS_KEY, LAYOUTS_WRAPPER_KEY } from './keys';
|
||||
import { attachComponentData, Extension } from '@backstage/core-plugin-api';
|
||||
import type { FormProps as SchemaFormProps } from '@rjsf/core-v5';
|
||||
import type { FormProps as SchemaFormProps } from '@rjsf/core';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
|
||||
+9
-12
@@ -15,13 +15,11 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
NextCustomFieldValidator,
|
||||
NextFieldExtensionOptions,
|
||||
NextFieldExtensionComponentProps,
|
||||
NextFieldExtensionUiSchema,
|
||||
LegacyCustomFieldValidator,
|
||||
LegacyFieldExtensionOptions,
|
||||
LegacyFieldExtensionComponentProps,
|
||||
} from './types';
|
||||
import { Extension, attachComponentData } from '@backstage/core-plugin-api';
|
||||
import { UIOptionsType } from '@rjsf/utils';
|
||||
import { FIELD_EXTENSION_KEY } from '../../extensions/keys';
|
||||
import { FieldExtensionComponent } from '@backstage/plugin-scaffolder-react';
|
||||
|
||||
@@ -30,11 +28,11 @@ import { FieldExtensionComponent } from '@backstage/plugin-scaffolder-react';
|
||||
* frontend form.
|
||||
* @alpha
|
||||
*/
|
||||
export function createNextScaffolderFieldExtension<
|
||||
export function createLegacyScaffolderFieldExtension<
|
||||
TReturnValue = unknown,
|
||||
TInputProps extends UIOptionsType = {},
|
||||
TInputProps = unknown,
|
||||
>(
|
||||
options: NextFieldExtensionOptions<TReturnValue, TInputProps>,
|
||||
options: LegacyFieldExtensionOptions<TReturnValue, TInputProps>,
|
||||
): Extension<FieldExtensionComponent<TReturnValue, TInputProps>> {
|
||||
return {
|
||||
expose() {
|
||||
@@ -52,8 +50,7 @@ export function createNextScaffolderFieldExtension<
|
||||
}
|
||||
|
||||
export type {
|
||||
NextCustomFieldValidator,
|
||||
NextFieldExtensionOptions,
|
||||
NextFieldExtensionComponentProps,
|
||||
NextFieldExtensionUiSchema,
|
||||
LegacyCustomFieldValidator,
|
||||
LegacyFieldExtensionOptions,
|
||||
LegacyFieldExtensionComponentProps,
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 { ApiHolder } from '@backstage/core-plugin-api';
|
||||
import { FieldValidation } from '@rjsf/utils';
|
||||
import {
|
||||
CustomFieldExtensionSchema,
|
||||
ScaffolderRJSFFieldProps,
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
|
||||
/**
|
||||
* Field validation type for Custom Field Extensions.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type LegacyCustomFieldValidator<TFieldReturnValue> = (
|
||||
data: TFieldReturnValue,
|
||||
field: FieldValidation,
|
||||
context: { apiHolder: ApiHolder },
|
||||
) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Type for the Custom Field Extension with the
|
||||
* name and components and validation function.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type LegacyFieldExtensionOptions<
|
||||
TFieldReturnValue = unknown,
|
||||
TInputProps = unknown,
|
||||
> = {
|
||||
name: string;
|
||||
component: (
|
||||
props: LegacyFieldExtensionComponentProps<TFieldReturnValue, TInputProps>,
|
||||
) => JSX.Element | null;
|
||||
validation?: LegacyCustomFieldValidator<TFieldReturnValue>;
|
||||
schema?: CustomFieldExtensionSchema;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type for field extensions and being able to type
|
||||
* incoming props easier.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface LegacyFieldExtensionComponentProps<
|
||||
TFieldReturnValue,
|
||||
TUiOptions = unknown,
|
||||
> extends ScaffolderRJSFFieldProps<TFieldReturnValue> {
|
||||
uiSchema: ScaffolderRJSFFieldProps['uiSchema'] & {
|
||||
'ui:options'?: TUiOptions;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2023 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 './extensions';
|
||||
@@ -14,26 +14,47 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormProps, withTheme } from '@rjsf/core-v5';
|
||||
import { withTheme } from '@rjsf/core';
|
||||
import React from 'react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { FieldTemplate } from './FieldTemplate';
|
||||
import { DescriptionFieldTemplate } from './DescriptionFieldTemplate';
|
||||
import { FieldProps } from '@rjsf/utils';
|
||||
import { ScaffolderRJSFFormProps } from '@backstage/plugin-scaffolder-react';
|
||||
import { Theme as MuiTheme } from '@rjsf/material-ui';
|
||||
|
||||
// TODO(blam): We require here, as the types in this package depend on @rjsf/core explicitly
|
||||
// which is what we're using here as the default types, it needs to depend on @rjsf/core-v5 because
|
||||
// of the re-writing we're doing. Once we've migrated, we can import this the exact same as before.
|
||||
const WrappedForm = withTheme(require('@rjsf/material-ui-v5').Theme);
|
||||
const WrappedForm = withTheme(MuiTheme);
|
||||
|
||||
/**
|
||||
* The Form component
|
||||
* @alpha
|
||||
*/
|
||||
export const Form = (props: PropsWithChildren<FormProps>) => {
|
||||
export const Form = (props: PropsWithChildren<ScaffolderRJSFFormProps>) => {
|
||||
// This is where we unbreak the changes from RJSF, and make it work with our custom fields so we don't pass on this
|
||||
// breaking change to our users. We will look more into a better API for this in scaffolderv2.
|
||||
const wrappedFields = Object.fromEntries(
|
||||
Object.entries(props.fields ?? {}).map(([key, Component]) => [
|
||||
key,
|
||||
(wrapperProps: FieldProps) => {
|
||||
return (
|
||||
<Component
|
||||
{...wrapperProps}
|
||||
uiSchema={wrapperProps.uiSchema ?? {}}
|
||||
formData={wrapperProps.formData}
|
||||
rawErrors={wrapperProps.rawErrors ?? []}
|
||||
/>
|
||||
);
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const templates = {
|
||||
FieldTemplate,
|
||||
DescriptionFieldTemplate,
|
||||
...props.templates,
|
||||
};
|
||||
return <WrappedForm {...props} templates={templates} />;
|
||||
|
||||
return (
|
||||
<WrappedForm {...props} templates={templates} fields={wrappedFields} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import type { RJSFValidationError } from '@rjsf/utils';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { NextFieldExtensionComponentProps } from '../../extensions';
|
||||
import { FieldExtensionComponentProps } from '../../../extensions';
|
||||
import { LayoutTemplate } from '../../../layouts';
|
||||
|
||||
describe('Stepper', () => {
|
||||
@@ -115,7 +115,7 @@ describe('Stepper', () => {
|
||||
it('should merge nested formData correctly in multiple steps', async () => {
|
||||
const Repo = ({
|
||||
onChange,
|
||||
}: NextFieldExtensionComponentProps<{ repository: string }, any>) => (
|
||||
}: FieldExtensionComponentProps<{ repository: string }, any>) => (
|
||||
<input
|
||||
aria-label="repo"
|
||||
type="text"
|
||||
@@ -126,7 +126,7 @@ describe('Stepper', () => {
|
||||
|
||||
const Owner = ({
|
||||
onChange,
|
||||
}: NextFieldExtensionComponentProps<{ owner: string }, any>) => (
|
||||
}: FieldExtensionComponentProps<{ owner: string }, any>) => (
|
||||
<input
|
||||
aria-label="owner"
|
||||
type="text"
|
||||
@@ -273,12 +273,12 @@ describe('Stepper', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(getByRole('button', { name: 'Review' }));
|
||||
|
||||
expect(getByRole('progressbar')).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: 'Review' })).toBeDisabled();
|
||||
act(() => {
|
||||
fireEvent.click(getByRole('button', { name: 'Review' }));
|
||||
});
|
||||
|
||||
expect(getByRole('progressbar')).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: 'Review' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should transform default error message', async () => {
|
||||
@@ -312,7 +312,7 @@ describe('Stepper', () => {
|
||||
manifest={manifest}
|
||||
extensions={[]}
|
||||
onCreate={jest.fn()}
|
||||
FormProps={{ transformErrors }}
|
||||
formProps={{ transformErrors }}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -23,18 +23,23 @@ import {
|
||||
makeStyles,
|
||||
LinearProgress,
|
||||
} from '@material-ui/core';
|
||||
import { type IChangeEvent } from '@rjsf/core-v5';
|
||||
import { type IChangeEvent } from '@rjsf/core';
|
||||
import { ErrorSchema } from '@rjsf/utils';
|
||||
import React, { useCallback, useMemo, useState, type ReactNode } from 'react';
|
||||
import { NextFieldExtensionOptions } from '../../extensions';
|
||||
import React, {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
ComponentType,
|
||||
} from 'react';
|
||||
import {
|
||||
createAsyncValidators,
|
||||
type FormValidation,
|
||||
} from './createAsyncValidators';
|
||||
import { ReviewState, type ReviewStateProps } from '../ReviewState';
|
||||
import { useTemplateSchema, useFormDataFromQuery } from '../../hooks';
|
||||
import { useTemplateSchema } from '../../hooks/useTemplateSchema';
|
||||
import validator from '@rjsf/validator-ajv8';
|
||||
import { FormProps } from '../../types';
|
||||
import { useFormDataFromQuery } from '../../hooks';
|
||||
import { useTransformSchemaToProps } from '../../hooks/useTransformSchemaToProps';
|
||||
import { hasErrors } from './utils';
|
||||
import * as FieldOverrides from './FieldOverrides';
|
||||
@@ -42,7 +47,10 @@ import { Form } from '../Form';
|
||||
import {
|
||||
TemplateParameterSchema,
|
||||
LayoutOptions,
|
||||
FieldExtensionOptions,
|
||||
FormProps,
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
import { ReviewStepProps } from '@backstage/plugin-scaffolder-react';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
backButton: {
|
||||
@@ -65,14 +73,14 @@ const useStyles = makeStyles(theme => ({
|
||||
*/
|
||||
export type StepperProps = {
|
||||
manifest: TemplateParameterSchema;
|
||||
extensions: NextFieldExtensionOptions<any, any>[];
|
||||
extensions: FieldExtensionOptions<any, any>[];
|
||||
templateName?: string;
|
||||
FormProps?: FormProps;
|
||||
formProps?: FormProps;
|
||||
initialState?: Record<string, JsonValue>;
|
||||
onCreate: (values: Record<string, JsonValue>) => Promise<void>;
|
||||
components?: {
|
||||
ReviewStepComponent?: ComponentType<ReviewStepProps>;
|
||||
ReviewStateComponent?: (props: ReviewStateProps) => JSX.Element;
|
||||
backButtonText?: ReactNode;
|
||||
createButtonText?: ReactNode;
|
||||
reviewButtonText?: ReactNode;
|
||||
};
|
||||
@@ -87,12 +95,12 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
const { layouts = [], components = {}, ...props } = stepperProps;
|
||||
const {
|
||||
ReviewStateComponent = ReviewState,
|
||||
backButtonText = 'Back',
|
||||
ReviewStepComponent,
|
||||
createButtonText = 'Create',
|
||||
reviewButtonText = 'Review',
|
||||
} = components;
|
||||
const analytics = useAnalytics();
|
||||
const { configurations, steps } = useTemplateSchema(props.manifest);
|
||||
const { steps } = useTemplateSchema(props.manifest);
|
||||
const apiHolder = useApiHolder();
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
@@ -129,6 +137,13 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
[setFormState],
|
||||
);
|
||||
|
||||
const handleCreate = useCallback(() => {
|
||||
props.onCreate(formState);
|
||||
const name =
|
||||
typeof formState.name === 'string' ? formState.name : undefined;
|
||||
analytics.captureEvent('create', name ?? props.templateName ?? 'unknown');
|
||||
}, [props, formState, analytics]);
|
||||
|
||||
const currentStep = useTransformSchemaToProps(steps[activeStep], { layouts });
|
||||
|
||||
const handleNext = async ({
|
||||
@@ -158,13 +173,6 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
setFormState(current => ({ ...current, ...formData }));
|
||||
};
|
||||
|
||||
const backLabel =
|
||||
configurations?.buttonLabels?.backButtonText ?? backButtonText;
|
||||
const createLabel =
|
||||
configurations?.buttonLabels?.createButtonText ?? createButtonText;
|
||||
const reviewLabel =
|
||||
configurations?.buttonLabels?.reviewButtonText ?? reviewButtonText;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isValidating && <LinearProgress variant="indeterminate" />}
|
||||
@@ -175,10 +183,11 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
</MuiStep>
|
||||
))}
|
||||
<MuiStep>
|
||||
<MuiStepLabel>${reviewLabel}</MuiStepLabel>
|
||||
<MuiStepLabel>Review</MuiStepLabel>
|
||||
</MuiStep>
|
||||
</MuiStepper>
|
||||
<div className={styles.formWrapper}>
|
||||
{/* eslint-disable-next-line no-nested-ternary */}
|
||||
{activeStep < steps.length ? (
|
||||
<Form
|
||||
validator={validator}
|
||||
@@ -191,7 +200,7 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
fields={{ ...FieldOverrides, ...extensions }}
|
||||
showErrorList={false}
|
||||
onChange={handleChange}
|
||||
{...(props.FormProps ?? {})}
|
||||
{...(props.formProps ?? {})}
|
||||
>
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
@@ -199,7 +208,7 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
className={styles.backButton}
|
||||
disabled={activeStep < 1 || isValidating}
|
||||
>
|
||||
{backLabel}
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -207,10 +216,20 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
type="submit"
|
||||
disabled={isValidating}
|
||||
>
|
||||
{activeStep === steps.length - 1 ? reviewLabel : 'Next'}
|
||||
{activeStep === steps.length - 1 ? reviewButtonText : 'Next'}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
) : // TODO: potentially move away from this pattern, deprecate?
|
||||
ReviewStepComponent ? (
|
||||
<ReviewStepComponent
|
||||
disableButtons={isValidating}
|
||||
formData={formState}
|
||||
handleBack={handleBack}
|
||||
handleReset={() => {}}
|
||||
steps={steps}
|
||||
handleCreate={handleCreate}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<ReviewStateComponent formState={formState} schemas={steps} />
|
||||
@@ -220,24 +239,14 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
className={styles.backButton}
|
||||
disabled={activeStep < 1}
|
||||
>
|
||||
{configurations?.buttonLabels?.backButtonText ?? backButtonText}
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
props.onCreate(formState);
|
||||
const name =
|
||||
typeof formState.name === 'string'
|
||||
? formState.name
|
||||
: undefined;
|
||||
analytics.captureEvent(
|
||||
'create',
|
||||
name ?? props.templateName ?? 'unknown',
|
||||
);
|
||||
}}
|
||||
onClick={handleCreate}
|
||||
>
|
||||
{createLabel}
|
||||
{createButtonText}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { NextCustomFieldValidator } from '../../extensions';
|
||||
import { CustomFieldValidator } from '../../../extensions';
|
||||
import { createAsyncValidators } from './createAsyncValidators';
|
||||
|
||||
describe('createAsyncValidators', () => {
|
||||
@@ -158,16 +158,13 @@ describe('createAsyncValidators', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const NameField: NextCustomFieldValidator<string> = (
|
||||
value,
|
||||
{ addError },
|
||||
) => {
|
||||
const NameField: CustomFieldValidator<string> = (value, { addError }) => {
|
||||
if (!value) {
|
||||
addError('something is broken here!');
|
||||
}
|
||||
};
|
||||
|
||||
const AddressField: NextCustomFieldValidator<{
|
||||
const AddressField: CustomFieldValidator<{
|
||||
street?: string;
|
||||
postcode?: string;
|
||||
}> = (value, { addError }) => {
|
||||
@@ -183,8 +180,8 @@ describe('createAsyncValidators', () => {
|
||||
const validate = createAsyncValidators(
|
||||
schema,
|
||||
{
|
||||
NameField: NameField as NextCustomFieldValidator<unknown>,
|
||||
AddressField: AddressField as NextCustomFieldValidator<unknown>,
|
||||
NameField: NameField as CustomFieldValidator<unknown>,
|
||||
AddressField: AddressField as CustomFieldValidator<unknown>,
|
||||
},
|
||||
{
|
||||
apiHolder: { get: jest.fn() },
|
||||
@@ -298,7 +295,7 @@ describe('createAsyncValidators', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const AddressField: NextCustomFieldValidator<{
|
||||
const AddressField: CustomFieldValidator<{
|
||||
street?: string;
|
||||
postcode?: string;
|
||||
}> = (value, { addError }) => {
|
||||
@@ -311,18 +308,15 @@ describe('createAsyncValidators', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const NameField: NextCustomFieldValidator<string> = (
|
||||
value,
|
||||
{ addError },
|
||||
) => {
|
||||
const NameField: CustomFieldValidator<string> = (value, { addError }) => {
|
||||
if (!value) {
|
||||
addError('something is broken here!');
|
||||
}
|
||||
};
|
||||
|
||||
const validators = {
|
||||
AddressField: AddressField as NextCustomFieldValidator<unknown>,
|
||||
NameField: NameField as NextCustomFieldValidator<unknown>,
|
||||
AddressField: AddressField as CustomFieldValidator<unknown>,
|
||||
NameField: NameField as CustomFieldValidator<unknown>,
|
||||
};
|
||||
|
||||
const validate = createAsyncValidators(schema, validators, {
|
||||
|
||||
@@ -19,22 +19,23 @@ import type { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { ApiHolder } from '@backstage/core-plugin-api';
|
||||
import { Draft07 as JSONSchema } from 'json-schema-library';
|
||||
import { createFieldValidation, extractSchemaFromStep } from '../../lib';
|
||||
import { NextCustomFieldValidator } from '../../extensions';
|
||||
import {
|
||||
CustomFieldValidator,
|
||||
FieldExtensionUiSchema,
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
import { isObject } from './utils';
|
||||
import { NextFieldExtensionUiSchema } from '../../extensions/types';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
/** @alpha */
|
||||
export type FormValidation = {
|
||||
[name: string]: FieldValidation | FormValidation;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export const createAsyncValidators = (
|
||||
rootSchema: JsonObject,
|
||||
validators: Record<
|
||||
string,
|
||||
undefined | NextCustomFieldValidator<unknown, unknown>
|
||||
undefined | CustomFieldValidator<unknown, unknown>
|
||||
>,
|
||||
context: {
|
||||
apiHolder: ApiHolder;
|
||||
@@ -53,7 +54,7 @@ export const createAsyncValidators = (
|
||||
key: string,
|
||||
value: JsonValue | undefined,
|
||||
schema: JsonObject,
|
||||
uiSchema: NextFieldExtensionUiSchema<unknown, unknown>,
|
||||
uiSchema: FieldExtensionUiSchema<unknown, unknown>,
|
||||
) => {
|
||||
const validator = validators[validatorName];
|
||||
if (validator) {
|
||||
@@ -82,7 +83,7 @@ export const createAsyncValidators = (
|
||||
const doValidateItem = async (
|
||||
propValue: JsonObject,
|
||||
itemSchema: JsonObject,
|
||||
itemUiSchema: NextFieldExtensionUiSchema<unknown, unknown>,
|
||||
itemUiSchema: FieldExtensionUiSchema<unknown, unknown>,
|
||||
) => {
|
||||
await validateForm(
|
||||
propValue['ui:field'] as string,
|
||||
|
||||
@@ -14,3 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { Stepper, type StepperProps } from './Stepper';
|
||||
export {
|
||||
createAsyncValidators,
|
||||
type FormValidation,
|
||||
} from './createAsyncValidators';
|
||||
|
||||
+2
-1
@@ -20,6 +20,7 @@ import { TemplateCategoryPicker } from './TemplateCategoryPicker';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { alertApiRef } from '@backstage/core-plugin-api';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
jest.mock('@backstage/plugin-catalog-react', () => ({
|
||||
useEntityTypeFilter: jest.fn(),
|
||||
@@ -91,7 +92,7 @@ describe('TemplateCategoryPicker', () => {
|
||||
);
|
||||
|
||||
const openButton = getByRole('button', { name: 'Open' });
|
||||
openButton.click();
|
||||
await userEvent.click(openButton);
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Foo' })).toBeInTheDocument();
|
||||
expect(getByRole('checkbox', { name: 'Bar' })).toBeInTheDocument();
|
||||
|
||||
@@ -18,7 +18,7 @@ jest.mock('@backstage/plugin-catalog-react', () => ({
|
||||
useEntityList: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@backstage/plugin-scaffolder-react/alpha', () => ({
|
||||
jest.mock('../TemplateGroup/TemplateGroup', () => ({
|
||||
TemplateGroup: jest.fn(() => null),
|
||||
}));
|
||||
|
||||
@@ -27,7 +27,7 @@ import { useEntityList } from '@backstage/plugin-catalog-react';
|
||||
import { TemplateGroups } from './TemplateGroups';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { errorApiRef } from '@backstage/core-plugin-api';
|
||||
import { TemplateGroup } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { TemplateGroup } from '../TemplateGroup/TemplateGroup';
|
||||
|
||||
describe('TemplateGroups', () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
@@ -23,15 +23,8 @@ import {
|
||||
import { Progress, Link } from '@backstage/core-components';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import { errorApiRef, IconComponent, useApi } from '@backstage/core-plugin-api';
|
||||
import { TemplateGroup } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export type TemplateGroupFilter = {
|
||||
title?: React.ReactNode;
|
||||
filter: (entity: TemplateEntityV1beta3) => boolean;
|
||||
};
|
||||
import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react';
|
||||
import { TemplateGroup } from '../TemplateGroup/TemplateGroup';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
|
||||
@@ -27,6 +27,7 @@ import { errorApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { useTemplateParameterSchema } from '../../hooks/useTemplateParameterSchema';
|
||||
import { Stepper, type StepperProps } from '../Stepper/Stepper';
|
||||
import { SecretsContextProvider } from '../../../secrets/SecretsContext';
|
||||
import { ReviewStepProps } from '@backstage/plugin-scaffolder-react';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(() => ({
|
||||
markdown: {
|
||||
@@ -48,11 +49,14 @@ export type WorkflowProps = {
|
||||
description?: string;
|
||||
namespace: string;
|
||||
templateName: string;
|
||||
components?: {
|
||||
ReviewStepComponent?: React.ComponentType<ReviewStepProps>;
|
||||
};
|
||||
onError(error: Error | undefined): JSX.Element | null;
|
||||
} & Pick<
|
||||
StepperProps,
|
||||
| 'extensions'
|
||||
| 'FormProps'
|
||||
| 'formProps'
|
||||
| 'components'
|
||||
| 'onCreate'
|
||||
| 'initialState'
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* 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 { ApiHolder } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
UIOptionsType,
|
||||
FieldProps as FieldPropsV5,
|
||||
UiSchema as UiSchemaV5,
|
||||
FieldValidation as FieldValidationV5,
|
||||
} from '@rjsf/utils';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { CustomFieldExtensionSchema } from '@backstage/plugin-scaffolder-react';
|
||||
|
||||
/**
|
||||
* Type for Field Extension Props for RJSF v5
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface NextFieldExtensionComponentProps<
|
||||
TFieldReturnValue,
|
||||
TUiOptions = {},
|
||||
> extends PropsWithChildren<FieldPropsV5<TFieldReturnValue>> {
|
||||
uiSchema?: NextFieldExtensionUiSchema<TFieldReturnValue, TUiOptions>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for Field Extension UiSchema
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface NextFieldExtensionUiSchema<TFieldReturnValue, TUiOptions>
|
||||
extends UiSchemaV5<TFieldReturnValue> {
|
||||
'ui:options'?: TUiOptions & UIOptionsType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Field validation type for Custom Field Extensions.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type NextCustomFieldValidator<
|
||||
TFieldReturnValue,
|
||||
TUiOptions = unknown,
|
||||
> = (
|
||||
data: TFieldReturnValue,
|
||||
field: FieldValidationV5,
|
||||
context: {
|
||||
apiHolder: ApiHolder;
|
||||
formData: JsonObject;
|
||||
schema: JsonObject;
|
||||
uiSchema?: NextFieldExtensionUiSchema<TFieldReturnValue, TUiOptions>;
|
||||
},
|
||||
) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Type for the Custom Field Extension with the
|
||||
* name and components and validation function.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type NextFieldExtensionOptions<
|
||||
TFieldReturnValue = unknown,
|
||||
TUiOptions = unknown,
|
||||
> = {
|
||||
name: string;
|
||||
component: (
|
||||
props: NextFieldExtensionComponentProps<TFieldReturnValue, TUiOptions>,
|
||||
) => JSX.Element | null;
|
||||
validation?: NextCustomFieldValidator<TFieldReturnValue, TUiOptions>;
|
||||
schema?: CustomFieldExtensionSchema;
|
||||
};
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useTemplateSchema } from './useTemplateSchema';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { featureFlagsApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { type ParsedTemplateSchema } from './useTemplateSchema';
|
||||
import { useTransformSchemaToProps } from './useTransformSchemaToProps';
|
||||
|
||||
@@ -14,7 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './components';
|
||||
export * from './extensions';
|
||||
export * from './types';
|
||||
export * from './lib';
|
||||
export * from './hooks';
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* 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 type { FormProps as SchemaFormProps } from '@rjsf/core-v5';
|
||||
|
||||
// TODO(Rugvip): The FormProps type is actually supposed to be alpha, but since we want to
|
||||
// refer to it from @backstage/plugin-scaffolder, it needs to be public for now.
|
||||
// Once we support internal alpha re-exports this should be switched to an alpha export.
|
||||
|
||||
/**
|
||||
* Any `@rjsf/core` form properties that are publicly exposed to the `NextScaffolderpage`
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type FormProps = Pick<
|
||||
SchemaFormProps,
|
||||
'transformErrors' | 'noHtml5Validate'
|
||||
>;
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useTemplateSecrets, SecretsContextProvider } from './SecretsContext';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
|
||||
describe('SecretsContext', () => {
|
||||
it('should allow the setting of secrets in the context', async () => {
|
||||
|
||||
Reference in New Issue
Block a user