scaffolder: attach form field extensions to page instead
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
---
|
||||
|
||||
Deprecated the alpha `ScaffolderFormFieldsApi` and `formFieldsApiRef` as these are being replaced with a different solution.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Updated the alpha `page:scaffolder` extension to accept `formFields` input, matching the updated `FormFieldBlueprint`.
|
||||
@@ -16,6 +16,27 @@ const visualizerPlugin: FrontendPlugin<
|
||||
{},
|
||||
{},
|
||||
{
|
||||
'nav-item:app-visualizer': ExtensionDefinition<{
|
||||
kind: 'nav-item';
|
||||
name: undefined;
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
{
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
routeRef: RouteRef<undefined>;
|
||||
},
|
||||
'core.nav-item.target',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
routeRef: RouteRef<undefined>;
|
||||
};
|
||||
}>;
|
||||
'page:app-visualizer': ExtensionDefinition<{
|
||||
kind: 'page';
|
||||
name: undefined;
|
||||
@@ -46,27 +67,6 @@ const visualizerPlugin: FrontendPlugin<
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
};
|
||||
}>;
|
||||
'nav-item:app-visualizer': ExtensionDefinition<{
|
||||
kind: 'nav-item';
|
||||
name: undefined;
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
{
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
routeRef: RouteRef<undefined>;
|
||||
},
|
||||
'core.nav-item.target',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
routeRef: RouteRef<undefined>;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
export default visualizerPlugin;
|
||||
|
||||
@@ -18,6 +18,21 @@ const _default: FrontendPlugin<
|
||||
},
|
||||
{},
|
||||
{
|
||||
'api:catalog-import': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
name: undefined;
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
factory: AnyApiFactory;
|
||||
};
|
||||
}>;
|
||||
'page:catalog-import': ExtensionDefinition<{
|
||||
kind: 'page';
|
||||
name: undefined;
|
||||
@@ -48,21 +63,6 @@ const _default: FrontendPlugin<
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
};
|
||||
}>;
|
||||
'api:catalog-import': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
name: undefined;
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
factory: AnyApiFactory;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
export default _default;
|
||||
|
||||
@@ -19,36 +19,6 @@ const _default: FrontendPlugin<
|
||||
},
|
||||
{},
|
||||
{
|
||||
'page:devtools': ExtensionDefinition<{
|
||||
kind: 'page';
|
||||
name: undefined;
|
||||
config: {
|
||||
path: string | undefined;
|
||||
};
|
||||
configInput: {
|
||||
path?: string | undefined;
|
||||
};
|
||||
output:
|
||||
| ConfigurableExtensionDataRef<
|
||||
React_2.JSX.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ConfigurableExtensionDataRef<
|
||||
RouteRef<AnyRouteRefParams>,
|
||||
'core.routing.ref',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
defaultPath: string;
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
};
|
||||
}>;
|
||||
'nav-item:devtools': ExtensionDefinition<{
|
||||
kind: 'nav-item';
|
||||
name: undefined;
|
||||
@@ -85,6 +55,36 @@ const _default: FrontendPlugin<
|
||||
factory: AnyApiFactory;
|
||||
};
|
||||
}>;
|
||||
'page:devtools': ExtensionDefinition<{
|
||||
kind: 'page';
|
||||
name: undefined;
|
||||
config: {
|
||||
path: string | undefined;
|
||||
};
|
||||
configInput: {
|
||||
path?: string | undefined;
|
||||
};
|
||||
output:
|
||||
| ConfigurableExtensionDataRef<
|
||||
React_2.JSX.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ConfigurableExtensionDataRef<
|
||||
RouteRef<AnyRouteRefParams>,
|
||||
'core.routing.ref',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
defaultPath: string;
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
export default _default;
|
||||
|
||||
@@ -22,6 +22,21 @@ const _default: FrontendPlugin<
|
||||
},
|
||||
{},
|
||||
{
|
||||
'api:kubernetes': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
name: undefined;
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
factory: AnyApiFactory;
|
||||
};
|
||||
}>;
|
||||
'page:kubernetes': ExtensionDefinition<{
|
||||
kind: 'page';
|
||||
name: undefined;
|
||||
@@ -48,21 +63,6 @@ const _default: FrontendPlugin<
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
};
|
||||
}>;
|
||||
'api:kubernetes': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
name: undefined;
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
factory: AnyApiFactory;
|
||||
};
|
||||
}>;
|
||||
'entity-content:kubernetes/kubernetes': ExtensionDefinition<{
|
||||
kind: 'entity-content';
|
||||
name: 'kubernetes';
|
||||
|
||||
@@ -224,7 +224,7 @@ export const formFieldsApi: ExtensionDefinition<{
|
||||
};
|
||||
}>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
// @alpha @deprecated (undocumented)
|
||||
export const formFieldsApiRef: ApiRef<ScaffolderFormFieldsApi>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
@@ -301,7 +301,7 @@ export type ScaffolderFormDecoratorContext<
|
||||
) => void;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
// @alpha @deprecated (undocumented)
|
||||
export interface ScaffolderFormFieldsApi {
|
||||
// (undocumented)
|
||||
getFormFields(): Promise<FormFieldExtensionData[]>;
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
import { createApiRef } from '@backstage/frontend-plugin-api';
|
||||
import { ScaffolderFormFieldsApi } from './types';
|
||||
|
||||
/** @alpha */
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated This API is no longer necessary and will be removed
|
||||
*/
|
||||
export const formFieldsApiRef = createApiRef<ScaffolderFormFieldsApi>({
|
||||
id: 'plugin.scaffolder.form-fields',
|
||||
});
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
import { FormFieldExtensionData } from '../blueprints';
|
||||
|
||||
/** @alpha */
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated This API is no longer necessary and will be removed
|
||||
*/
|
||||
export interface ScaffolderFormFieldsApi {
|
||||
getFormFields(): Promise<FormFieldExtensionData[]>;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ const formFieldExtensionDataRef = createExtensionDataRef<
|
||||
* */
|
||||
export const FormFieldBlueprint = createExtensionBlueprint({
|
||||
kind: 'scaffolder-form-field',
|
||||
attachTo: { id: 'api:scaffolder/form-fields', input: 'formFields' },
|
||||
attachTo: [
|
||||
{ id: 'page:scaffolder', input: 'formFields' },
|
||||
{ id: 'api:scaffolder/form-fields', input: 'formFields' },
|
||||
],
|
||||
dataRefs: {
|
||||
formFieldLoader: formFieldExtensionDataRef,
|
||||
},
|
||||
|
||||
@@ -108,8 +108,6 @@ const _default: FrontendPlugin<
|
||||
};
|
||||
}>;
|
||||
'page:scaffolder': ExtensionDefinition<{
|
||||
kind: 'page';
|
||||
name: undefined;
|
||||
config: {
|
||||
path: string | undefined;
|
||||
};
|
||||
@@ -126,7 +124,21 @@ const _default: FrontendPlugin<
|
||||
optional: true;
|
||||
}
|
||||
>;
|
||||
inputs: {};
|
||||
inputs: {
|
||||
formFields: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
() => Promise<FormField>,
|
||||
'scaffolder.form-field-loader',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'page';
|
||||
name: undefined;
|
||||
params: {
|
||||
defaultPath: string;
|
||||
loader: () => Promise<JSX.Element>;
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
identityApiRef,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import React from 'react';
|
||||
import { rootRouteRef } from '../routes';
|
||||
@@ -35,12 +36,26 @@ import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
|
||||
import { ScaffolderClient } from '../api';
|
||||
|
||||
export const scaffolderPage = PageBlueprint.make({
|
||||
params: {
|
||||
routeRef: convertLegacyRouteRef(rootRouteRef),
|
||||
defaultPath: '/create',
|
||||
loader: () =>
|
||||
import('../components/Router').then(m => compatWrapper(<m.Router />)),
|
||||
export const scaffolderPage = PageBlueprint.makeWithOverrides({
|
||||
inputs: {
|
||||
formFields: createExtensionInput([
|
||||
FormFieldBlueprint.dataRefs.formFieldLoader,
|
||||
]),
|
||||
},
|
||||
factory(originalFactory, { inputs }) {
|
||||
const formFieldLoaders = inputs.formFields.map(i =>
|
||||
i.get(FormFieldBlueprint.dataRefs.formFieldLoader),
|
||||
);
|
||||
return originalFactory({
|
||||
routeRef: convertLegacyRouteRef(rootRouteRef),
|
||||
defaultPath: '/create',
|
||||
loader: () =>
|
||||
import('../components/Router/Router').then(m =>
|
||||
compatWrapper(
|
||||
<m.InternalRouter formFieldLoaders={formFieldLoaders} />,
|
||||
),
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -64,6 +64,8 @@ import {
|
||||
templateManagementPermission,
|
||||
} from '@backstage/plugin-scaffolder-common/alpha';
|
||||
import { useApp } from '@backstage/core-plugin-api';
|
||||
import { FormField, OpaqueFormField } from '@internal/scaffolder';
|
||||
import { useAsync, useMountEffect } from '@react-hookz/web';
|
||||
|
||||
/**
|
||||
* The Props for the Scaffolder Router
|
||||
@@ -105,11 +107,16 @@ export type RouterProps = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The Scaffolder Router
|
||||
* Internal router with additional props that aren't available in the public API
|
||||
* for the old frontend system.
|
||||
*
|
||||
* @public
|
||||
* @internal
|
||||
*/
|
||||
export const Router = (props: PropsWithChildren<RouterProps>) => {
|
||||
export const InternalRouter = (
|
||||
props: PropsWithChildren<
|
||||
RouterProps & { formFieldLoaders?: Array<() => Promise<FormField>> }
|
||||
>,
|
||||
) => {
|
||||
const {
|
||||
components: {
|
||||
TemplateCardComponent,
|
||||
@@ -123,13 +130,15 @@ export const Router = (props: PropsWithChildren<RouterProps>) => {
|
||||
} = {},
|
||||
} = props;
|
||||
const outlet = useOutlet() || props.children;
|
||||
const customFieldExtensions =
|
||||
useCustomFieldExtensions<FieldExtensionOptions>(outlet);
|
||||
const customFieldExtensions = useCustomFieldExtensions(outlet);
|
||||
const loadedFieldExtensions = useFormFieldLoaders(props.formFieldLoaders);
|
||||
|
||||
const app = useApp();
|
||||
const { NotFoundErrorPage } = app.getComponents();
|
||||
|
||||
const fieldExtensions = [
|
||||
...customFieldExtensions,
|
||||
...loadedFieldExtensions,
|
||||
...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(
|
||||
({ name }) =>
|
||||
!customFieldExtensions.some(
|
||||
@@ -243,3 +252,26 @@ export const Router = (props: PropsWithChildren<RouterProps>) => {
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* The Scaffolder Router
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -31,36 +31,6 @@ const _default: FrontendPlugin<
|
||||
},
|
||||
{},
|
||||
{
|
||||
'page:techdocs': ExtensionDefinition<{
|
||||
kind: 'page';
|
||||
name: undefined;
|
||||
config: {
|
||||
path: string | undefined;
|
||||
};
|
||||
configInput: {
|
||||
path?: string | undefined;
|
||||
};
|
||||
output:
|
||||
| ConfigurableExtensionDataRef<
|
||||
React_2.JSX.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ConfigurableExtensionDataRef<
|
||||
RouteRef<AnyRouteRefParams>,
|
||||
'core.routing.ref',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
defaultPath: string;
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
};
|
||||
}>;
|
||||
'nav-item:techdocs': ExtensionDefinition<{
|
||||
kind: 'nav-item';
|
||||
name: undefined;
|
||||
@@ -112,6 +82,36 @@ const _default: FrontendPlugin<
|
||||
factory: AnyApiFactory;
|
||||
};
|
||||
}>;
|
||||
'page:techdocs': ExtensionDefinition<{
|
||||
kind: 'page';
|
||||
name: undefined;
|
||||
config: {
|
||||
path: string | undefined;
|
||||
};
|
||||
configInput: {
|
||||
path?: string | undefined;
|
||||
};
|
||||
output:
|
||||
| ConfigurableExtensionDataRef<
|
||||
React_2.JSX.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ConfigurableExtensionDataRef<
|
||||
RouteRef<AnyRouteRefParams>,
|
||||
'core.routing.ref',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
defaultPath: string;
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
};
|
||||
}>;
|
||||
'search-result-list-item:techdocs': ExtensionDefinition<{
|
||||
config: {
|
||||
title: string | undefined;
|
||||
|
||||
Reference in New Issue
Block a user