From 413f05a6c9dfe99b17562a448a093619b1b5db35 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 17 Nov 2023 17:10:40 +0100 Subject: [PATCH] refactor: replace components context in app Signed-off-by: Camila Belo --- .../ComponentsApi/ComponentsApi.ts | 8 ++- .../frontend-app-api/src/extensions/Core.tsx | 16 +---- .../src/extensions/CoreRoutes.tsx | 8 ++- .../frontend-app-api/src/wiring/createApp.tsx | 71 ++++++------------- packages/frontend-plugin-api/api-report.md | 23 +++--- .../src/apis/definitions/ComponentsApi.ts | 2 +- .../src/components/ComponentsContext.tsx | 58 --------------- .../src/components/index.ts | 6 -- 8 files changed, 44 insertions(+), 148 deletions(-) delete mode 100644 packages/frontend-plugin-api/src/components/ComponentsContext.tsx diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts index 87a12d136e..50d125e4ab 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts @@ -28,7 +28,11 @@ export class DefaultComponentsApi implements ComponentsApi { this.#components = components; } - getComponent(ref: ComponentRef): T | undefined { - return this.#components.get(ref); + getComponent(ref: ComponentRef): T { + const impl = this.#components.get(ref); + if (!impl) { + throw new Error(`No implementation found for component ref ${ref}`); + } + return impl; } } diff --git a/packages/frontend-app-api/src/extensions/Core.tsx b/packages/frontend-app-api/src/extensions/Core.tsx index 4347eb1b45..441cb5bcf0 100644 --- a/packages/frontend-app-api/src/extensions/Core.tsx +++ b/packages/frontend-app-api/src/extensions/Core.tsx @@ -18,9 +18,7 @@ import { coreExtensionData, createExtension, createExtensionInput, - ComponentsProvider, } from '@backstage/frontend-plugin-api'; -import React from 'react'; export const Core = createExtension({ id: 'core', @@ -46,20 +44,8 @@ export const Core = createExtension({ root: coreExtensionData.reactElement, }, factory({ inputs }) { - const components = inputs.components.reduce( - (rest, { component }) => ({ - ...rest, - [component.ref.id]: component.impl, - }), - {}, - ); - return { - root: ( - - {inputs.root.element} - - ), + root: inputs.root.element, }; }, }); diff --git a/packages/frontend-app-api/src/extensions/CoreRoutes.tsx b/packages/frontend-app-api/src/extensions/CoreRoutes.tsx index b2918cfcc9..37b0e5d62f 100644 --- a/packages/frontend-app-api/src/extensions/CoreRoutes.tsx +++ b/packages/frontend-app-api/src/extensions/CoreRoutes.tsx @@ -20,7 +20,8 @@ import { coreExtensionData, createExtensionInput, coreNotFoundErrorPageComponentRef, - useComponent, + useApi, + componentsApiRef, } from '@backstage/frontend-plugin-api'; import { useRoutes } from 'react-router-dom'; @@ -39,7 +40,10 @@ export const CoreRoutes = createExtension({ }, factory({ inputs }) { const Routes = () => { - const NotFoundErrorPage = useComponent(coreNotFoundErrorPageComponentRef); + const componentsApi = useApi(componentsApiRef); + const NotFoundErrorPage = componentsApi.getComponent( + coreNotFoundErrorPageComponentRef, + ); const element = useRoutes([ ...inputs.routes.map(route => ({ diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index b22d258eae..4e463fb526 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -21,11 +21,8 @@ import { appTreeApiRef, BackstagePlugin, ComponentRef, - coreBootErrorPageComponentRef, - coreErrorBoundaryFallbackComponentRef, + componentsApiRef, coreExtensionData, - coreNotFoundErrorPageComponentRef, - coreProgressComponentRef, ExtensionDataRef, ExtensionOverrides, RouteRef, @@ -108,6 +105,7 @@ import { CoreRouter } from '../extensions/CoreRouter'; import { toInternalBackstagePlugin } from '../../../frontend-plugin-api/src/wiring/createPlugin'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides'; +import { DefaultComponentsApi } from '../apis/implementations/ComponentsApi'; export const builtinExtensions = [ Core, @@ -319,7 +317,6 @@ export function createSpecializedApp(options?: { features.filter( (f): f is BackstagePlugin => f.$$type === '@backstage/BackstagePlugin', ), - tree.root, ); const appIdentityProxy = new AppIdentityProxy(); @@ -375,49 +372,7 @@ export function createSpecializedApp(options?: { }; } -function createLegacyAppContext( - plugins: BackstagePlugin[], - core: AppNode, -): AppContext { - const components = core.edges.attachments - .get('components') - ?.map(e => e.instance?.getData(coreExtensionData.component)); - - function getComponent>( - ref: TRef, - ): TRef['T'] | undefined { - return components?.find(component => component?.ref.id === ref.id)?.impl; - } - - const { - Progress: DefaultProgress, - BootErrorPage: DefaultBootErrorPage, - NotFoundErrorPage: DefaultNotFoundErrorPage, - ErrorBoundaryFallback: DefaultErrorBoundaryFallback, - ...rest - } = defaultComponents; - - const CustomProgress = getComponent(coreProgressComponentRef); - - const CustomBootErrorPage = getComponent(coreBootErrorPageComponentRef); - - const CustomNotFoundErrorPage = getComponent( - coreNotFoundErrorPageComponentRef, - ); - - const CustomErrorBoundaryFallback = getComponent( - coreErrorBoundaryFallbackComponentRef, - ); - - const overriddenComponents = { - ...rest, - Progress: CustomProgress ?? DefaultProgress, - BootErrorPage: CustomBootErrorPage ?? DefaultBootErrorPage, - NotFoundErrorPage: CustomNotFoundErrorPage ?? DefaultNotFoundErrorPage, - ErrorBoundaryFallback: - CustomErrorBoundaryFallback ?? DefaultErrorBoundaryFallback, - }; - +function createLegacyAppContext(plugins: BackstagePlugin[]): AppContext { return { getPlugins(): LegacyBackstagePlugin[] { return plugins.map(toLegacyPlugin); @@ -434,7 +389,7 @@ function createLegacyAppContext( }, getComponents(): AppComponents { - return overriddenComponents; + return defaultComponents; }, }; } @@ -483,6 +438,24 @@ function createApiHolder( }), }); + const componentsExtensions = + tree.root.edges.attachments + .get('components') + ?.map(e => e.instance?.getData(coreExtensionData.component)) + .filter(x => !!x) ?? []; + + const componentsMap = componentsExtensions.reduce( + (components, component) => + component ? components.set(component.ref, component?.impl) : components, + new Map, any>(), + ); + + factoryRegistry.register('static', { + api: componentsApiRef, + deps: {}, + factory: () => new DefaultComponentsApi(componentsMap), + }); + factoryRegistry.register('static', { api: appThemeApiRef, deps: {}, diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 2255dafdd0..3d89c415fa 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -13,6 +13,7 @@ import { AnyApiRef } from '@backstage/core-plugin-api'; import { ApiFactory } from '@backstage/core-plugin-api'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef as ApiRef_2 } from '@backstage/core-plugin-api/src/apis/system/types'; import { ApiRefConfig } from '@backstage/core-plugin-api'; import { AppTheme } from '@backstage/core-plugin-api'; import { AppThemeApi } from '@backstage/core-plugin-api'; @@ -26,7 +27,6 @@ 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'; -import { ComponentRef as ComponentRef_2 } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigApi } from '@backstage/core-plugin-api'; import { configApiRef } from '@backstage/core-plugin-api'; @@ -293,15 +293,14 @@ export type ComponentRef = { T: T; }; -// @public (undocumented) -export type ComponentsContextValue = Record>; +// @public +export interface ComponentsApi { + // (undocumented) + getComponent(ref: ComponentRef): T; +} -// @public (undocumented) -export function ComponentsProvider( - props: PropsWithChildren<{ - value: ComponentsContextValue; - }>, -): React_2.JSX.Element; +// @public +export const componentsApiRef: ApiRef_2; export { ConfigApi }; @@ -910,12 +909,6 @@ export { useApi }; export { useApiHolder }; -// @public (undocumented) -export function useComponent< - P extends {}, - T extends ComponentRef_2>, ->(ref: T): T['T']; - // @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 6f057e70f2..452e692389 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts @@ -24,7 +24,7 @@ import { createApiRef } from '../system'; */ 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 | undefined; + getComponent(ref: ComponentRef): T; } /** diff --git a/packages/frontend-plugin-api/src/components/ComponentsContext.tsx b/packages/frontend-plugin-api/src/components/ComponentsContext.tsx deleted file mode 100644 index 7b1058f83c..0000000000 --- a/packages/frontend-plugin-api/src/components/ComponentsContext.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { ComponentRef } from '@backstage/frontend-plugin-api'; -import { - createVersionedContext, - createVersionedValueMap, - useVersionedContext, -} from '@backstage/version-bridge'; -import React from 'react'; -import { ComponentType, PropsWithChildren } from 'react'; - -/** @public */ -export type ComponentsContextValue = Record>; - -const ComponentsContext = createVersionedContext<{ 1: ComponentsContextValue }>( - 'components-context', -); - -/** @public */ -export function ComponentsProvider( - props: PropsWithChildren<{ value: ComponentsContextValue }>, -) { - const { value, children } = props; - return ( - - {children} - - ); -} - -/** @public */ -export function useComponent< - P extends {}, - T extends ComponentRef>, ->(ref: T): T['T'] { - const components = useVersionedContext<{ 1: ComponentsContextValue }>( - 'components-context', - ); - const component = components?.atVersion(1)?.[ref.id]; - if (!component) { - throw new Error(`No implementation available for ${ref.id}`); - } - return component; -} diff --git a/packages/frontend-plugin-api/src/components/index.ts b/packages/frontend-plugin-api/src/components/index.ts index 575e5efbe0..4bee5c5f7b 100644 --- a/packages/frontend-plugin-api/src/components/index.ts +++ b/packages/frontend-plugin-api/src/components/index.ts @@ -14,12 +14,6 @@ * limitations under the License. */ -export { - ComponentsProvider, - useComponent, - type ComponentsContextValue, -} from './ComponentsContext'; - export { coreProgressComponentRef, coreBootErrorPageComponentRef,