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:
@@ -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.
|
||||
@@ -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,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}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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[];
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user