refactor(frontend-plugin-api): merge boundary and suspense components

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2023-10-19 14:54:35 +02:00
parent cca1afc547
commit 66af240ea6
8 changed files with 57 additions and 80 deletions
+1 -12
View File
@@ -340,7 +340,7 @@ export interface ExtensionBoundaryProps {
// (undocumented)
id: string;
// (undocumented)
routeRef?: RouteRef;
routable?: boolean;
// (undocumented)
source?: BackstagePlugin;
}
@@ -416,17 +416,6 @@ export interface ExtensionOverridesOptions {
extensions: Extension<unknown>[];
}
// @public (undocumented)
export function ExtensionSuspense(
props: ExtensionSuspenseProps,
): React_2.JSX.Element;
// @public (undocumented)
export interface ExtensionSuspenseProps {
// (undocumented)
children: ReactNode;
}
// @public
export interface ExternalRouteRef<
TParams extends AnyRouteRefParams = AnyRouteRefParams,
@@ -15,9 +15,10 @@
*/
import React, { Component, PropsWithChildren } from 'react';
// TODO: Dependency on MUI should be removed from core packages
import { Button } from '@material-ui/core';
import { BackstagePlugin } from '../wiring';
import { ErrorPanel } from '@backstage/core-components';
import { BackstagePlugin } from '../wiring';
type DefaultErrorBoundaryFallbackProps = PropsWithChildren<{
plugin?: BackstagePlugin;
@@ -33,7 +33,6 @@ import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api';
import { createApp } from '@backstage/frontend-app-api';
import { JsonObject } from '@backstage/types';
import { createRouteRef } from '../routing';
import { toInternalRouteRef } from '../routing/RouteRef';
function renderExtensionInTestApp(
extension: Extension<unknown>,
@@ -59,7 +58,6 @@ function renderExtensionInTestApp(
const wrapInBoundaryExtension = (element: JSX.Element) => {
const id = 'plugin.extension';
const routeRef = createRouteRef();
toInternalRouteRef(routeRef).setId(id);
return createExtension({
id,
attachTo: { id: 'core.routes', input: 'routes' },
@@ -73,7 +71,7 @@ const wrapInBoundaryExtension = (element: JSX.Element) => {
routeRef,
path: '/',
element: (
<ExtensionBoundary id={id} source={source} routeRef={routeRef}>
<ExtensionBoundary id={id} source={source}>
{element}
</ExtensionBoundary>
),
@@ -128,7 +126,7 @@ describe('ExtensionBoundary', () => {
subject,
context: {
extension: 'plugin.extension',
routeRef: 'plugin.extension',
routeRef: 'unknown',
},
}),
);
@@ -14,36 +14,59 @@
* limitations under the License.
*/
import React, { ReactNode } from 'react';
import { AnalyticsContext } from '@backstage/core-plugin-api';
import React, { PropsWithChildren, ReactNode, useEffect } from 'react';
import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '../wiring';
import { RouteRef } from '../routing';
import { ErrorBoundary } from './ErrorBoundary';
import { toInternalRouteRef } from '../routing/RouteRef';
import { ExtensionSuspense } from './ExtensionSuspense';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker';
type RouteTrackerProps = PropsWithChildren<{
disableTracking?: boolean;
}>;
const RouteTracker = (props: RouteTrackerProps) => {
const { disableTracking, children } = props;
const analytics = useAnalytics();
// This event, never exposed to end-users of the analytics API,
// helps inform which extension metadata gets associated with a
// navigation event when the route navigated to is a gathered
// mountpoint.
useEffect(() => {
if (disableTracking) return;
analytics.captureEvent(routableExtensionRenderedEvent, '');
}, [analytics, disableTracking]);
return <>{children}</>;
};
/** @public */
export interface ExtensionBoundaryProps {
id: string;
source?: BackstagePlugin;
routeRef?: RouteRef;
routable?: boolean;
children: ReactNode;
}
/** @public */
export function ExtensionBoundary(props: ExtensionBoundaryProps) {
const { id, source, routeRef, children } = props;
const { id, source, routable, children } = props;
// Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight
const attributes = {
extension: id,
pluginId: source?.id,
routeRef: routeRef
? toInternalRouteRef(routeRef).getDescription()
: undefined,
};
return (
<ErrorBoundary plugin={source}>
<AnalyticsContext attributes={attributes}>{children}</AnalyticsContext>
</ErrorBoundary>
<ExtensionSuspense>
<ErrorBoundary plugin={source}>
<AnalyticsContext attributes={attributes}>
<RouteTracker disableTracking={!routable}>{children}</RouteTracker>
</AnalyticsContext>
</ErrorBoundary>
</ExtensionSuspense>
);
}
@@ -18,8 +18,3 @@ export {
ExtensionBoundary,
type ExtensionBoundaryProps,
} from './ExtensionBoundary';
export {
ExtensionSuspense,
type ExtensionSuspenseProps,
} from './ExtensionSuspense';
@@ -14,11 +14,8 @@
* limitations under the License.
*/
import React, { lazy, useEffect } from 'react';
import { useAnalytics } from '@backstage/core-plugin-api';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker';
import { ExtensionBoundary, ExtensionSuspense } from '../components';
import React, { lazy } from 'react';
import { ExtensionBoundary } from '../components';
import { createSchemaFromZod, PortableSchema } from '../schema';
import {
coreExtensionData,
@@ -58,9 +55,7 @@ export function createPageExtension<
}) => Promise<JSX.Element>;
},
): Extension<TConfig> {
const { id, routeRef } = options;
const attachTo = options.attachTo ?? { id: 'core.routes', input: 'routes' };
const { id } = options;
const configSchema =
'configSchema' in options
@@ -71,7 +66,7 @@ export function createPageExtension<
return createExtension({
id,
attachTo,
attachTo: options.attachTo ?? { id: 'core.routes', input: 'routes' },
configSchema,
inputs: options.inputs,
disabled: options.disabled,
@@ -81,36 +76,18 @@ export function createPageExtension<
routeRef: coreExtensionData.routeRef.optional(),
},
factory({ bind, config, inputs, source }) {
const { path } = config;
const PageComponent = lazy(() =>
const ExtensionComponent = lazy(() =>
options
.loader({ config, inputs })
.then(element => ({ default: () => element })),
);
const ExtensionComponent = () => {
const analytics = useAnalytics();
// This event, never exposed to end-users of the analytics API,
// helps inform which extension metadata gets associated with a
// navigation event when the route navigated to is a gathered
// mountpoint.
useEffect(() => {
analytics.captureEvent(routableExtensionRenderedEvent, '');
}, [analytics]);
return <PageComponent />;
};
bind({
path,
routeRef,
path: config.path,
routeRef: options.routeRef,
element: (
<ExtensionBoundary id={id} source={source} routeRef={routeRef}>
<ExtensionSuspense>
<ExtensionComponent />
</ExtensionSuspense>
<ExtensionBoundary id={id} source={source} routable>
<ExtensionComponent />
</ExtensionBoundary>
),
});
+1 -4
View File
@@ -36,7 +36,6 @@ import {
PortableSchema,
ExtensionBoundary,
createExtensionInput,
ExtensionSuspense,
} from '@backstage/frontend-plugin-api';
import {
AsyncEntityProvider,
@@ -117,9 +116,7 @@ export function createCatalogFilterExtension<
bind({
element: (
<ExtensionBoundary id={id} source={source}>
<ExtensionSuspense>
<ExtensionComponent />
</ExtensionSuspense>
<ExtensionComponent />
</ExtensionBoundary>
),
});
+7 -10
View File
@@ -20,7 +20,6 @@ import { ListItemProps } from '@material-ui/core';
import {
ExtensionBoundary,
ExtensionSuspense,
PortableSchema,
createExtension,
createExtensionDataRef,
@@ -120,15 +119,13 @@ export function createSearchResultListItemExtension<
predicate: options.predicate,
component: props => (
<ExtensionBoundary id={id} source={source}>
<ExtensionSuspense>
<SearchResultListItemExtension
rank={props.rank}
result={props.result}
noTrack={config.noTrack}
>
<ExtensionComponent {...props} />
</SearchResultListItemExtension>
</ExtensionSuspense>
<SearchResultListItemExtension
rank={props.rank}
result={props.result}
noTrack={config.noTrack}
>
<ExtensionComponent {...props} />
</SearchResultListItemExtension>
</ExtensionBoundary>
),
},