@@ -27,6 +27,9 @@ import {
|
||||
iconsApiRef,
|
||||
useApi,
|
||||
routeResolutionApiRef,
|
||||
ErrorBoundary,
|
||||
NotFoundErrorPage,
|
||||
Progress,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
AppComponents,
|
||||
@@ -42,9 +45,6 @@ import {
|
||||
} from '@backstage/version-bridge';
|
||||
import { convertLegacyRouteRef } from '../convertLegacyRouteRef';
|
||||
|
||||
import { ErrorBoundary, NotFoundErrorPage } from '@backstage/plugin-app';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
|
||||
// Make sure that we only convert each new plugin instance to its legacy equivalent once
|
||||
const legacyPluginStore = getOrCreateGlobalSingleton(
|
||||
'legacy-plugin-compatibility-store',
|
||||
|
||||
@@ -37,6 +37,9 @@ import {
|
||||
componentsApiRef,
|
||||
iconsApiRef,
|
||||
routeResolutionApiRef,
|
||||
Progress,
|
||||
NotFoundErrorPage,
|
||||
ErrorBoundary,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { ComponentType, useMemo } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
@@ -66,14 +69,28 @@ class CompatComponentsApi implements ComponentsApi {
|
||||
this.#ErrorBoundaryFallback = ErrorBoundaryFallback;
|
||||
}
|
||||
|
||||
getComponent<T extends {}>(ref: ComponentRef<T>): ComponentType<T> {
|
||||
getComponent<
|
||||
TInnerComponentProps extends {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
>(
|
||||
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>,
|
||||
):
|
||||
| (() => (props: TInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>)
|
||||
| undefined {
|
||||
switch (ref.id) {
|
||||
case coreComponentRefs.progress.id:
|
||||
return this.#Progress as ComponentType<any>;
|
||||
case coreComponentRefs.notFoundErrorPage.id:
|
||||
return this.#NotFoundErrorPage as ComponentType<any>;
|
||||
case coreComponentRefs.errorBoundaryFallback.id:
|
||||
return this.#ErrorBoundaryFallback as ComponentType<any>;
|
||||
case Progress.ref.id:
|
||||
return (() => this.#Progress) as () => (
|
||||
props: object,
|
||||
) => JSX.Element | null;
|
||||
case NotFoundErrorPage.ref.id:
|
||||
return (() => this.#NotFoundErrorPage) as () => (
|
||||
props: object,
|
||||
) => JSX.Element | null;
|
||||
case ErrorBoundary.ref.id:
|
||||
return (() => this.#ErrorBoundaryFallback) as () => (
|
||||
props: object,
|
||||
) => JSX.Element | null;
|
||||
default:
|
||||
throw new Error(
|
||||
`No backwards compatible component is available for ref '${ref.id}'`,
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
import {
|
||||
componentsApiRef,
|
||||
coreComponentRefs,
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
iconsApiRef,
|
||||
useRouteRef as useNewRouteRef,
|
||||
createRouteRef as createNewRouteRef,
|
||||
useApi,
|
||||
NotFoundErrorPage,
|
||||
ErrorBoundary,
|
||||
Progress,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
createExtensionTester,
|
||||
@@ -97,13 +99,19 @@ describe('BackwardsCompatProvider', () => {
|
||||
|
||||
describe('ForwardsCompatProvider', () => {
|
||||
it('should convert the app context', async () => {
|
||||
const defaultComponentRefs = {
|
||||
progress: Progress.ref,
|
||||
notFoundErrorPage: NotFoundErrorPage.ref,
|
||||
errorBoundary: ErrorBoundary.ref,
|
||||
};
|
||||
|
||||
function Component() {
|
||||
const components = useApi(componentsApiRef);
|
||||
const icons = useApi(iconsApiRef);
|
||||
return (
|
||||
<div data-testid="ctx">
|
||||
components:{' '}
|
||||
{Object.entries(coreComponentRefs)
|
||||
{Object.entries(defaultComponentRefs)
|
||||
.map(
|
||||
([name, ref]) =>
|
||||
`${name}=${Boolean(components.getComponent(ref))}`,
|
||||
|
||||
@@ -27,6 +27,11 @@ export const componentDataRef = createExtensionDataRef<{
|
||||
| (() => Promise<(props: {}) => JSX.Element | null>);
|
||||
}>().with({ id: 'core.component.component' });
|
||||
|
||||
/**
|
||||
* Blueprint for creating adaptable components from a componentRef and a loader
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const AdaptableComponentBlueprint = createExtensionBlueprint({
|
||||
kind: 'component',
|
||||
attachTo: { id: 'api:app/components', input: 'components' },
|
||||
|
||||
@@ -13,16 +13,32 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createAdaptableComponent } from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
CoreErrorBoundaryFallbackProps,
|
||||
CoreNotFoundErrorPageProps,
|
||||
CoreProgressProps,
|
||||
createAdaptableComponent,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
export const Progress = createAdaptableComponent({
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const Progress = createAdaptableComponent<CoreProgressProps>({
|
||||
id: 'core.components.progress',
|
||||
});
|
||||
|
||||
export const NotFoundErrorPage = createAdaptableComponent({
|
||||
id: 'core.components.notFoundErrorPage',
|
||||
});
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const NotFoundErrorPage =
|
||||
createAdaptableComponent<CoreNotFoundErrorPageProps>({
|
||||
id: 'core.components.notFoundErrorPage',
|
||||
});
|
||||
|
||||
export const ErrorBoundary = createAdaptableComponent({
|
||||
id: 'core.components.errorBoundary',
|
||||
});
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const ErrorBoundary =
|
||||
createAdaptableComponent<CoreErrorBoundaryFallbackProps>({
|
||||
id: 'core.components.errorBoundary',
|
||||
});
|
||||
|
||||
@@ -29,7 +29,12 @@ export type ComponentRef<
|
||||
$$type: '@backstage/ComponentRef';
|
||||
};
|
||||
|
||||
export type ComponentRefOptions<
|
||||
/**
|
||||
* Options for creating an AdaptableComponent.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CreateAdaptableComponentOptions<
|
||||
TInnerComponentProps extends {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
> = {
|
||||
@@ -90,11 +95,19 @@ function makeComponentFromRef<
|
||||
return ComponentRefImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a AdaptableComponent that can be used to render the component, optionally overriden by the app.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createAdaptableComponent<
|
||||
TInnerComponentProps extends {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
>(
|
||||
options: ComponentRefOptions<TInnerComponentProps, TExternalComponentProps>,
|
||||
options: CreateAdaptableComponentOptions<
|
||||
TInnerComponentProps,
|
||||
TExternalComponentProps
|
||||
>,
|
||||
): ((props: TExternalComponentProps) => JSX.Element) & {
|
||||
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>;
|
||||
} {
|
||||
|
||||
@@ -20,6 +20,7 @@ export {
|
||||
} from './ExtensionBoundary';
|
||||
export {
|
||||
createAdaptableComponent,
|
||||
type CreateAdaptableComponentOptions,
|
||||
type ComponentRef,
|
||||
} from './createAdaptableComponent';
|
||||
export { useAppNode } from './AppNodeProvider';
|
||||
|
||||
+135
-1
@@ -324,7 +324,9 @@ const appPlugin: FrontendPlugin<
|
||||
ConfigurableExtensionDataRef<
|
||||
{
|
||||
ref: ComponentRef;
|
||||
impl: ComponentType;
|
||||
loader:
|
||||
| (() => (props: {}) => JSX.Element | null)
|
||||
| (() => Promise<(props: {}) => JSX.Element | null>);
|
||||
},
|
||||
'core.component.component',
|
||||
{}
|
||||
@@ -726,6 +728,138 @@ const appPlugin: FrontendPlugin<
|
||||
element: JSX.Element;
|
||||
};
|
||||
}>;
|
||||
'component:app/core.components.errorBoundary': ExtensionDefinition<{
|
||||
kind: 'component';
|
||||
name: 'core.components.errorBoundary';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
{
|
||||
ref: ComponentRef;
|
||||
loader:
|
||||
| (() => (props: {}) => JSX.Element | null)
|
||||
| (() => Promise<(props: {}) => JSX.Element | null>);
|
||||
},
|
||||
'core.component.component',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: <Ref extends ComponentRef<any>>(params: {
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<
|
||||
(props: IInnerComponentProps) => JSX.Element | null
|
||||
>)
|
||||
: never;
|
||||
}) => ExtensionBlueprintParams<{
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<
|
||||
(props: IInnerComponentProps) => JSX.Element | null
|
||||
>)
|
||||
: never;
|
||||
}>;
|
||||
}>;
|
||||
'component:app/core.components.notFoundErrorPage': ExtensionDefinition<{
|
||||
kind: 'component';
|
||||
name: 'core.components.notFoundErrorPage';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
{
|
||||
ref: ComponentRef;
|
||||
loader:
|
||||
| (() => (props: {}) => JSX.Element | null)
|
||||
| (() => Promise<(props: {}) => JSX.Element | null>);
|
||||
},
|
||||
'core.component.component',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: <Ref extends ComponentRef<any>>(params: {
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<
|
||||
(props: IInnerComponentProps) => JSX.Element | null
|
||||
>)
|
||||
: never;
|
||||
}) => ExtensionBlueprintParams<{
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<
|
||||
(props: IInnerComponentProps) => JSX.Element | null
|
||||
>)
|
||||
: never;
|
||||
}>;
|
||||
}>;
|
||||
'component:app/core.components.progress': ExtensionDefinition<{
|
||||
kind: 'component';
|
||||
name: 'core.components.progress';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
{
|
||||
ref: ComponentRef;
|
||||
loader:
|
||||
| (() => (props: {}) => JSX.Element | null)
|
||||
| (() => Promise<(props: {}) => JSX.Element | null>);
|
||||
},
|
||||
'core.component.component',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: <Ref extends ComponentRef<any>>(params: {
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<
|
||||
(props: IInnerComponentProps) => JSX.Element | null
|
||||
>)
|
||||
: never;
|
||||
}) => ExtensionBlueprintParams<{
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<
|
||||
(props: IInnerComponentProps) => JSX.Element | null
|
||||
>)
|
||||
: never;
|
||||
}>;
|
||||
}>;
|
||||
'sign-in-page:app': ExtensionDefinition<{
|
||||
kind: 'sign-in-page';
|
||||
name: undefined;
|
||||
|
||||
@@ -20,7 +20,12 @@ import {
|
||||
AdaptableComponentBlueprint,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
import { Progress as ProgressComponent } from '@backstage/core-components';
|
||||
import {
|
||||
ErrorPage,
|
||||
ErrorPanel,
|
||||
Progress as ProgressComponent,
|
||||
} from '@backstage/core-components';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
export const Progress = AdaptableComponentBlueprint.make({
|
||||
name: 'core.components.progress',
|
||||
@@ -36,7 +41,8 @@ export const NotFoundErrorPage = AdaptableComponentBlueprint.make({
|
||||
params: define =>
|
||||
define({
|
||||
component: AdaptableNotFoundErrorPage,
|
||||
loader: () => NotFoundErrorPageComponent,
|
||||
loader: () => () =>
|
||||
<ErrorPage status="404" statusMessage="PAGE NOT FOUND" />,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -45,6 +51,16 @@ export const ErrorBoundary = AdaptableComponentBlueprint.make({
|
||||
params: define =>
|
||||
define({
|
||||
component: AdaptableErrorBoundary,
|
||||
loader: () => ErrorBoundaryComponent,
|
||||
loader: () => props => {
|
||||
const { plugin, error, resetError } = props;
|
||||
const title = `Error in ${plugin?.id}`;
|
||||
return (
|
||||
<ErrorPanel title={title} error={error} defaultExpanded>
|
||||
<Button variant="outlined" onClick={resetError}>
|
||||
Retry
|
||||
</Button>
|
||||
</ErrorPanel>
|
||||
);
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -32295,12 +32295,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"for-each@npm:^0.3.3":
|
||||
version: 0.3.3
|
||||
resolution: "for-each@npm:0.3.3"
|
||||
"for-each@npm:^0.3.3, for-each@npm:^0.3.5":
|
||||
version: 0.3.5
|
||||
resolution: "for-each@npm:0.3.5"
|
||||
dependencies:
|
||||
is-callable: "npm:^1.1.3"
|
||||
checksum: 10/fdac0cde1be35610bd635ae958422e8ce0cc1313e8d32ea6d34cfda7b60850940c1fd07c36456ad76bd9c24aef6ff5e03b02beb58c83af5ef6c968a64eada676
|
||||
is-callable: "npm:^1.2.7"
|
||||
checksum: 10/330cc2439f85c94f4609de3ee1d32c5693ae15cdd7fe3d112c4fd9efd4ce7143f2c64ef6c2c9e0cfdb0058437f33ef05b5bdae5b98fcc903fb2143fbaf0fea0f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -34907,7 +34907,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-callable@npm:^1.1.3, is-callable@npm:^1.1.5, is-callable@npm:^1.2.7":
|
||||
"is-callable@npm:^1.1.5, is-callable@npm:^1.2.7":
|
||||
version: 1.2.7
|
||||
resolution: "is-callable@npm:1.2.7"
|
||||
checksum: 10/48a9297fb92c99e9df48706241a189da362bff3003354aea4048bd5f7b2eb0d823cd16d0a383cece3d76166ba16d85d9659165ac6fcce1ac12e6c649d66dbdb9
|
||||
@@ -45108,14 +45108,16 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"regexp.prototype.flags@npm:^1.5.3":
|
||||
version: 1.5.3
|
||||
resolution: "regexp.prototype.flags@npm:1.5.3"
|
||||
version: 1.5.4
|
||||
resolution: "regexp.prototype.flags@npm:1.5.4"
|
||||
dependencies:
|
||||
call-bind: "npm:^1.0.7"
|
||||
call-bind: "npm:^1.0.8"
|
||||
define-properties: "npm:^1.2.1"
|
||||
es-errors: "npm:^1.3.0"
|
||||
get-proto: "npm:^1.0.1"
|
||||
gopd: "npm:^1.2.0"
|
||||
set-function-name: "npm:^2.0.2"
|
||||
checksum: 10/fe17bc4eebbc72945aaf9dd059eb7784a5ca453a67cc4b5b3e399ab08452c9a05befd92063e2c52e7b24d9238c60031656af32dd57c555d1ba6330dbf8c23b43
|
||||
checksum: 10/8ab897ca445968e0b96f6237641510f3243e59c180ee2ee8d83889c52ff735dd1bf3657fcd36db053e35e1d823dd53f2565d0b8021ea282c9fe62401c6c3bd6d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -50789,16 +50791,17 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2":
|
||||
version: 1.1.18
|
||||
resolution: "which-typed-array@npm:1.1.18"
|
||||
version: 1.1.19
|
||||
resolution: "which-typed-array@npm:1.1.19"
|
||||
dependencies:
|
||||
available-typed-arrays: "npm:^1.0.7"
|
||||
call-bind: "npm:^1.0.8"
|
||||
call-bound: "npm:^1.0.3"
|
||||
for-each: "npm:^0.3.3"
|
||||
call-bound: "npm:^1.0.4"
|
||||
for-each: "npm:^0.3.5"
|
||||
get-proto: "npm:^1.0.1"
|
||||
gopd: "npm:^1.2.0"
|
||||
has-tostringtag: "npm:^1.0.2"
|
||||
checksum: 10/11eed801b2bd08cdbaecb17aff381e0fb03526532f61acc06e6c7b9370e08062c33763a51f27825f13fdf34aabd0df6104007f4e8f96e6eaef7db0ce17a26d6e
|
||||
checksum: 10/12be30fb88567f9863186bee1777f11bea09dd59ed8b3ce4afa7dd5cade75e2f4cc56191a2da165113cc7cf79987ba021dac1e22b5b62aa7e5c56949f2469a68
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user