diff --git a/.changeset/brave-beers-happen.md b/.changeset/brave-beers-happen.md new file mode 100644 index 0000000000..34bbb95666 --- /dev/null +++ b/.changeset/brave-beers-happen.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Update alpha component ref type to be more specific than any, delete boot page component and use new plugin type for error boundary component extensions. diff --git a/.changeset/good-wolves-leave.md b/.changeset/good-wolves-leave.md new file mode 100644 index 0000000000..51ac99a55a --- /dev/null +++ b/.changeset/good-wolves-leave.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Use the new plugin type for error boundary components. diff --git a/.changeset/sharp-dingos-learn.md b/.changeset/sharp-dingos-learn.md new file mode 100644 index 0000000000..9447ca9698 --- /dev/null +++ b/.changeset/sharp-dingos-learn.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': patch +--- + +Delete alpha DI compatibility helper for components, migrating components should be simple without a helper. diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index ccfeb61b58..d7ec30bf95 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { createApp } from '@backstage/frontend-app-api'; import { pagesPlugin } from './examples/pagesPlugin'; -import { CustomNotFoundErrorPage } from './examples/notFoundErrorPageExtension'; +import notFoundErrorPage from './examples/notFoundErrorPageExtension'; import graphiqlPlugin from '@backstage/plugin-graphiql/alpha'; import techRadarPlugin from '@backstage/plugin-tech-radar/alpha'; import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; @@ -33,10 +33,7 @@ import { } from '@backstage/frontend-plugin-api'; import techdocsPlugin from '@backstage/plugin-techdocs/alpha'; import { homePage } from './HomePage'; -import { - collectLegacyComponents, - collectLegacyRoutes, -} from '@backstage/core-compat-api'; +import { collectLegacyRoutes } from '@backstage/core-compat-api'; import { FlatRoutes } from '@backstage/core-app-api'; import { Route } from 'react-router'; import { CatalogImportPage } from '@backstage/plugin-catalog-import'; @@ -118,10 +115,6 @@ const collectedLegacyPlugins = collectLegacyRoutes( , ); -const legacyAppComponents = collectLegacyComponents({ - NotFoundErrorPage: CustomNotFoundErrorPage, -}); - const app = createApp({ features: [ graphiqlPlugin, @@ -137,7 +130,7 @@ const app = createApp({ scmAuthExtension, scmIntegrationApi, signInPage, - ...legacyAppComponents, + notFoundErrorPage, ], }), ], diff --git a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx index 82acc25fb5..06e9b50bb8 100644 --- a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx +++ b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { createComponentExtension, - coreComponentsRefs, + coreComponentRefs, } from '@backstage/frontend-plugin-api'; import { Box, Typography } from '@material-ui/core'; import { Button } from '@backstage/core-components'; @@ -36,15 +36,9 @@ export function CustomNotFoundErrorPage() { > 404 - Bowie was unable to locate this page. Please contact your support team - if this page used to exist. + Unable to locate this page. Please contact your support team if this + page used to exist. - Backstage bowie + + ); + }, + }, }); diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx index 2713a9743e..32773de7e1 100644 --- a/packages/frontend-app-api/src/wiring/createApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx @@ -201,10 +201,9 @@ describe('createApp', () => { ] components [ - - - - + + + ] themes [ diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 2580307d2f..c1c1e3e283 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -88,7 +88,6 @@ import { createAppTree } from '../tree'; import { DefaultProgressComponent, DefaultErrorBoundaryComponent, - DefaultBootErrorPageComponent, DefaultNotFoundErrorPageComponent, } from '../extensions/components'; import { AppNode } from '@backstage/frontend-plugin-api'; @@ -108,7 +107,6 @@ export const builtinExtensions = [ CoreLayout, DefaultProgressComponent, DefaultErrorBoundaryComponent, - DefaultBootErrorPageComponent, DefaultNotFoundErrorPageComponent, LightTheme, DarkTheme, diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 826b1a62a7..8934badbc0 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -22,7 +22,6 @@ import { AuthProviderInfo } from '@backstage/core-plugin-api'; import { AuthRequestOptions } from '@backstage/core-plugin-api'; import { BackstageIdentityApi } from '@backstage/core-plugin-api'; import { BackstageIdentityResponse } from '@backstage/core-plugin-api'; -import { BackstagePlugin as BackstagePlugin_2 } from '@backstage/core-plugin-api'; import { BackstageUserIdentity } from '@backstage/core-plugin-api'; import { bitbucketAuthApiRef } from '@backstage/core-plugin-api'; import { bitbucketServerAuthApiRef } from '@backstage/core-plugin-api'; @@ -68,7 +67,6 @@ import { OpenIdConnectApi } from '@backstage/core-plugin-api'; import { PendingOAuthRequest } from '@backstage/core-plugin-api'; import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; -import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { SessionApi } from '@backstage/core-plugin-api'; @@ -297,7 +295,7 @@ export type CommonAnalyticsContext = { }; // @public (undocumented) -export type ComponentRef = { +export type ComponentRef = { id: string; T: T; }; @@ -305,7 +303,7 @@ export type ComponentRef = { // @public export interface ComponentsApi { // (undocumented) - getComponent(ref: ComponentRef): T; + getComponent(ref: ComponentRef): ComponentType; } // @public @@ -332,29 +330,18 @@ export interface ConfigurableExtensionDataRef< } // @public (undocumented) -export type CoreBootErrorPageComponent = ComponentType< - PropsWithChildren<{ - step: 'load-config' | 'load-chunk'; - error: Error; - }> ->; - -// @public (undocumented) -export const coreComponentsRefs: { - progress: ComponentRef; - bootErrorPage: ComponentRef; - notFoundErrorPage: ComponentRef; - errorBoundaryFallback: ComponentRef; +export const coreComponentRefs: { + progress: ComponentRef; + notFoundErrorPage: ComponentRef; + errorBoundaryFallback: ComponentRef; }; // @public (undocumented) -export type CoreErrorBoundaryFallbackComponent = ComponentType< - PropsWithChildren<{ - plugin?: BackstagePlugin_2; - error: Error; - resetError: () => void; - }> ->; +export type CoreErrorBoundaryFallbackProps = { + plugin?: BackstagePlugin; + error: Error; + resetError: () => void; +}; // @public (undocumented) export const coreExtensionData: { @@ -367,20 +354,20 @@ export const coreExtensionData: { logoElements: ConfigurableExtensionDataRef; component: ConfigurableExtensionDataRef< { - ref: ComponentRef>; - impl: ComponentType; + ref: ComponentRef; + impl: ComponentType; }, {} >; }; // @public (undocumented) -export type CoreNotFoundErrorPageComponent = ComponentType< - PropsWithChildren<{}> ->; +export type CoreNotFoundErrorPageProps = { + children?: ReactNode; +}; // @public (undocumented) -export type CoreProgressComponent = ComponentType>; +export type CoreProgressProps = {}; // @public (undocumented) export function createApiExtension< @@ -410,11 +397,11 @@ export { createApiRef }; // @public (undocumented) export function createComponentExtension< - TRef extends ComponentRef, + TProps extends {}, TConfig extends {}, TInputs extends AnyExtensionInputMap, >(options: { - ref: TRef; + ref: ComponentRef; name?: string; disabled?: boolean; inputs?: TInputs; @@ -424,13 +411,13 @@ export function createComponentExtension< lazy: (values: { config: TConfig; inputs: Expand>; - }) => Promise; + }) => Promise>; } | { sync: (values: { config: TConfig; inputs: Expand>; - }) => TRef['T']; + }) => ComponentType; }; }): ExtensionDefinition; @@ -1005,6 +992,11 @@ export { useApi }; export { useApiHolder }; +// @public +export function useComponentRef( + ref: ComponentRef, +): ComponentType; + // @public export function useRouteRef< TOptional extends boolean, diff --git a/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts index b55ed96549..c5e9d2c44f 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; +import { ComponentType } from 'react'; +import { createApiRef, useApi } from '@backstage/core-plugin-api'; import { ComponentRef } from '../../components'; /** @@ -24,7 +25,7 @@ import { ComponentRef } from '../../components'; */ export interface ComponentsApi { // TODO: Should component refs also provide the default implementation so that we're guaranteed to get a component? - getComponent(ref: ComponentRef): T; + getComponent(ref: ComponentRef): ComponentType; } /** @@ -35,3 +36,14 @@ export interface ComponentsApi { export const componentsApiRef = createApiRef({ id: 'core.components', }); + +/** + * @public + * Returns the component associated with the given ref. + */ +export function useComponentRef( + ref: ComponentRef, +): ComponentType { + const componentsApi = useApi(componentsApiRef); + return componentsApi.getComponent(ref); +} diff --git a/packages/frontend-plugin-api/src/components/ComponentRef.tsx b/packages/frontend-plugin-api/src/components/ComponentRef.tsx index 2db85b9a42..50df9552ec 100644 --- a/packages/frontend-plugin-api/src/components/ComponentRef.tsx +++ b/packages/frontend-plugin-api/src/components/ComponentRef.tsx @@ -15,54 +15,44 @@ */ import { - CoreBootErrorPageComponent, - CoreErrorBoundaryFallbackComponent, - CoreNotFoundErrorPageComponent, - CoreProgressComponent, + CoreErrorBoundaryFallbackProps, + CoreNotFoundErrorPageProps, + CoreProgressProps, } from '../types'; /** @public */ -export type ComponentRef = { +export type ComponentRef = { id: string; T: T; }; /** @public */ -export function createComponentRef(options: { +export function createComponentRef(options: { id: string; }): ComponentRef { const { id } = options; return { id, - get T(): T { - throw new Error(`tried to read ComponentRef.T of ${id}`); - }, - }; + } as ComponentRef; } -const coreProgressComponentRef = createComponentRef({ +const coreProgressComponentRef = createComponentRef({ id: 'core.components.progress', }); -const coreBootErrorPageComponentRef = - createComponentRef({ - id: 'core.components.bootErrorPage', - }); - const coreNotFoundErrorPageComponentRef = - createComponentRef({ + createComponentRef({ id: 'core.components.notFoundErrorPage', }); const coreErrorBoundaryFallbackComponentRef = - createComponentRef({ + createComponentRef({ id: 'core.components.errorBoundaryFallback', }); /** @public */ -export const coreComponentsRefs = { +export const coreComponentRefs = { progress: coreProgressComponentRef, - bootErrorPage: coreBootErrorPageComponentRef, notFoundErrorPage: coreNotFoundErrorPageComponentRef, errorBoundaryFallback: coreErrorBoundaryFallbackComponentRef, }; diff --git a/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx b/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx index 1191b75a1d..be988dc14f 100644 --- a/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx @@ -14,35 +14,14 @@ * limitations under the License. */ -import React, { Component, PropsWithChildren } from 'react'; -// TODO: Dependency on MUI should be removed from core packages -import { Button } from '@material-ui/core'; -import { ErrorPanel } from '@backstage/core-components'; +import React, { Component, ComponentType, PropsWithChildren } from 'react'; import { BackstagePlugin } from '../wiring'; +import { CoreErrorBoundaryFallbackProps } from '../types'; -type DefaultErrorBoundaryFallbackProps = PropsWithChildren<{ +type ErrorBoundaryProps = PropsWithChildren<{ plugin?: BackstagePlugin; - error: Error; - resetError: () => void; + fallback: ComponentType; }>; - -const DefaultErrorBoundaryFallback = ({ - plugin, - error, - resetError, -}: DefaultErrorBoundaryFallbackProps) => { - const title = `Error in ${plugin?.id}`; - - return ( - - - - ); -}; - -type ErrorBoundaryProps = PropsWithChildren<{ plugin?: BackstagePlugin }>; type ErrorBoundaryState = { error?: Error }; /** @internal */ @@ -62,12 +41,11 @@ export class ErrorBoundary extends Component< render() { const { error } = this.state; - const { plugin, children } = this.props; + const { plugin, children, fallback: Fallback } = this.props; if (error) { - // TODO: use a configurable error boundary fallback return ( - - + }> + {children} diff --git a/packages/frontend-plugin-api/src/components/index.ts b/packages/frontend-plugin-api/src/components/index.ts index a7d947599f..893918059b 100644 --- a/packages/frontend-plugin-api/src/components/index.ts +++ b/packages/frontend-plugin-api/src/components/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export { coreComponentsRefs, type ComponentRef } from './ComponentRef'; +export { coreComponentRefs, type ComponentRef } from './ComponentRef'; export { ExtensionBoundary, diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx index 601ee81d11..816dee6054 100644 --- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { lazy } from 'react'; +import React, { lazy, ComponentType } from 'react'; import { AnyExtensionInputMap, ResolvedExtensionInputs, @@ -27,11 +27,11 @@ import { ExtensionBoundary, ComponentRef } from '../components'; /** @public */ export function createComponentExtension< - TRef extends ComponentRef, + TProps extends {}, TConfig extends {}, TInputs extends AnyExtensionInputMap, >(options: { - ref: TRef; + ref: ComponentRef; name?: string; disabled?: boolean; inputs?: TInputs; @@ -41,13 +41,13 @@ export function createComponentExtension< lazy: (values: { config: TConfig; inputs: Expand>; - }) => Promise; + }) => Promise>; } | { sync: (values: { config: TConfig; inputs: Expand>; - }) => TRef['T']; + }) => ComponentType; }; }) { return createExtension({ @@ -62,15 +62,17 @@ export function createComponentExtension< component: coreExtensionData.component, }, factory({ config, inputs, node }) { - let ExtensionComponent: TRef['T']; + let ExtensionComponent: ComponentType; if ('sync' in options.component) { ExtensionComponent = options.component.sync({ config, inputs }); } else { - const loader = options.component.lazy({ config, inputs }); + const lazyLoader = options.component.lazy; ExtensionComponent = lazy(() => - loader.then(component => ({ default: component })), - ); + lazyLoader({ config, inputs }).then(component => ({ + default: component, + })), + ) as unknown as ComponentType; } return { @@ -78,7 +80,7 @@ export function createComponentExtension< ref: options.ref, impl: props => ( - + ), }, diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts index ca3b291d64..263380b1f8 100644 --- a/packages/frontend-plugin-api/src/index.ts +++ b/packages/frontend-plugin-api/src/index.ts @@ -32,8 +32,7 @@ export * from './translation'; export * from './wiring'; export type { - CoreProgressComponent, - CoreBootErrorPageComponent, - CoreNotFoundErrorPageComponent, - CoreErrorBoundaryFallbackComponent, + CoreProgressProps, + CoreNotFoundErrorPageProps, + CoreErrorBoundaryFallbackProps, } from './types'; diff --git a/packages/frontend-plugin-api/src/types.ts b/packages/frontend-plugin-api/src/types.ts index f3f68c362c..4d0fb1ac4b 100644 --- a/packages/frontend-plugin-api/src/types.ts +++ b/packages/frontend-plugin-api/src/types.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { ComponentType, PropsWithChildren } from 'react'; +import { ReactNode } from 'react'; +import { BackstagePlugin } from './wiring'; // TODO(Rugvip): This might be a quite useful utility type, maybe add to @backstage/types? /** @@ -25,26 +25,16 @@ import { ComponentType, PropsWithChildren } from 'react'; export type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; /** @public */ -export type CoreProgressComponent = ComponentType>; +export type CoreProgressProps = {}; /** @public */ -export type CoreBootErrorPageComponent = ComponentType< - PropsWithChildren<{ - step: 'load-config' | 'load-chunk'; - error: Error; - }> ->; +export type CoreNotFoundErrorPageProps = { + children?: ReactNode; +}; /** @public */ -export type CoreNotFoundErrorPageComponent = ComponentType< - PropsWithChildren<{}> ->; - -/** @public */ -export type CoreErrorBoundaryFallbackComponent = ComponentType< - PropsWithChildren<{ - plugin?: BackstagePlugin; - error: Error; - resetError: () => void; - }> ->; +export type CoreErrorBoundaryFallbackProps = { + plugin?: BackstagePlugin; + error: Error; + resetError: () => void; +}; diff --git a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts index 9bdb4b23eb..d52a672679 100644 --- a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts +++ b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts @@ -47,7 +47,7 @@ export const coreExtensionData = { theme: createExtensionDataRef('core.theme'), logoElements: createExtensionDataRef('core.logos'), component: createExtensionDataRef<{ - ref: ComponentRef>; - impl: ComponentType; - }>('component.ref'), + ref: ComponentRef; + impl: ComponentType; + }>('core.component'), };