scaffolder: migrate nfs form fields to utility API
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
---
|
||||
|
||||
Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release.
|
||||
|
||||
As part of this change, the following alpha exports were removed:
|
||||
|
||||
- `formFieldsApi`
|
||||
- `formFieldsApiRef`
|
||||
- `ScaffolderFormFieldsApi`
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release.
|
||||
|
||||
As part of this change, the following alpha exports were removed:
|
||||
|
||||
- `formFieldsApiRef`
|
||||
- `ScaffolderFormFieldsApi`
|
||||
@@ -3,19 +3,14 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { AnyApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { AnyApiRef } from '@backstage/core-plugin-api';
|
||||
import { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { ApiHolder } from '@backstage/core-plugin-api';
|
||||
import { ApiRef } from '@backstage/frontend-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 { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionInput } 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';
|
||||
@@ -26,7 +21,6 @@ import { JsonObject } from '@backstage/types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { JSX as JSX_2 } from 'react/jsx-runtime';
|
||||
import { LayoutOptions } from '@backstage/plugin-scaffolder-react';
|
||||
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
import { Overrides } from '@material-ui/core/styles/overrides';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { ReactElement } from 'react';
|
||||
@@ -204,39 +198,6 @@ export type FormFieldExtensionData<
|
||||
schema?: FieldSchema<z.output<TReturnValue>, z.output<TUiOptions>>;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const formFieldsApi: OverridableExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
|
||||
inputs: {
|
||||
formFields: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
() => Promise<FormField>,
|
||||
'scaffolder.form-field-loader',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
internal: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'api';
|
||||
name: 'form-fields';
|
||||
params: <
|
||||
TApi,
|
||||
TImpl extends TApi,
|
||||
TDeps extends { [name in string]: unknown },
|
||||
>(
|
||||
params: ApiFactory<TApi, TImpl, TDeps>,
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
|
||||
// @alpha @deprecated (undocumented)
|
||||
export const formFieldsApiRef: ApiRef<ScaffolderFormFieldsApi>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type FormValidation = {
|
||||
[name: string]: FieldValidation | FormValidation;
|
||||
@@ -311,12 +272,6 @@ export type ScaffolderFormDecoratorContext<
|
||||
) => void;
|
||||
};
|
||||
|
||||
// @alpha @deprecated (undocumented)
|
||||
export interface ScaffolderFormFieldsApi {
|
||||
// (undocumented)
|
||||
getFormFields(): Promise<FormFieldExtensionData[]>;
|
||||
}
|
||||
|
||||
// @alpha (undocumented)
|
||||
export function ScaffolderPageContextMenu(
|
||||
props: ScaffolderPageContextMenuProps,
|
||||
@@ -345,6 +300,11 @@ export const scaffolderReactTranslationRef: TranslationRef<
|
||||
'scaffolder-react',
|
||||
{
|
||||
readonly 'workflow.noDescription': 'No description';
|
||||
readonly 'stepper.backButtonText': 'Back';
|
||||
readonly 'stepper.createButtonText': 'Create';
|
||||
readonly 'stepper.reviewButtonText': 'Review';
|
||||
readonly 'stepper.nextButtonText': 'Next';
|
||||
readonly 'stepper.stepIndexLabel': 'Step {{index, number}}';
|
||||
readonly 'passwordWidget.content': 'This widget is insecure. Please use [`ui:field: Secret`](https://backstage.io/docs/features/software-templates/writing-templates/#using-secrets) instead of `ui:widget: password`';
|
||||
readonly 'scaffolderPageContextMenu.createLabel': 'Create';
|
||||
readonly 'scaffolderPageContextMenu.moreLabel': 'more';
|
||||
@@ -352,11 +312,6 @@ export const scaffolderReactTranslationRef: TranslationRef<
|
||||
readonly 'scaffolderPageContextMenu.actionsLabel': 'Installed Actions';
|
||||
readonly 'scaffolderPageContextMenu.tasksLabel': 'Task List';
|
||||
readonly 'scaffolderPageContextMenu.templatingExtensionsLabel': 'Templating Extensions';
|
||||
readonly 'stepper.backButtonText': 'Back';
|
||||
readonly 'stepper.createButtonText': 'Create';
|
||||
readonly 'stepper.reviewButtonText': 'Review';
|
||||
readonly 'stepper.nextButtonText': 'Next';
|
||||
readonly 'stepper.stepIndexLabel': 'Step {{index, number}}';
|
||||
readonly 'templateCategoryPicker.title': 'Categories';
|
||||
readonly 'templateCard.noDescription': 'No description';
|
||||
readonly 'templateCard.chooseButtonText': 'Choose';
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { createPlugin } from '@backstage/core-plugin-api';
|
||||
import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { ScaffolderFormFieldsApi, formFieldsApiRef } from '../alpha';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useCustomFieldExtensions } from './useCustomFieldExtensions';
|
||||
import {
|
||||
ScaffolderFieldExtensions,
|
||||
@@ -33,22 +32,10 @@ const plugin = createPlugin({
|
||||
});
|
||||
|
||||
describe('useCustomFieldExtensions', () => {
|
||||
const mockFormFieldsApi: jest.Mocked<ScaffolderFormFieldsApi> = {
|
||||
getFormFields: jest.fn(),
|
||||
};
|
||||
const wrapper = ({ children }: PropsWithChildren<{}>) =>
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[formFieldsApiRef, mockFormFieldsApi]]}>
|
||||
{children}
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
wrapInTestApp(<>{children}</>);
|
||||
|
||||
it('should return field extensions from the React tree', async () => {
|
||||
mockFormFieldsApi.getFormFields.mockResolvedValue([]);
|
||||
const CustomFieldExtension = plugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
name: 'test',
|
||||
@@ -70,60 +57,4 @@ describe('useCustomFieldExtensions', () => {
|
||||
|
||||
expect(result.current).toEqual([expect.objectContaining({ name: 'test' })]);
|
||||
});
|
||||
|
||||
it('should return field extensions from formFieldsApi', async () => {
|
||||
mockFormFieldsApi.getFormFields.mockResolvedValue([
|
||||
{
|
||||
name: 'blueprint',
|
||||
component: () => <div>Test</div>,
|
||||
},
|
||||
]);
|
||||
|
||||
const { result } = renderHook(() => useCustomFieldExtensions(<div />), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
expect(result.current).toEqual([
|
||||
expect.objectContaining({ name: 'blueprint' }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return field extensions from both sources', async () => {
|
||||
mockFormFieldsApi.getFormFields.mockResolvedValue([
|
||||
{
|
||||
name: 'blueprint',
|
||||
component: () => <div>Test</div>,
|
||||
},
|
||||
]);
|
||||
|
||||
const CustomFieldExtension = plugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
name: 'test',
|
||||
component: () => <div>Test</div>,
|
||||
}),
|
||||
);
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useCustomFieldExtensions(
|
||||
<ScaffolderFieldExtensions>
|
||||
<CustomFieldExtension />
|
||||
</ScaffolderFieldExtensions>,
|
||||
),
|
||||
{
|
||||
wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current).toHaveLength(2);
|
||||
});
|
||||
|
||||
const fieldNames = result.current.map(field => field.name);
|
||||
expect(fieldNames).toEqual(expect.arrayContaining(['test', 'blueprint']));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useAsync, useMountEffect } from '@react-hookz/web';
|
||||
import { useApi, useElementFilter } from '@backstage/core-plugin-api';
|
||||
import { formFieldsApiRef } from '../next';
|
||||
import { useElementFilter } from '@backstage/core-plugin-api';
|
||||
import { FieldExtensionOptions } from '../extensions';
|
||||
import {
|
||||
FIELD_EXTENSION_KEY,
|
||||
@@ -32,14 +30,6 @@ export const useCustomFieldExtensions = <
|
||||
>(
|
||||
outlet: React.ReactNode,
|
||||
) => {
|
||||
// Get custom fields created with FormFieldBlueprint
|
||||
const formFieldsApi = useApi(formFieldsApiRef);
|
||||
const [{ result: blueprintFields }, { execute }] = useAsync(
|
||||
() => formFieldsApi.getFormFields(),
|
||||
[],
|
||||
);
|
||||
useMountEffect(execute);
|
||||
|
||||
// Get custom fields created with ScaffolderFieldExtensions
|
||||
const outletFields = useElementFilter(outlet, elements =>
|
||||
elements
|
||||
@@ -51,17 +41,5 @@ export const useCustomFieldExtensions = <
|
||||
}),
|
||||
);
|
||||
|
||||
// This should really be a different type moving forward, but we do this to keep type compatibility.
|
||||
// should probably also move the defaults into the API eventually too, but that will come with the move
|
||||
// to the new frontend system.
|
||||
const blueprintsToLegacy: FieldExtensionOptions[] = blueprintFields?.map(
|
||||
field => ({
|
||||
component: field.component,
|
||||
name: field.name,
|
||||
validation: field.validation,
|
||||
schema: field.schema?.schema,
|
||||
}),
|
||||
);
|
||||
|
||||
return [...blueprintsToLegacy, ...outletFields] as TComponentDataType[];
|
||||
return outletFields as TComponentDataType[];
|
||||
};
|
||||
|
||||
@@ -14,6 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { formFieldsApi } from './FormFieldsApi';
|
||||
export { formFieldsApiRef } from './ref';
|
||||
export type { ScaffolderFormFieldsApi, FormField } from './types';
|
||||
export { type FormField } from './types';
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* 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 { createApiRef } from '@backstage/frontend-plugin-api';
|
||||
import { ScaffolderFormFieldsApi } from './types';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated This API is no longer necessary and will be removed
|
||||
*/
|
||||
export const formFieldsApiRef = createApiRef<ScaffolderFormFieldsApi>({
|
||||
id: 'plugin.scaffolder.form-fields',
|
||||
});
|
||||
@@ -14,16 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormFieldExtensionData } from '../blueprints';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated This API is no longer necessary and will be removed
|
||||
*/
|
||||
export interface ScaffolderFormFieldsApi {
|
||||
getFormFields(): Promise<FormFieldExtensionData[]>;
|
||||
}
|
||||
|
||||
/** @alpha */
|
||||
export interface FormField {
|
||||
readonly $$type: '@backstage/scaffolder/FormField';
|
||||
|
||||
@@ -35,10 +35,7 @@ const formFieldExtensionDataRef = createExtensionDataRef<
|
||||
* */
|
||||
export const FormFieldBlueprint = createExtensionBlueprint({
|
||||
kind: 'scaffolder-form-field',
|
||||
attachTo: [
|
||||
{ id: 'page:scaffolder', input: 'formFields' },
|
||||
{ id: 'api:scaffolder/form-fields', input: 'formFields' },
|
||||
],
|
||||
attachTo: { id: 'api:scaffolder/form-fields', input: 'formFields' },
|
||||
dataRefs: {
|
||||
formFieldLoader: formFieldExtensionDataRef,
|
||||
},
|
||||
|
||||
@@ -17,7 +17,6 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api';
|
||||
import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react';
|
||||
import { FormField } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import type { FormProps as FormProps_2 } from '@rjsf/core';
|
||||
import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react';
|
||||
import { IconComponent } from '@backstage/frontend-plugin-api';
|
||||
@@ -31,7 +30,6 @@ import { ReviewStepProps } from '@backstage/plugin-scaffolder-react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api';
|
||||
import { ScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { ScaffolderFormFieldsApi } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { SubRouteRef } from '@backstage/core-plugin-api';
|
||||
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react';
|
||||
@@ -432,8 +430,6 @@ export const formDecoratorsApi: OverridableExtensionDefinition<{
|
||||
// @alpha (undocumented)
|
||||
export const formDecoratorsApiRef: ApiRef<ScaffolderFormDecoratorsApi>;
|
||||
|
||||
export { formFieldsApiRef };
|
||||
|
||||
// @alpha @deprecated
|
||||
export type FormProps = Pick<
|
||||
FormProps_2,
|
||||
@@ -453,8 +449,6 @@ export interface ScaffolderFormDecoratorsApi {
|
||||
getFormDecorators(): Promise<ScaffolderFormDecorator[]>;
|
||||
}
|
||||
|
||||
export { ScaffolderFormFieldsApi };
|
||||
|
||||
// @public (undocumented)
|
||||
export type ScaffolderTemplateEditorClassKey =
|
||||
| 'root'
|
||||
|
||||
@@ -29,6 +29,7 @@ import { FormFieldBlueprint } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
|
||||
import { ScaffolderClient } from '../api';
|
||||
import { formFieldsApiRef } from './formFieldsApi';
|
||||
|
||||
export const scaffolderPage = PageBlueprint.makeWithOverrides({
|
||||
inputs: {
|
||||
@@ -36,17 +37,29 @@ export const scaffolderPage = PageBlueprint.makeWithOverrides({
|
||||
FormFieldBlueprint.dataRefs.formFieldLoader,
|
||||
]),
|
||||
},
|
||||
factory(originalFactory, { inputs }) {
|
||||
const formFieldLoaders = inputs.formFields.map(i =>
|
||||
i.get(FormFieldBlueprint.dataRefs.formFieldLoader),
|
||||
);
|
||||
factory(originalFactory, { apis, inputs }) {
|
||||
const formFieldsApi = apis.get(formFieldsApiRef);
|
||||
|
||||
return originalFactory({
|
||||
routeRef: rootRouteRef,
|
||||
path: '/create',
|
||||
loader: () =>
|
||||
import('../components/Router/Router').then(m => (
|
||||
<m.InternalRouter formFieldLoaders={formFieldLoaders} />
|
||||
)),
|
||||
loader: async () => {
|
||||
// Merge form fields from the API with old-style direct attachments
|
||||
const apiFormFields = (await formFieldsApi?.loadFormFields()) ?? [];
|
||||
const formFieldLoaders = inputs.formFields.map(output =>
|
||||
output.get(FormFieldBlueprint.dataRefs.formFieldLoader),
|
||||
);
|
||||
|
||||
// Resolve direct attachments and combine with API form fields
|
||||
const loadedFormFields = await Promise.all(
|
||||
formFieldLoaders.map(loader => loader()),
|
||||
);
|
||||
const formFields = [...apiFormFields, ...loadedFormFields];
|
||||
|
||||
return import('../components/Router/Router').then(m => (
|
||||
<m.InternalRouter formFields={formFields} />
|
||||
));
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
+27
-21
@@ -16,32 +16,24 @@
|
||||
|
||||
import {
|
||||
ApiBlueprint,
|
||||
createApiRef,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { formFieldsApiRef } from './ref';
|
||||
import { FormField, ScaffolderFormFieldsApi } from './types';
|
||||
import { FormFieldBlueprint } from '../blueprints';
|
||||
import { FormFieldBlueprint } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { OpaqueFormField } from '@internal/scaffolder';
|
||||
|
||||
class DefaultScaffolderFormFieldsApi implements ScaffolderFormFieldsApi {
|
||||
private readonly formFieldLoaders: Array<() => Promise<FormField>>;
|
||||
|
||||
constructor(formFieldLoaders: Array<() => Promise<FormField>> = []) {
|
||||
this.formFieldLoaders = formFieldLoaders;
|
||||
}
|
||||
|
||||
async getFormFields() {
|
||||
const formFields = await Promise.all(
|
||||
this.formFieldLoaders.map(loader => loader()),
|
||||
);
|
||||
|
||||
const internalFormFields = formFields.map(OpaqueFormField.toInternal);
|
||||
|
||||
return internalFormFields;
|
||||
}
|
||||
interface FormField {
|
||||
readonly $$type: '@backstage/scaffolder/FormField';
|
||||
}
|
||||
|
||||
/** @alpha */
|
||||
interface ScaffolderFormFieldsApi {
|
||||
loadFormFields(): Promise<FormField[]>;
|
||||
}
|
||||
|
||||
const formFieldsApiRef = createApiRef<ScaffolderFormFieldsApi>({
|
||||
id: 'plugin.scaffolder.form-fields-loader',
|
||||
});
|
||||
|
||||
export const formFieldsApi = ApiBlueprint.makeWithOverrides({
|
||||
name: 'form-fields',
|
||||
inputs: {
|
||||
@@ -58,8 +50,22 @@ export const formFieldsApi = ApiBlueprint.makeWithOverrides({
|
||||
defineParams({
|
||||
api: formFieldsApiRef,
|
||||
deps: {},
|
||||
factory: () => new DefaultScaffolderFormFieldsApi(formFieldLoaders),
|
||||
factory: () => ({
|
||||
async loadFormFields() {
|
||||
const formFields = await Promise.all(
|
||||
formFieldLoaders.map(loader => loader()),
|
||||
);
|
||||
|
||||
const internalFormFields = formFields.map(
|
||||
OpaqueFormField.toInternal,
|
||||
);
|
||||
|
||||
return internalFormFields;
|
||||
},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export { formFieldsApiRef };
|
||||
@@ -25,9 +25,4 @@ export {
|
||||
export { scaffolderTranslationRef } from '../translation';
|
||||
export * from './api';
|
||||
|
||||
export {
|
||||
formFieldsApiRef,
|
||||
type ScaffolderFormFieldsApi,
|
||||
} from '@backstage/plugin-scaffolder-react/alpha';
|
||||
|
||||
export { default } from './plugin';
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
scaffolderPage,
|
||||
} from './extensions';
|
||||
import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { formFieldsApi } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { formFieldsApi } from './formFieldsApi';
|
||||
import { formDecoratorsApi } from './api';
|
||||
import { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { useScaffolderTemplateIconLinkProps } from './hooks/useScaffolderTemplateIconLinkProps';
|
||||
|
||||
@@ -13,13 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ReactElement } from 'react';
|
||||
import { Router } from './Router';
|
||||
import {
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
TestAppOptions,
|
||||
} from '@backstage/test-utils';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import {
|
||||
createScaffolderFieldExtension,
|
||||
ScaffolderFieldExtensions,
|
||||
@@ -30,23 +25,12 @@ import {
|
||||
ScaffolderLayouts,
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
import { TemplateListPage, TemplateWizardPage } from '../../alpha/components';
|
||||
import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
|
||||
jest.mock('../../alpha/components', () => ({
|
||||
TemplateWizardPage: jest.fn(() => null),
|
||||
TemplateListPage: jest.fn(() => null),
|
||||
}));
|
||||
|
||||
const wrapInApisAndRender = (element: ReactElement, opts?: TestAppOptions) =>
|
||||
renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[[formFieldsApiRef, { getFormFields: async () => [] }]]}
|
||||
>
|
||||
{element}
|
||||
</TestApiProvider>,
|
||||
opts,
|
||||
);
|
||||
|
||||
describe('Router', () => {
|
||||
beforeEach(() => {
|
||||
(TemplateWizardPage as jest.Mock).mockClear();
|
||||
@@ -54,13 +38,13 @@ describe('Router', () => {
|
||||
});
|
||||
describe('/', () => {
|
||||
it('should render the TemplateListPage', async () => {
|
||||
await wrapInApisAndRender(<Router />);
|
||||
await renderInTestApp(<Router />);
|
||||
|
||||
expect(TemplateListPage).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render user-provided TemplateListPage', async () => {
|
||||
const { getByText } = await wrapInApisAndRender(
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Router
|
||||
components={{
|
||||
EXPERIMENTAL_TemplateListPageComponent: () => <>foobar</>,
|
||||
@@ -76,7 +60,7 @@ describe('Router', () => {
|
||||
|
||||
it('should render not found error page', async () => {
|
||||
await expect(
|
||||
wrapInApisAndRender(<Router />, {
|
||||
renderInTestApp(<Router />, {
|
||||
routeEntries: ['/foonotfounderror'],
|
||||
}),
|
||||
).rejects.toThrow('Reached NotFound Page');
|
||||
@@ -85,7 +69,7 @@ describe('Router', () => {
|
||||
|
||||
describe('/templates/:templateName', () => {
|
||||
it('should render the TemplateWizard page', async () => {
|
||||
await wrapInApisAndRender(<Router />, {
|
||||
await renderInTestApp(<Router />, {
|
||||
routeEntries: ['/templates/default/foo'],
|
||||
});
|
||||
|
||||
@@ -93,7 +77,7 @@ describe('Router', () => {
|
||||
});
|
||||
|
||||
it('should render user-provided TemplateWizardPage', async () => {
|
||||
const { getByText } = await wrapInApisAndRender(
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Router
|
||||
components={{
|
||||
EXPERIMENTAL_TemplateWizardPageComponent: () => <>foobar</>,
|
||||
@@ -110,7 +94,7 @@ describe('Router', () => {
|
||||
it('should pass through the FormProps property', async () => {
|
||||
const transformErrorsMock = jest.fn();
|
||||
|
||||
await wrapInApisAndRender(
|
||||
await renderInTestApp(
|
||||
<Router
|
||||
formProps={{
|
||||
transformErrors: transformErrorsMock,
|
||||
@@ -142,7 +126,7 @@ describe('Router', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
await wrapInApisAndRender(
|
||||
await renderInTestApp(
|
||||
<Router>
|
||||
<ScaffolderFieldExtensions>
|
||||
<CustomFieldExtension />
|
||||
@@ -170,7 +154,7 @@ describe('Router', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
await wrapInApisAndRender(
|
||||
await renderInTestApp(
|
||||
<Router>
|
||||
<ScaffolderLayouts>
|
||||
<Layout />
|
||||
|
||||
@@ -63,7 +63,6 @@ import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
import { templateManagementPermission } from '@backstage/plugin-scaffolder-common/alpha';
|
||||
import { useApp } from '@backstage/core-plugin-api';
|
||||
import { OpaqueFormField } from '@internal/scaffolder';
|
||||
import { useAsync, useMountEffect } from '@react-hookz/web';
|
||||
import { TemplatingExtensionsPage } from '../TemplatingExtensionsPage';
|
||||
import { FormField } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
|
||||
@@ -116,7 +115,9 @@ export type RouterProps = {
|
||||
*/
|
||||
export const InternalRouter = (
|
||||
props: PropsWithChildren<
|
||||
RouterProps & { formFieldLoaders?: Array<() => Promise<FormField>> }
|
||||
RouterProps & {
|
||||
formFields?: Array<FormField>;
|
||||
}
|
||||
>,
|
||||
) => {
|
||||
const {
|
||||
@@ -133,14 +134,13 @@ export const InternalRouter = (
|
||||
} = props;
|
||||
const outlet = useOutlet() || props.children;
|
||||
const customFieldExtensions = useCustomFieldExtensions(outlet);
|
||||
const loadedFieldExtensions = useFormFieldLoaders(props.formFieldLoaders);
|
||||
|
||||
const app = useApp();
|
||||
const { NotFoundErrorPage } = app.getComponents();
|
||||
|
||||
const fieldExtensions = [
|
||||
...customFieldExtensions,
|
||||
...loadedFieldExtensions,
|
||||
...(props.formFields?.map(OpaqueFormField.toInternal) ?? []),
|
||||
...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(
|
||||
({ name }) =>
|
||||
!customFieldExtensions.some(
|
||||
@@ -261,17 +261,3 @@ export const InternalRouter = (
|
||||
export const Router = (props: PropsWithChildren<RouterProps>) => {
|
||||
return <InternalRouter {...props} />;
|
||||
};
|
||||
|
||||
function useFormFieldLoaders(
|
||||
formFieldLoaders?: Array<() => Promise<FormField>>,
|
||||
) {
|
||||
const [{ result: loadedFieldExtensions }, { execute }] =
|
||||
useAsync(async () => {
|
||||
const loaded = await Promise.all(
|
||||
(formFieldLoaders ?? []).map(loader => loader()),
|
||||
);
|
||||
return loaded.map(f => OpaqueFormField.toInternal(f));
|
||||
}, []);
|
||||
useMountEffect(execute);
|
||||
return loadedFieldExtensions;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@ import { RepoBranchPicker } from './components/fields/RepoBranchPicker/RepoBranc
|
||||
import { RepoBranchPickerSchema } from './components/fields/RepoBranchPicker/schema';
|
||||
import { formDecoratorsApiRef } from './alpha/api/ref';
|
||||
import { DefaultScaffolderFormDecoratorsApi } from './alpha/api/FormDecoratorsApi';
|
||||
import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import {
|
||||
RepoOwnerPicker,
|
||||
RepoOwnerPickerSchema,
|
||||
@@ -115,11 +114,6 @@ export const scaffolderPlugin = createPlugin({
|
||||
deps: {},
|
||||
factory: () => DefaultScaffolderFormDecoratorsApi.create(),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: formFieldsApiRef,
|
||||
deps: {},
|
||||
factory: () => ({ getFormFields: async () => [] }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
|
||||
Reference in New Issue
Block a user