From bdf32a00a031bc6cf67bb73940138871e16f1abe Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 May 2020 14:16:43 +0200 Subject: [PATCH] packages/core-api: add configLoader option to App --- packages/core-api/src/app/App.tsx | 44 ++++++++++++++------ packages/core-api/src/app/types.ts | 27 ++++++++++++ packages/core/src/api-wrappers/createApp.tsx | 27 +++++++++++- 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 844c38ecce..c3ea77d87f 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -17,7 +17,7 @@ import React, { ComponentType, FC } from 'react'; import { Route, Switch, Redirect } from 'react-router-dom'; import { AppContextProvider } from './AppContext'; -import { BackstageApp, AppComponents } from './types'; +import { BackstageApp, AppComponents, AppConfigLoader } from './types'; import { BackstagePlugin } from '../plugin'; import { FeatureFlagsRegistryItem } from './FeatureFlags'; import { featureFlagsApiRef } from '../apis/definitions'; @@ -31,8 +31,11 @@ import { AppTheme, AppThemeSelector, appThemeApiRef, + configApiRef, + ConfigReader, } from '../apis'; import { ApiAggregator } from '../apis/ApiAggregator'; +import { useAsync } from 'react-use'; type FullAppOptions = { apis: ApiHolder; @@ -40,6 +43,7 @@ type FullAppOptions = { plugins: BackstagePlugin[]; components: AppComponents; themes: AppTheme[]; + configLoader: AppConfigLoader; }; export class PrivateAppImpl implements BackstageApp { @@ -48,6 +52,7 @@ export class PrivateAppImpl implements BackstageApp { private readonly plugins: BackstagePlugin[]; private readonly components: AppComponents; private readonly themes: AppTheme[]; + private readonly configLoader: AppConfigLoader; constructor(options: FullAppOptions) { this.apis = options.apis; @@ -55,6 +60,7 @@ export class PrivateAppImpl implements BackstageApp { this.plugins = options.plugins; this.components = options.components; this.themes = options.themes; + this.configLoader = options.configLoader; } getApis(): ApiHolder { @@ -141,18 +147,32 @@ export class PrivateAppImpl implements BackstageApp { } getProvider(): ComponentType<{}> { - const appApis = ApiRegistry.from([ - [appThemeApiRef, AppThemeSelector.createWithStorage(this.themes)], - ]); - const apis = new ApiAggregator(this.apis, appApis); + const Provider: FC<{}> = ({ children }) => { + const config = useAsync(this.configLoader); + if (config.loading) { + return null; + } - const Provider: FC<{}> = ({ children }) => ( - - - {children} - - - ); + let errorPage = undefined; + if (config.error) { + const { BootErrorPage } = this.components; + errorPage = ; + } + + const appApis = ApiRegistry.from([ + [appThemeApiRef, AppThemeSelector.createWithStorage(this.themes)], + [configApiRef, new ConfigReader(config.value ?? {})], + ]); + const apis = new ApiAggregator(this.apis, appApis); + + return ( + + + {errorPage ?? children} + + + ); + }; return Provider; } diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index ea3a812557..e43a625569 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -20,10 +20,26 @@ import { BackstagePlugin } from '../plugin'; import { ApiHolder } from '../apis'; import { AppTheme } from '../apis/definitions'; +export type BootErrorPageProps = { + step: 'load-config'; + error: Error; +}; + export type AppComponents = { NotFoundErrorPage: ComponentType<{}>; + BootErrorPage: ComponentType; }; +/** + * TBD + */ +export type AppConfig = any; + +/** + * A function that loads in the App config that will be accessible via the ConfigApi. + */ +export type AppConfigLoader = () => Promise; + export type AppOptions = { /** * A holder of all APIs available in the app. @@ -68,6 +84,17 @@ export type AppOptions = { * ``` */ themes?: AppTheme[]; + + /** + * A function that loads in App configuration that will be accessible via + * the ConfigApi. + * + * Defaults to an empty config. + * + * TODO(Rugvip): Omitting this should instead default to loading in configuration + * that was packaged by the backstage-cli and default docker container boot script. + */ + configLoader?: AppConfigLoader; }; export type BackstageApp = { diff --git a/packages/core/src/api-wrappers/createApp.tsx b/packages/core/src/api-wrappers/createApp.tsx index 03adc41ec6..cc083b0e24 100644 --- a/packages/core/src/api-wrappers/createApp.tsx +++ b/packages/core/src/api-wrappers/createApp.tsx @@ -14,12 +14,14 @@ * limitations under the License. */ -import React from 'react'; +import React, { FC } from 'react'; import privateExports, { AppOptions, ApiRegistry, defaultSystemIcons, + BootErrorPageProps, } from '@backstage/core-api'; +import { BrowserRouter as Router } from 'react-router-dom'; import { ErrorPage } from '../layout/ErrorPage'; import { lightTheme, darkTheme } from '@backstage/theme'; @@ -38,12 +40,25 @@ export function createApp(options?: AppOptions) { const DefaultNotFoundPage = () => ( ); + const DefaultBootErrorPage: FC = ({ step, error }) => { + let message = ''; + if (step === 'load-config') { + message = `The configuration failed to load, someone should have a look at this error: ${error.message}`; + } + // TODO: figure out a nicer way to handle routing on the error page, when it can be done. + return ( + + + + ); + }; const apis = options?.apis ?? ApiRegistry.from([]); const icons = { ...defaultSystemIcons, ...options?.icons }; const plugins = options?.plugins ?? []; const components = { NotFoundErrorPage: DefaultNotFoundPage, + BootErrorPage: DefaultBootErrorPage, ...options?.components, }; const themes = options?.themes ?? [ @@ -60,8 +75,16 @@ export function createApp(options?: AppOptions) { theme: darkTheme, }, ]; + const configLoader = options?.configLoader ?? (async () => ({})); - const app = new PrivateAppImpl({ apis, icons, plugins, components, themes }); + const app = new PrivateAppImpl({ + apis, + icons, + plugins, + components, + themes, + configLoader, + }); app.verify();