refactor: use component ref

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2023-10-30 12:13:38 +01:00
parent a159a0498e
commit 52cbee4025
12 changed files with 217 additions and 375 deletions
@@ -0,0 +1,63 @@
/*
* 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 {
CoreBootErrorPageComponent,
CoreErrorBoundaryFallbackComponent,
CoreNotFoundErrorPageComponent,
CoreProgressComponent,
} from '../types';
/** @public */
export type ComponentRef<T> = {
id: string;
T: T;
};
/** @public */
export function createComponentRef<T>(options: {
id: string;
}): ComponentRef<T> {
const { id } = options;
return {
id,
get T(): T {
throw new Error(`tried to read ComponentRef.T of ${id}`);
},
};
}
/** @public */
export const coreProgressComponentRef =
createComponentRef<CoreProgressComponent>({ id: 'core.components.progress' });
/** @public */
export const coreBootErrorPageComponentRef =
createComponentRef<CoreBootErrorPageComponent>({
id: 'core.components.bootErrorPage',
});
/** @public */
export const coreNotFoundErrorPageComponentRef =
createComponentRef<CoreNotFoundErrorPageComponent>({
id: 'core.components.notFoundErrorPage',
});
/** @public */
export const coreErrorBoundaryFallbackComponentRef =
createComponentRef<CoreErrorBoundaryFallbackComponent>({
id: 'core.components.errorBoundaryFallback',
});
@@ -14,10 +14,14 @@
* limitations under the License.
*/
import React, { PropsWithChildren, ReactNode, useEffect } from 'react';
import React, {
PropsWithChildren,
ReactNode,
Suspense,
useEffect,
} from 'react';
import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';
import { ErrorBoundary } from './ErrorBoundary';
import { ExtensionSuspense } from './ExtensionSuspense';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker';
import { AppNode } from '../apis';
@@ -60,12 +64,12 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) {
};
return (
<ExtensionSuspense>
<Suspense fallback="Loading...">
<ErrorBoundary plugin={node.spec.source}>
<AnalyticsContext attributes={attributes}>
<RouteTracker disableTracking={!routable}>{children}</RouteTracker>
</AnalyticsContext>
</ErrorBoundary>
</ExtensionSuspense>
</Suspense>
);
}
@@ -14,6 +14,14 @@
* limitations under the License.
*/
export {
coreProgressComponentRef,
coreBootErrorPageComponentRef,
coreErrorBoundaryFallbackComponentRef,
coreNotFoundErrorPageComponentRef,
type ComponentRef,
} from './ComponentRef';
export { ExtensionError } from './ExtensionError';
export {
@@ -21,40 +21,36 @@ import {
coreExtensionData,
createExtension,
} from '../wiring';
import {
Expand,
CoreProgressComponent,
CoreBootErrorPageComponent,
CoreNotFoundErrorPageComponent,
CoreErrorBoundaryFallbackComponent,
} from '../types';
import { Expand } from '../types';
import { PortableSchema } from '../schema';
import { ExtensionError, ExtensionBoundary } from '../components';
import { ExtensionError, ExtensionBoundary, ComponentRef } from '../components';
/** @public */
export function createProgressExtension<
export function createComponentExtension<
TRef extends ComponentRef<any>,
TConfig extends {},
TInputs extends AnyExtensionInputMap,
>(options: {
ref: TRef;
disabled?: boolean;
inputs?: TInputs;
configSchema?: PortableSchema<TConfig>;
component: (options: {
component: (values: {
config: TConfig;
inputs: Expand<ExtensionInputValues<TInputs>>;
}) => Promise<CoreProgressComponent>;
}) => Promise<TRef['T']>;
}) {
const id = 'core.components.progress';
const id = options.ref.id;
return createExtension({
id,
attachTo: { id: 'core.components', input: 'progress' },
attachTo: { id: 'core', input: 'components' },
inputs: options.inputs,
disabled: options.disabled,
configSchema: options.configSchema,
output: {
component: coreExtensionData.components.progress,
component: coreExtensionData.component,
},
factory({ bind, config, inputs, source }) {
factory({ config, inputs, source }) {
const ExtensionComponent = lazy(() =>
options
.component({ config, inputs })
@@ -64,145 +60,16 @@ export function createProgressExtension<
})),
);
bind({
component: props => (
<ExtensionBoundary id={id} source={source}>
<ExtensionComponent {...props} />
</ExtensionBoundary>
),
});
},
});
}
/** @public */
export function createBootErrorPageExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
>(options: {
disabled?: boolean;
inputs?: TInputs;
configSchema?: PortableSchema<TConfig>;
component: (options: {
config: TConfig;
inputs: Expand<ExtensionInputValues<TInputs>>;
}) => Promise<CoreBootErrorPageComponent>;
}) {
const id = 'core.components.bootErrorPage';
return createExtension({
id,
attachTo: { id: 'core.components', input: 'bootErrorPage' },
inputs: options.inputs,
disabled: options.disabled,
configSchema: options.configSchema,
output: {
component: coreExtensionData.components.bootErrorPage,
},
factory({ bind, config, inputs, source }) {
const ExtensionComponent = lazy(() =>
options
.component({ config, inputs })
.then(component => ({ default: component }))
.catch(error => ({
default: () => <ExtensionError error={error} />,
})),
);
bind({
component: props => (
<ExtensionBoundary id={id} source={source}>
<ExtensionComponent {...props} />
</ExtensionBoundary>
),
});
},
});
}
/** @public */
export function createNotFoundErrorPageExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
>(options: {
disabled?: boolean;
inputs?: TInputs;
configSchema?: PortableSchema<TConfig>;
component: (options: {
config: TConfig;
inputs: Expand<ExtensionInputValues<TInputs>>;
}) => Promise<CoreNotFoundErrorPageComponent>;
}) {
const id = 'core.components.notFoundErrorPage';
return createExtension({
id,
attachTo: { id: 'core.components', input: 'notFoundErrorPage' },
inputs: options.inputs,
disabled: options.disabled,
configSchema: options.configSchema,
output: {
component: coreExtensionData.components.notFoundErrorPage,
},
factory({ bind, config, inputs, source }) {
const ExtensionComponent = lazy(() =>
options
.component({ config, inputs })
.then(component => ({ default: component }))
.catch(error => ({
default: () => <ExtensionError error={error} />,
})),
);
bind({
component: props => (
<ExtensionBoundary id={id} source={source}>
<ExtensionComponent {...props} />
</ExtensionBoundary>
),
});
},
});
}
/** @public */
export function createErrorBoundaryFallbackExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
>(options: {
disabled?: boolean;
inputs?: TInputs;
configSchema?: PortableSchema<TConfig>;
component: (options: {
config: TConfig;
inputs: Expand<ExtensionInputValues<TInputs>>;
}) => Promise<CoreErrorBoundaryFallbackComponent>;
}) {
const id = 'core.components.errorBoundaryFallback';
return createExtension({
id,
attachTo: { id: 'core.components', input: 'errorBoundaryFallback' },
inputs: options.inputs,
disabled: options.disabled,
configSchema: options.configSchema,
output: {
component: coreExtensionData.components.errorBoundaryFallback,
},
factory({ bind, config, inputs, source }) {
const ExtensionComponent = lazy(() =>
options
.component({ config, inputs })
.then(component => ({ default: component }))
.catch(error => ({
default: () => <ExtensionError error={error} />,
})),
);
bind({
component: props => (
<ExtensionBoundary id={id} source={source}>
<ExtensionComponent {...props} />
</ExtensionBoundary>
),
});
return {
component: {
ref: options.ref,
impl: props => (
<ExtensionBoundary id={id} source={source}>
<ExtensionComponent {...props} />
</ExtensionBoundary>
),
},
};
},
});
}
@@ -19,9 +19,4 @@ export { createPageExtension } from './createPageExtension';
export { createNavItemExtension } from './createNavItemExtension';
export { createSignInPageExtension } from './createSignInPageExtension';
export { createThemeExtension } from './createThemeExtension';
export {
createProgressExtension,
createBootErrorPageExtension,
createNotFoundErrorPageExtension,
createErrorBoundaryFallbackExtension,
} from './createComponentExtension';
export { createComponentExtension } from './createComponentExtension';
@@ -14,19 +14,14 @@
* limitations under the License.
*/
import { JSX } from 'react';
import { ComponentType, JSX } from 'react';
import {
AnyApiFactory,
AppTheme,
IconComponent,
} from '@backstage/core-plugin-api';
import { RouteRef } from '../routing';
import {
CoreProgressComponent,
CoreBootErrorPageComponent,
CoreNotFoundErrorPageComponent,
CoreErrorBoundaryFallbackComponent,
} from '../types';
import { ComponentRef } from '../components';
import { createExtensionDataRef } from './createExtensionDataRef';
/** @public */
@@ -44,9 +39,6 @@ export type LogoElements = {
/** @public */
export const coreExtensionData = {
reactComponent: createExtensionDataRef<
<T extends {}>(props: T) => JSX.Element
>('core.reactComponent'),
reactElement: createExtensionDataRef<JSX.Element>('core.reactElement'),
routePath: createExtensionDataRef<string>('core.routing.path'),
apiFactory: createExtensionDataRef<AnyApiFactory>('core.api.factory'),
@@ -54,19 +46,8 @@ export const coreExtensionData = {
navTarget: createExtensionDataRef<NavTarget>('core.nav.target'),
theme: createExtensionDataRef<AppTheme>('core.theme'),
logoElements: createExtensionDataRef<LogoElements>('core.logos'),
components: {
progress: createExtensionDataRef<CoreProgressComponent>(
'core.components.progress',
),
bootErrorPage: createExtensionDataRef<CoreBootErrorPageComponent>(
'core.components.bootErrorPage',
),
notFoundErrorPage: createExtensionDataRef<CoreNotFoundErrorPageComponent>(
'core.components.notFoundErrorPage',
),
errorBoundaryFallback:
createExtensionDataRef<CoreErrorBoundaryFallbackComponent>(
'core.components.errorBoundary',
),
},
component: createExtensionDataRef<{
ref: ComponentRef<ComponentType<any>>;
impl: ComponentType<any>;
}>('component.ref'),
};