From 64f6f5255c1789740169c948ef089cdede9d65d7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 21 Feb 2021 22:55:25 +0100 Subject: [PATCH 1/6] core-api: migrate app context to use a separate type and implementation --- packages/core-api/src/app/App.tsx | 37 +++++++++++++++++++++++- packages/core-api/src/app/AppContext.tsx | 18 ++++++------ packages/core-api/src/app/types.ts | 27 +++++++++++++++++ 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index f50bcdd015..0062a48853 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -64,6 +64,7 @@ import { AppThemeProvider } from './AppThemeProvider'; import { AppComponents, AppConfigLoader, + AppContext, AppOptions, AppRouteBinder, BackstageApp, @@ -139,6 +140,38 @@ function useConfigLoader( return { api: configReader }; } +class AppContextImpl implements AppContext { + constructor(private readonly app: PrivateAppImpl) {} + + getPlugins(): BackstagePlugin[] { + // eslint-disable-next-line no-console + console.warn('appContext.getPlugins() is deprecated and will be removed'); + return this.app.getPlugins(); + } + + getSystemIcon(key: string): IconComponent { + return this.app.getSystemIcon(key); + } + + getProvider(): React.ComponentType<{}> { + // eslint-disable-next-line no-console + console.warn('appContext.getProvider() is deprecated and will be removed'); + return this.app.getProvider(); + } + + getRouter(): React.ComponentType<{}> { + // eslint-disable-next-line no-console + console.warn('appContext.getRouter() is deprecated and will be removed'); + return this.app.getRouter(); + } + + getRoutes(): JSX.Element[] { + // eslint-disable-next-line no-console + console.warn('appContext.getRoutes() is deprecated and will be removed'); + return this.app.getRoutes(); + } +} + export class PrivateAppImpl implements BackstageApp { private apiHolder?: ApiHolder; private configApi?: ConfigApi; @@ -236,6 +269,8 @@ export class PrivateAppImpl implements BackstageApp { } getProvider(): ComponentType<{}> { + const appContext = new AppContextImpl(this); + const Provider = ({ children }: PropsWithChildren<{}>) => { const appThemeApi = useMemo( () => AppThemeSelector.createWithStorage(this.themes), @@ -273,7 +308,7 @@ export class PrivateAppImpl implements BackstageApp { return ( - + (undefined); +const Context = createContext(undefined); type Props = { - app: BackstageApp; + appContext: AppContext; }; export const AppContextProvider = ({ - app, + appContext, children, }: PropsWithChildren) => ( - + ); -export const useApp = (): BackstageApp => { - const app = useContext(Context); - if (!app) { +export const useApp = (): AppContext => { + const appContext = useContext(Context); + if (!appContext) { throw new Error('No app context available'); } - return app; + return appContext; }; diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 1970868ca6..6f96242d36 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -190,3 +190,30 @@ export type BackstageApp = { */ getRoutes(): JSX.Element[]; }; + +export type AppContext = { + /** + * @deprecated Will be removed + */ + getPlugins(): BackstagePlugin[]; + + /** + * Get a common or custom icon for this app. + */ + getSystemIcon(key: IconKey): IconComponent; + + /** + * @deprecated Will be removed + */ + getProvider(): ComponentType<{}>; + + /** + * @deprecated Will be removed + */ + getRouter(): ComponentType<{}>; + + /** + * @deprecated Will be removed + */ + getRoutes(): JSX.Element[]; +}; From 904280dd7269560a0dd08221b1b8ca80290f6948 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 21 Feb 2021 22:59:27 +0100 Subject: [PATCH 2/6] core-api: add app components to app context --- packages/core-api/src/app/App.tsx | 8 ++++++++ packages/core-api/src/app/types.ts | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 0062a48853..07479ce19b 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -153,6 +153,10 @@ class AppContextImpl implements AppContext { return this.app.getSystemIcon(key); } + getComponents(): AppComponents { + return this.app.getComponents(); + } + getProvider(): React.ComponentType<{}> { // eslint-disable-next-line no-console console.warn('appContext.getProvider() is deprecated and will be removed'); @@ -206,6 +210,10 @@ export class PrivateAppImpl implements BackstageApp { return this.icons[key]; } + getComponents(): AppComponents { + return this.components; + } + getRoutes(): JSX.Element[] { const routes = new Array(); diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 6f96242d36..373839d308 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -202,6 +202,11 @@ export type AppContext = { */ getSystemIcon(key: IconKey): IconComponent; + /** + * Get the components registered for various purposes in the app. + */ + getComponents(): AppComponents; + /** * @deprecated Will be removed */ From c7fd1c6a2e470e0a2a11bca442ffe9881ffa8fad Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 21 Feb 2021 23:02:34 +0100 Subject: [PATCH 3/6] core-api: add NotFoundPage to FlatRoutes --- packages/core-api/src/routing/FlatRoutes.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/core-api/src/routing/FlatRoutes.tsx b/packages/core-api/src/routing/FlatRoutes.tsx index cefb347290..3702442811 100644 --- a/packages/core-api/src/routing/FlatRoutes.tsx +++ b/packages/core-api/src/routing/FlatRoutes.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ -import { ReactNode, Children, isValidElement, Fragment } from 'react'; +import React, { ReactNode, Children, isValidElement, Fragment } from 'react'; import { useRoutes } from 'react-router-dom'; +import { useApp } from '../app'; type RouteObject = { path: string; @@ -71,6 +72,15 @@ type FlatRoutesProps = { }; export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => { + const app = useApp(); + const { NotFoundErrorPage } = app.getComponents(); const routes = createRoutesFromChildren(props.children); + + // TODO(Rugvip): Possibly add a way to skip this, like a noNotFoundPage prop + routes.push({ + element: , + path: '/*', + }); + return useRoutes(routes); }; From 3a58084b656d782349f281241a8b8767c4df6964 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 22 Feb 2021 01:04:10 +0100 Subject: [PATCH 4/6] added changesets --- .changeset/chilly-eels-try.md | 6 ++++++ .changeset/silly-pandas-flash.md | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 .changeset/chilly-eels-try.md create mode 100644 .changeset/silly-pandas-flash.md diff --git a/.changeset/chilly-eels-try.md b/.changeset/chilly-eels-try.md new file mode 100644 index 0000000000..e682c567ea --- /dev/null +++ b/.changeset/chilly-eels-try.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-api': patch +'@backstage/core': patch +--- + +The `FlatRoutes` components now renders the not found page of the app if no routes are matched. diff --git a/.changeset/silly-pandas-flash.md b/.changeset/silly-pandas-flash.md new file mode 100644 index 0000000000..114b2386e0 --- /dev/null +++ b/.changeset/silly-pandas-flash.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-api': patch +'@backstage/core': patch +--- + +Created separate `AppContext` type to be returned from `useApp` rather than the `BackstageApp` itself. The `AppContext` type includes but deprecates `getPlugins`, `getProvider`, `getRouter`, and `getRoutes`. In addition, the `AppContext` adds a new `getComponents` method which providers access to the app components. From c7a4b73f7f9d0b6ddced5aa14f0efbae7dab03e8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 22 Feb 2021 13:20:11 +0100 Subject: [PATCH 5/6] core-api: move feature flag collection to app.getProvider() --- packages/core-api/src/app/App.tsx | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 07479ce19b..3cf5abdb44 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -217,8 +217,6 @@ export class PrivateAppImpl implements BackstageApp { getRoutes(): JSX.Element[] { const routes = new Array(); - const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!; - const { NotFoundErrorPage } = this.components; for (const plugin of this.plugins.values()) { @@ -252,13 +250,6 @@ export class PrivateAppImpl implements BackstageApp { routes.push(); break; } - case 'feature-flag': { - featureFlagsApi.registerFlag({ - name: output.name, - pluginId: plugin.getId(), - }); - break; - } default: break; } @@ -278,6 +269,25 @@ export class PrivateAppImpl implements BackstageApp { getProvider(): ComponentType<{}> { const appContext = new AppContextImpl(this); + const apiHolder = this.getApiHolder(); + + const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!; + + for (const plugin of this.plugins.values()) { + for (const output of plugin.output()) { + switch (output.type) { + case 'feature-flag': { + featureFlagsApi.registerFlag({ + name: output.name, + pluginId: plugin.getId(), + }); + break; + } + default: + break; + } + } + } const Provider = ({ children }: PropsWithChildren<{}>) => { const appThemeApi = useMemo( @@ -315,7 +325,7 @@ export class PrivateAppImpl implements BackstageApp { this.configApi = loadedConfig.api; return ( - + Date: Mon, 22 Feb 2021 13:21:33 +0100 Subject: [PATCH 6/6] core-api: deprecate app.getRoutes --- packages/core-api/src/app/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 373839d308..69eb7f7189 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -187,6 +187,8 @@ export type BackstageApp = { /** * Routes component that contains all routes for plugin pages in the app. + * + * @deprecated Registering routes in plugins is deprecated and this method will be removed. */ getRoutes(): JSX.Element[]; };