packages/core-api: add configLoader option to App

This commit is contained in:
Patrik Oldsberg
2020-05-28 14:16:43 +02:00
parent 1ab6499bbb
commit bdf32a00a0
3 changed files with 84 additions and 14 deletions
+32 -12
View File
@@ -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 }) => (
<ApiProvider apis={apis}>
<AppContextProvider app={this}>
<AppThemeProvider>{children}</AppThemeProvider>
</AppContextProvider>
</ApiProvider>
);
let errorPage = undefined;
if (config.error) {
const { BootErrorPage } = this.components;
errorPage = <BootErrorPage step="load-config" error={config.error} />;
}
const appApis = ApiRegistry.from([
[appThemeApiRef, AppThemeSelector.createWithStorage(this.themes)],
[configApiRef, new ConfigReader(config.value ?? {})],
]);
const apis = new ApiAggregator(this.apis, appApis);
return (
<ApiProvider apis={apis}>
<AppContextProvider app={this}>
<AppThemeProvider>{errorPage ?? children}</AppThemeProvider>
</AppContextProvider>
</ApiProvider>
);
};
return Provider;
}
+27
View File
@@ -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<BootErrorPageProps>;
};
/**
* TBD
*/
export type AppConfig = any;
/**
* A function that loads in the App config that will be accessible via the ConfigApi.
*/
export type AppConfigLoader = () => Promise<AppConfig>;
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 = {
+25 -2
View File
@@ -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 = () => (
<ErrorPage status="404" statusMessage="PAGE NOT FOUND" />
);
const DefaultBootErrorPage: FC<BootErrorPageProps> = ({ 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 (
<Router>
<ErrorPage status="501" statusMessage={message} />
</Router>
);
};
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();