Merge pull request #15110 from backstage/rugvip/router
core-app-api: add app.createRoot() to replace app.getProvider()
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
---
|
||||
'@backstage/core-app-api': minor
|
||||
---
|
||||
|
||||
Added a new `AppRouter` component and `app.createRoot()` method that replaces `app.getRouter()` and `app.getProvider()`, which are now deprecated. The new `AppRouter` component is a drop-in replacement for the old router component, while the new `app.createRoot()` method is used instead of the old provider component.
|
||||
|
||||
An old app setup might look like this:
|
||||
|
||||
```tsx
|
||||
const app = createApp(/* ... */);
|
||||
|
||||
const AppProvider = app.getProvider();
|
||||
const AppRouter = app.getRouter();
|
||||
|
||||
const routes = ...;
|
||||
|
||||
const App = () => (
|
||||
<AppProvider>
|
||||
<AlertDisplay />
|
||||
<OAuthRequestDialog />
|
||||
<AppRouter>
|
||||
<Root>{routes}</Root>
|
||||
</AppRouter>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
With these new APIs, the setup now looks like this:
|
||||
|
||||
```tsx
|
||||
import { AppRouter } from '@backstage/core-app-api';
|
||||
|
||||
const app = createApp(/* ... */);
|
||||
|
||||
const routes = ...;
|
||||
|
||||
export default app.createRoot(
|
||||
<>
|
||||
<AlertDisplay />
|
||||
<OAuthRequestDialog />
|
||||
<AppRouter>
|
||||
<Root>{routes}</Root>
|
||||
</AppRouter>
|
||||
</>,
|
||||
);
|
||||
```
|
||||
|
||||
Note that `app.createRoot()` accepts a React element, rather than a component.
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Updated the app template to use the new `AppRouter` component instead of `app.getRouter()`, as well as `app.createRoot()` instead of `app.getProvider()`.
|
||||
|
||||
To apply this change to an existing app, make the following change to `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
-import { FlatRoutes } from '@backstage/core-app-api';
|
||||
+import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
|
||||
|
||||
...
|
||||
|
||||
-const AppProvider = app.getProvider();
|
||||
-const AppRouter = app.getRouter();
|
||||
|
||||
...
|
||||
|
||||
-const App = () => (
|
||||
+export default app.createRoot(
|
||||
- <AppProvider>
|
||||
+ <>
|
||||
<AlertDisplay />
|
||||
<OAuthRequestDialog />
|
||||
<AppRouter>
|
||||
<Root>{routes}</Root>
|
||||
</AppRouter>
|
||||
- </AppProvider>
|
||||
+ </>,
|
||||
);
|
||||
```
|
||||
|
||||
The final export step should end up looking something like this:
|
||||
|
||||
```tsx
|
||||
export default app.createRoot(
|
||||
<>
|
||||
<AlertDisplay />
|
||||
<OAuthRequestDialog />
|
||||
<AppRouter>
|
||||
<Root>{routes}</Root>
|
||||
</AppRouter>
|
||||
</>,
|
||||
);
|
||||
```
|
||||
|
||||
Note that `app.createRoot()` accepts a React element, rather than a component.
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
RELATION_PROVIDES_API,
|
||||
} from '@backstage/catalog-model';
|
||||
import { createApp } from '@backstage/app-defaults';
|
||||
import { FlatRoutes } from '@backstage/core-app-api';
|
||||
import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
|
||||
import {
|
||||
AlertDisplay,
|
||||
OAuthRequestDialog,
|
||||
@@ -145,9 +145,6 @@ const app = createApp({
|
||||
},
|
||||
});
|
||||
|
||||
const AppProvider = app.getProvider();
|
||||
const AppRouter = app.getRouter();
|
||||
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
<Route path="/" element={<Navigate to="catalog" />} />
|
||||
@@ -278,14 +275,12 @@ const routes = (
|
||||
</FlatRoutes>
|
||||
);
|
||||
|
||||
const App = () => (
|
||||
<AppProvider>
|
||||
export default app.createRoot(
|
||||
<>
|
||||
<AlertDisplay transientTimeoutMs={2500} />
|
||||
<OAuthRequestDialog />
|
||||
<AppRouter>
|
||||
<Root>{routes}</Root>
|
||||
</AppRouter>
|
||||
</AppProvider>
|
||||
</>,
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -227,6 +227,15 @@ export type AppRouteBinder = <
|
||||
>,
|
||||
) => void;
|
||||
|
||||
// @public
|
||||
export function AppRouter(props: AppRouterProps): JSX.Element;
|
||||
|
||||
// @public
|
||||
export interface AppRouterProps {
|
||||
// (undocumented)
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class AppThemeSelector implements AppThemeApi {
|
||||
constructor(themes: AppTheme[]);
|
||||
@@ -259,6 +268,7 @@ export type AuthApiCreateOptions = {
|
||||
export type BackstageApp = {
|
||||
getPlugins(): BackstagePlugin[];
|
||||
getSystemIcon(key: string): IconComponent | undefined;
|
||||
createRoot(element: JSX.Element): ComponentType<{}>;
|
||||
getProvider(): ComponentType<{}>;
|
||||
getRouter(): ComponentType<{}>;
|
||||
};
|
||||
|
||||
@@ -17,15 +17,10 @@
|
||||
import { AppConfig, Config } from '@backstage/config';
|
||||
import React, {
|
||||
ComponentType,
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
ReactElement,
|
||||
useContext,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import {
|
||||
ApiProvider,
|
||||
@@ -34,7 +29,6 @@ import {
|
||||
LocalStorageFeatureFlags,
|
||||
} from '../apis';
|
||||
import {
|
||||
useApi,
|
||||
AnyApiFactory,
|
||||
ApiHolder,
|
||||
IconComponent,
|
||||
@@ -44,7 +38,6 @@ import {
|
||||
AppThemeApi,
|
||||
ConfigApi,
|
||||
featureFlagsApiRef,
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
BackstagePlugin,
|
||||
} from '@backstage/core-plugin-api';
|
||||
@@ -61,7 +54,6 @@ import {
|
||||
routingV2Collector,
|
||||
} from '../routing/collectors';
|
||||
import { RoutingProvider } from '../routing/RoutingProvider';
|
||||
import { RouteTracker } from '../routing/RouteTracker';
|
||||
import {
|
||||
validateRouteParameters,
|
||||
validateRouteBindings,
|
||||
@@ -74,14 +66,14 @@ import {
|
||||
AppContext,
|
||||
AppOptions,
|
||||
BackstageApp,
|
||||
SignInPageProps,
|
||||
} from './types';
|
||||
import { AppThemeProvider } from './AppThemeProvider';
|
||||
import { defaultConfigLoader } from './defaultConfigLoader';
|
||||
import { ApiRegistry } from '../apis/system/ApiRegistry';
|
||||
import { resolveRouteBindings } from './resolveRouteBindings';
|
||||
import { BackstageRouteObject } from '../routing/types';
|
||||
import { isReactRouterBeta } from './isReactRouterBeta';
|
||||
import { InternalAppContext } from './InternalAppContext';
|
||||
import { AppRouter, getBasePath } from './AppRouter';
|
||||
|
||||
type CompatiblePlugin =
|
||||
| BackstagePlugin
|
||||
@@ -89,39 +81,6 @@ type CompatiblePlugin =
|
||||
output(): Array<{ type: 'feature-flag'; name: string }>;
|
||||
});
|
||||
|
||||
const InternalAppContext = createContext<{
|
||||
routeObjects: BackstageRouteObject[];
|
||||
}>({ routeObjects: [] });
|
||||
|
||||
/**
|
||||
* Get the app base path from the configured app baseUrl.
|
||||
*
|
||||
* The returned path does not have a trailing slash.
|
||||
*/
|
||||
function getBasePath(configApi: Config) {
|
||||
if (!isReactRouterBeta()) {
|
||||
// When using rr v6 stable the base path is handled through the
|
||||
// basename prop on the router component instead.
|
||||
return '';
|
||||
}
|
||||
|
||||
return readBasePath(configApi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the configured base path.
|
||||
*
|
||||
* The returned path does not have a trailing slash.
|
||||
*/
|
||||
function readBasePath(configApi: ConfigApi) {
|
||||
let { pathname } = new URL(
|
||||
configApi.getOptionalString('app.baseUrl') ?? '/',
|
||||
'http://sample.dev', // baseUrl can be specified as just a path
|
||||
);
|
||||
pathname = pathname.replace(/\/*$/, '');
|
||||
return pathname;
|
||||
}
|
||||
|
||||
function useConfigLoader(
|
||||
configLoader: AppConfigLoader | undefined,
|
||||
components: AppComponents,
|
||||
@@ -289,7 +248,23 @@ export class AppManager implements BackstageApp {
|
||||
return this.components;
|
||||
}
|
||||
|
||||
createRoot(element: JSX.Element): ComponentType<{}> {
|
||||
const AppProvider = this.getProvider();
|
||||
const AppRoot = () => {
|
||||
return <AppProvider>{element}</AppProvider>;
|
||||
};
|
||||
return AppRoot;
|
||||
}
|
||||
|
||||
#getProviderCalled = false;
|
||||
getProvider(): ComponentType<{}> {
|
||||
if (this.#getProviderCalled) {
|
||||
throw new Error(
|
||||
'app.getProvider() or app.createRoot() has already been called, and can only be called once',
|
||||
);
|
||||
}
|
||||
this.#getProviderCalled = true;
|
||||
|
||||
const appContext = new AppContextImpl(this);
|
||||
|
||||
// We only validate routes once
|
||||
@@ -413,7 +388,10 @@ export class AppManager implements BackstageApp {
|
||||
basePath={getBasePath(loadedConfig.api)}
|
||||
>
|
||||
<InternalAppContext.Provider
|
||||
value={{ routeObjects: routing.objects }}
|
||||
value={{
|
||||
routeObjects: routing.objects,
|
||||
appIdentityProxy: this.appIdentityProxy,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</InternalAppContext.Provider>
|
||||
@@ -427,104 +405,6 @@ export class AppManager implements BackstageApp {
|
||||
}
|
||||
|
||||
getRouter(): ComponentType<{}> {
|
||||
const { Router: RouterComponent, SignInPage: SignInPageComponent } =
|
||||
this.components;
|
||||
|
||||
// This wraps the sign-in page and waits for sign-in to be completed before rendering the app
|
||||
const SignInPageWrapper = ({
|
||||
component: Component,
|
||||
children,
|
||||
}: {
|
||||
component: ComponentType<SignInPageProps>;
|
||||
children: ReactElement;
|
||||
}) => {
|
||||
const [identityApi, setIdentityApi] = useState<IdentityApi>();
|
||||
const configApi = useApi(configApiRef);
|
||||
const basePath = getBasePath(configApi);
|
||||
|
||||
if (!identityApi) {
|
||||
return <Component onSignInSuccess={setIdentityApi} />;
|
||||
}
|
||||
|
||||
this.appIdentityProxy.setTarget(identityApi, {
|
||||
signOutTargetUrl: basePath || '/',
|
||||
});
|
||||
return children;
|
||||
};
|
||||
|
||||
const AppRouter = ({ children }: PropsWithChildren<{}>) => {
|
||||
const configApi = useApi(configApiRef);
|
||||
const basePath = readBasePath(configApi);
|
||||
const mountPath = `${basePath}/*`;
|
||||
const { routeObjects } = useContext(InternalAppContext);
|
||||
|
||||
// If the app hasn't configured a sign-in page, we just continue as guest.
|
||||
if (!SignInPageComponent) {
|
||||
this.appIdentityProxy.setTarget(
|
||||
{
|
||||
getUserId: () => 'guest',
|
||||
getIdToken: async () => undefined,
|
||||
getProfile: () => ({
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
}),
|
||||
getProfileInfo: async () => ({
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
}),
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: ['user:default/guest'],
|
||||
}),
|
||||
getCredentials: async () => ({}),
|
||||
signOut: async () => {},
|
||||
},
|
||||
{ signOutTargetUrl: basePath || '/' },
|
||||
);
|
||||
|
||||
if (isReactRouterBeta()) {
|
||||
return (
|
||||
<RouterComponent>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
<Routes>
|
||||
<Route path={mountPath} element={<>{children}</>} />
|
||||
</Routes>
|
||||
</RouterComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RouterComponent basename={basePath}>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
{children}
|
||||
</RouterComponent>
|
||||
);
|
||||
}
|
||||
|
||||
if (isReactRouterBeta()) {
|
||||
return (
|
||||
<RouterComponent>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
<SignInPageWrapper component={SignInPageComponent}>
|
||||
<Routes>
|
||||
<Route path={mountPath} element={<>{children}</>} />
|
||||
</Routes>
|
||||
</SignInPageWrapper>
|
||||
</RouterComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RouterComponent basename={basePath}>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
<SignInPageWrapper component={SignInPageComponent}>
|
||||
<>{children}</>
|
||||
</SignInPageWrapper>
|
||||
</RouterComponent>
|
||||
);
|
||||
};
|
||||
|
||||
return AppRouter;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
AppComponents,
|
||||
configApiRef,
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
SignInPageProps,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { InternalAppContext } from './InternalAppContext';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { AppRouter } from './AppRouter';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { AppContextProvider } from './AppContext';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
function UserRefDisplay() {
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const { value } = useAsync(() => identityApi.getBackstageIdentity());
|
||||
return <div>ref: {value?.userEntityRef}</div>;
|
||||
}
|
||||
|
||||
describe('AppRouter', () => {
|
||||
const mockComponents = {
|
||||
Router: MemoryRouter,
|
||||
} as AppComponents;
|
||||
|
||||
it('should fall back to guest if there is no sign-in page', async () => {
|
||||
const appIdentityProxy = new AppIdentityProxy();
|
||||
|
||||
render(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, appIdentityProxy],
|
||||
[configApiRef, new ConfigReader({})],
|
||||
]}
|
||||
>
|
||||
<InternalAppContext.Provider
|
||||
value={{ routeObjects: [], appIdentityProxy }}
|
||||
>
|
||||
<AppContextProvider
|
||||
appContext={{ getComponents: () => mockComponents } as any}
|
||||
>
|
||||
<AppRouter>
|
||||
<UserRefDisplay />
|
||||
</AppRouter>
|
||||
</AppContextProvider>
|
||||
</InternalAppContext.Provider>
|
||||
,
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
await expect(
|
||||
screen.findByText('ref: user:default/guest'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should use the result from the sign-in page', async () => {
|
||||
const appIdentityProxy = new AppIdentityProxy();
|
||||
|
||||
const SignInPage = (props: SignInPageProps) => {
|
||||
props.onSignInSuccess({
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/test',
|
||||
ownershipEntityRefs: ['user:default/test'],
|
||||
}),
|
||||
} as IdentityApi);
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, appIdentityProxy],
|
||||
[configApiRef, new ConfigReader({})],
|
||||
]}
|
||||
>
|
||||
<InternalAppContext.Provider
|
||||
value={{ routeObjects: [], appIdentityProxy }}
|
||||
>
|
||||
<AppContextProvider
|
||||
appContext={
|
||||
{
|
||||
getComponents: () => ({ ...mockComponents, SignInPage }),
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<AppRouter>
|
||||
<UserRefDisplay />
|
||||
</AppRouter>
|
||||
</AppContextProvider>
|
||||
</InternalAppContext.Provider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
await expect(
|
||||
screen.findByText('ref: user:default/test'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useContext, ReactNode, ComponentType, useState } from 'react';
|
||||
import {
|
||||
ConfigApi,
|
||||
configApiRef,
|
||||
IdentityApi,
|
||||
SignInPageProps,
|
||||
useApi,
|
||||
useApp,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { InternalAppContext } from './InternalAppContext';
|
||||
import { isReactRouterBeta } from './isReactRouterBeta';
|
||||
import { RouteTracker } from '../routing/RouteTracker';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy';
|
||||
|
||||
/**
|
||||
* Get the app base path from the configured app baseUrl.
|
||||
*
|
||||
* The returned path does not have a trailing slash.
|
||||
*/
|
||||
export function getBasePath(configApi: ConfigApi) {
|
||||
if (!isReactRouterBeta()) {
|
||||
// When using rr v6 stable the base path is handled through the
|
||||
// basename prop on the router component instead.
|
||||
return '';
|
||||
}
|
||||
|
||||
return readBasePath(configApi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the configured base path.
|
||||
*
|
||||
* The returned path does not have a trailing slash.
|
||||
*/
|
||||
function readBasePath(configApi: ConfigApi) {
|
||||
let { pathname } = new URL(
|
||||
configApi.getOptionalString('app.baseUrl') ?? '/',
|
||||
'http://sample.dev', // baseUrl can be specified as just a path
|
||||
);
|
||||
pathname = pathname.replace(/\/*$/, '');
|
||||
return pathname;
|
||||
}
|
||||
|
||||
// This wraps the sign-in page and waits for sign-in to be completed before rendering the app
|
||||
function SignInPageWrapper({
|
||||
component: Component,
|
||||
appIdentityProxy,
|
||||
children,
|
||||
}: {
|
||||
component: ComponentType<SignInPageProps>;
|
||||
appIdentityProxy: AppIdentityProxy;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [identityApi, setIdentityApi] = useState<IdentityApi>();
|
||||
const configApi = useApi(configApiRef);
|
||||
const basePath = getBasePath(configApi);
|
||||
|
||||
if (!identityApi) {
|
||||
return <Component onSignInSuccess={setIdentityApi} />;
|
||||
}
|
||||
|
||||
appIdentityProxy.setTarget(identityApi, {
|
||||
signOutTargetUrl: basePath || '/',
|
||||
});
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the {@link AppRouter} component.
|
||||
* @public
|
||||
*/
|
||||
export interface AppRouterProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* App router and sign-in page wrapper.
|
||||
*
|
||||
* @public
|
||||
* @remarks
|
||||
*
|
||||
* The AppRouter provides the routing context and renders the sign-in page.
|
||||
* Until the user has successfully signed in, this component will render
|
||||
* the sign-in page. Once the user has signed-in, it will instead render
|
||||
* the app, while providing routing and route tracking for the app.
|
||||
*/
|
||||
export function AppRouter(props: AppRouterProps) {
|
||||
const { Router: RouterComponent, SignInPage: SignInPageComponent } =
|
||||
useApp().getComponents();
|
||||
|
||||
const configApi = useApi(configApiRef);
|
||||
const basePath = readBasePath(configApi);
|
||||
const mountPath = `${basePath}/*`;
|
||||
const internalAppContext = useContext(InternalAppContext);
|
||||
if (!internalAppContext) {
|
||||
throw new Error('AppRouter must be rendered within the AppProvider');
|
||||
}
|
||||
const { routeObjects, appIdentityProxy } = internalAppContext;
|
||||
|
||||
// If the app hasn't configured a sign-in page, we just continue as guest.
|
||||
if (!SignInPageComponent) {
|
||||
appIdentityProxy.setTarget(
|
||||
{
|
||||
getUserId: () => 'guest',
|
||||
getIdToken: async () => undefined,
|
||||
getProfile: () => ({
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
}),
|
||||
getProfileInfo: async () => ({
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
}),
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: ['user:default/guest'],
|
||||
}),
|
||||
getCredentials: async () => ({}),
|
||||
signOut: async () => {},
|
||||
},
|
||||
{ signOutTargetUrl: basePath || '/' },
|
||||
);
|
||||
|
||||
if (isReactRouterBeta()) {
|
||||
return (
|
||||
<RouterComponent>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
<Routes>
|
||||
<Route path={mountPath} element={<>{props.children}</>} />
|
||||
</Routes>
|
||||
</RouterComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RouterComponent basename={basePath}>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
{props.children}
|
||||
</RouterComponent>
|
||||
);
|
||||
}
|
||||
|
||||
if (isReactRouterBeta()) {
|
||||
return (
|
||||
<RouterComponent>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
<SignInPageWrapper
|
||||
component={SignInPageComponent}
|
||||
appIdentityProxy={appIdentityProxy}
|
||||
>
|
||||
<Routes>
|
||||
<Route path={mountPath} element={<>{props.children}</>} />
|
||||
</Routes>
|
||||
</SignInPageWrapper>
|
||||
</RouterComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RouterComponent basename={basePath}>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
<SignInPageWrapper
|
||||
component={SignInPageComponent}
|
||||
appIdentityProxy={appIdentityProxy}
|
||||
>
|
||||
{props.children}
|
||||
</SignInPageWrapper>
|
||||
</RouterComponent>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createContext } from 'react';
|
||||
import { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy';
|
||||
import { BackstageRouteObject } from '../routing/types';
|
||||
|
||||
export const InternalAppContext = createContext<
|
||||
| undefined
|
||||
| {
|
||||
routeObjects: BackstageRouteObject[];
|
||||
appIdentityProxy: AppIdentityProxy;
|
||||
}
|
||||
>(undefined);
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { AppRouter } from './AppRouter';
|
||||
export type { AppRouterProps } from './AppRouter';
|
||||
export { createSpecializedApp } from './createSpecializedApp';
|
||||
export { defaultConfigLoader } from './defaultConfigLoader';
|
||||
export * from './types';
|
||||
|
||||
@@ -298,15 +298,44 @@ export type BackstageApp = {
|
||||
*/
|
||||
getSystemIcon(key: string): IconComponent | undefined;
|
||||
|
||||
/**
|
||||
* Creates the root component that renders the entire app.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This method must only be called once, and you have to provide it the entire
|
||||
* app element tree. The element tree will be analyzed to discover plugins,
|
||||
* routes, and other app features. The returned component will render all
|
||||
* of the app elements wrapped within the app context provider.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* export default app.createRoot(
|
||||
* <>
|
||||
* <AlertDisplay />
|
||||
* <OAuthRequestDialog />
|
||||
* <AppRouter>
|
||||
* <Root>{routes}</Root>
|
||||
* </AppRouter>
|
||||
* </>,
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
createRoot(element: JSX.Element): ComponentType<{}>;
|
||||
|
||||
/**
|
||||
* Provider component that should wrap the Router created with getRouter()
|
||||
* and any other components that need to be within the app context.
|
||||
*
|
||||
* @deprecated Use {@link BackstageApp.createRoot} instead.
|
||||
*/
|
||||
getProvider(): ComponentType<{}>;
|
||||
|
||||
/**
|
||||
* Router component that should wrap the App Routes create with getRoutes()
|
||||
* and any other components that should only be available while signed in.
|
||||
*
|
||||
* @deprecated Import and use the {@link AppRouter} component from `@backstage/core-app-api` instead
|
||||
*/
|
||||
getRouter(): ComponentType<{}>;
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ import { Root } from './components/Root';
|
||||
|
||||
import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components';
|
||||
import { createApp } from '@backstage/app-defaults';
|
||||
import { FlatRoutes } from '@backstage/core-app-api';
|
||||
import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
|
||||
import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
|
||||
import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
|
||||
@@ -53,9 +53,6 @@ const app = createApp({
|
||||
},
|
||||
});
|
||||
|
||||
const AppProvider = app.getProvider();
|
||||
const AppRouter = app.getRouter();
|
||||
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
<Route path="/" element={<Navigate to="catalog" />} />
|
||||
@@ -97,14 +94,12 @@ const routes = (
|
||||
</FlatRoutes>
|
||||
);
|
||||
|
||||
const App = () => (
|
||||
<AppProvider>
|
||||
export default app.createRoot(
|
||||
<>
|
||||
<AlertDisplay />
|
||||
<OAuthRequestDialog />
|
||||
<AppRouter>
|
||||
<Root>{routes}</Root>
|
||||
</AppRouter>
|
||||
</AppProvider>
|
||||
</>,
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
Reference in New Issue
Block a user