Merge pull request #4625 from backstage/rugvip/appconstigt

core-api: migrate app context to separate type and deprecate most methods + not found page for FlatRoutes
This commit is contained in:
Patrik Oldsberg
2021-02-22 17:24:12 +01:00
committed by GitHub
6 changed files with 130 additions and 21 deletions
+6
View File
@@ -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.
+6
View File
@@ -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.
+64 -11
View File
@@ -64,6 +64,7 @@ import { AppThemeProvider } from './AppThemeProvider';
import {
AppComponents,
AppConfigLoader,
AppContext,
AppOptions,
AppRouteBinder,
BackstageApp,
@@ -139,6 +140,42 @@ function useConfigLoader(
return { api: configReader };
}
class AppContextImpl implements AppContext {
constructor(private readonly app: PrivateAppImpl) {}
getPlugins(): BackstagePlugin<any, any>[] {
// 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);
}
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');
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;
@@ -173,11 +210,13 @@ export class PrivateAppImpl implements BackstageApp {
return this.icons[key];
}
getComponents(): AppComponents {
return this.components;
}
getRoutes(): JSX.Element[] {
const routes = new Array<JSX.Element>();
const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!;
const { NotFoundErrorPage } = this.components;
for (const plugin of this.plugins.values()) {
@@ -211,13 +250,6 @@ export class PrivateAppImpl implements BackstageApp {
routes.push(<Navigate key={from.path} to={to.path} />);
break;
}
case 'feature-flag': {
featureFlagsApi.registerFlag({
name: output.name,
pluginId: plugin.getId(),
});
break;
}
default:
break;
}
@@ -236,6 +268,27 @@ 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(
() => AppThemeSelector.createWithStorage(this.themes),
@@ -272,8 +325,8 @@ export class PrivateAppImpl implements BackstageApp {
this.configApi = loadedConfig.api;
return (
<ApiProvider apis={this.getApiHolder()}>
<AppContextProvider app={this}>
<ApiProvider apis={apiHolder}>
<AppContextProvider appContext={appContext}>
<AppThemeProvider>
<RoutingProvider
routePaths={routePaths}
+9 -9
View File
@@ -15,25 +15,25 @@
*/
import React, { createContext, PropsWithChildren, useContext } from 'react';
import { BackstageApp } from './types';
import { AppContext } from './types';
const Context = createContext<BackstageApp | undefined>(undefined);
const Context = createContext<AppContext | undefined>(undefined);
type Props = {
app: BackstageApp;
appContext: AppContext;
};
export const AppContextProvider = ({
app,
appContext,
children,
}: PropsWithChildren<Props>) => (
<Context.Provider value={app} children={children} />
<Context.Provider value={appContext} children={children} />
);
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;
};
+34
View File
@@ -187,6 +187,40 @@ 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[];
};
export type AppContext = {
/**
* @deprecated Will be removed
*/
getPlugins(): BackstagePlugin<any, any>[];
/**
* Get a common or custom icon for this app.
*/
getSystemIcon(key: IconKey): IconComponent;
/**
* Get the components registered for various purposes in the app.
*/
getComponents(): AppComponents;
/**
* @deprecated Will be removed
*/
getProvider(): ComponentType<{}>;
/**
* @deprecated Will be removed
*/
getRouter(): ComponentType<{}>;
/**
* @deprecated Will be removed
*/
getRoutes(): JSX.Element[];
};
+11 -1
View File
@@ -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: <NotFoundErrorPage />,
path: '/*',
});
return useRoutes(routes);
};