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();