Merge pull request #26215 from backstage/mob/app-it-up
NFS: Introduce the `@backstage/plugin-app` to house built in extensions
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
'@backstage/frontend-test-utils': patch
|
||||
'@backstage/frontend-app-api': patch
|
||||
'@backstage/plugin-app': minor
|
||||
---
|
||||
|
||||
Introduce the `@backstage/plugin-app` package to hold all of the built-in extensions for easy consumption and overriding.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-app-backend': patch
|
||||
'@backstage/plugin-app-node': patch
|
||||
---
|
||||
|
||||
Fixing dependency metadata with the new `@backstage/plugin-app` package
|
||||
@@ -495,7 +495,7 @@ The entity pages are typically defined in `packages/app/src/components/catalog`
|
||||
|
||||
New apps feature a built-in sidebar extension (`app/nav`) that will render all nav item extensions provided by plugins. This is a placeholder implementation and not intended as a long-term solution. In the future we will aim to provide a more flexible sidebar extension that allows for more customization out of the box.
|
||||
|
||||
Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an [extension override](../architecture/25-extension-overrides.md):
|
||||
Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/plugins/app/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an [extension override](../architecture/25-extension-overrides.md):
|
||||
|
||||
```tsx
|
||||
const nav = createExtension({
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/integration-react": "workspace:^",
|
||||
"@backstage/plugin-api-docs": "workspace:^",
|
||||
"@backstage/plugin-app": "workspace:^",
|
||||
"@backstage/plugin-app-visualizer": "workspace:^",
|
||||
"@backstage/plugin-auth-react": "workspace:^",
|
||||
"@backstage/plugin-catalog": "workspace:^",
|
||||
|
||||
@@ -262,7 +262,15 @@ describe('collectLegacyRoutes', () => {
|
||||
component: () =>
|
||||
Promise.resolve(() => {
|
||||
const app = useApp();
|
||||
return <div>plugins: {app.getPlugins().map(p => p.getId())}</div>;
|
||||
return (
|
||||
<div>
|
||||
plugins:{' '}
|
||||
{app
|
||||
.getPlugins()
|
||||
.map(p => p.getId())
|
||||
.join(', ')}
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
}),
|
||||
);
|
||||
@@ -276,7 +284,7 @@ describe('collectLegacyRoutes', () => {
|
||||
render(createSpecializedApp({ features }).createRoot());
|
||||
|
||||
await expect(
|
||||
screen.findByText('plugins: test'),
|
||||
screen.findByText('plugins: app, test'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('BackwardsCompatProvider', () => {
|
||||
const app = useApp();
|
||||
return (
|
||||
<div data-testid="ctx">
|
||||
plugins:
|
||||
plugins:{' '}
|
||||
{app
|
||||
.getPlugins()
|
||||
.map(p => p.getId())
|
||||
@@ -74,7 +74,7 @@ describe('BackwardsCompatProvider', () => {
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(`
|
||||
"plugins:
|
||||
"plugins: app
|
||||
components: NotFoundErrorPage, BootErrorPage, Progress, Router, ErrorBoundaryFallback
|
||||
icons: brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, user, warning"
|
||||
`);
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-app": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@backstage/version-bridge": "workspace:^",
|
||||
|
||||
@@ -32,6 +32,6 @@ export const Root = createExtension({
|
||||
replaces: [{ id: 'app', input: 'apis' }],
|
||||
}),
|
||||
},
|
||||
output: [],
|
||||
factory: () => [],
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: ({ inputs }) => inputs.app,
|
||||
});
|
||||
|
||||
@@ -29,12 +29,15 @@ import {
|
||||
createRouteRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { MockConfigApi, TestApiRegistry } from '@backstage/test-utils';
|
||||
import appPlugin from '@backstage/plugin-app';
|
||||
|
||||
import { builtinExtensions } from '../wiring/createApp';
|
||||
import { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig';
|
||||
import { resolveAppNodeSpecs } from '../tree/resolveAppNodeSpecs';
|
||||
import { resolveAppTree } from '../tree/resolveAppTree';
|
||||
import { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree';
|
||||
import { Root } from '../extensions/Root';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';
|
||||
|
||||
const ref1 = createRouteRef();
|
||||
const ref2 = createRouteRef();
|
||||
@@ -83,12 +86,12 @@ function routeInfoFromExtensions(extensions: ExtensionDefinition[]) {
|
||||
});
|
||||
|
||||
const tree = resolveAppTree(
|
||||
'app',
|
||||
'root',
|
||||
resolveAppNodeSpecs({
|
||||
features: [plugin],
|
||||
builtinExtensions,
|
||||
features: [appPlugin, plugin],
|
||||
builtinExtensions: [resolveExtensionDefinition(Root)],
|
||||
parameters: readAppExtensionsConfig(new MockConfigApi({})),
|
||||
forbidden: new Set(['app']),
|
||||
forbidden: new Set(['root']),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { CreateAppFeatureLoader, createApp } from './createApp';
|
||||
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import appPlugin from '@backstage/plugin-app';
|
||||
|
||||
describe('createApp', () => {
|
||||
it('should allow themes to be installed', async () => {
|
||||
@@ -136,7 +137,7 @@ describe('createApp', () => {
|
||||
configLoader: async () => ({
|
||||
config: new MockConfigApi({ key: 'config-value' }),
|
||||
}),
|
||||
features: [loader],
|
||||
features: [appPlugin, loader],
|
||||
});
|
||||
|
||||
await renderWithEffects(app.createRoot());
|
||||
@@ -174,6 +175,7 @@ describe('createApp', () => {
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: new MockConfigApi({}) }),
|
||||
features: [
|
||||
appPlugin,
|
||||
createFrontendPlugin({
|
||||
id: 'test',
|
||||
featureFlags: [{ name: 'test-1' }],
|
||||
@@ -255,13 +257,53 @@ describe('createApp', () => {
|
||||
const { tree } = appTreeApi!.getTree();
|
||||
|
||||
expect(String(tree.root)).toMatchInlineSnapshot(`
|
||||
"<root>
|
||||
"<root out=[core.reactElement]>
|
||||
apis [
|
||||
<api:app/discovery out=[core.api.factory] />
|
||||
<api:app/alert out=[core.api.factory] />
|
||||
<api:app/analytics out=[core.api.factory] />
|
||||
<api:app/error out=[core.api.factory] />
|
||||
<api:app/storage out=[core.api.factory] />
|
||||
<api:app/fetch out=[core.api.factory] />
|
||||
<api:app/oauth-request out=[core.api.factory] />
|
||||
<api:app/google-auth out=[core.api.factory] />
|
||||
<api:app/microsoft-auth out=[core.api.factory] />
|
||||
<api:app/github-auth out=[core.api.factory] />
|
||||
<api:app/okta-auth out=[core.api.factory] />
|
||||
<api:app/gitlab-auth out=[core.api.factory] />
|
||||
<api:app/onelogin-auth out=[core.api.factory] />
|
||||
<api:app/bitbucket-auth out=[core.api.factory] />
|
||||
<api:app/bitbucket-server-auth out=[core.api.factory] />
|
||||
<api:app/atlassian-auth out=[core.api.factory] />
|
||||
<api:app/vmware-cloud-auth out=[core.api.factory] />
|
||||
<api:app/permission out=[core.api.factory] />
|
||||
<api:app/app-language out=[core.api.factory] />
|
||||
<api:app/app-theme out=[core.api.factory]>
|
||||
themes [
|
||||
<theme:app/dark out=[core.theme.theme] />
|
||||
<theme:app/light out=[core.theme.theme] />
|
||||
]
|
||||
</api:app/app-theme>
|
||||
<api:app/components out=[core.api.factory]>
|
||||
components [
|
||||
<component:core.components.progress out=[core.component.component] />
|
||||
<component:core.components.notFoundErrorPage out=[core.component.component] />
|
||||
<component:core.components.errorBoundaryFallback out=[core.component.component] />
|
||||
]
|
||||
</api:app/components>
|
||||
<api:app/icons out=[core.api.factory] />
|
||||
<api:app/feature-flags out=[core.api.factory] />
|
||||
<api:app/translations out=[core.api.factory] />
|
||||
]
|
||||
app [
|
||||
<app out=[core.reactElement]>
|
||||
root [
|
||||
<app/root out=[core.reactElement]>
|
||||
children [
|
||||
<app/layout out=[core.reactElement]>
|
||||
nav [
|
||||
<app/nav out=[core.reactElement] />
|
||||
]
|
||||
content [
|
||||
<app/routes out=[core.reactElement]>
|
||||
routes [
|
||||
@@ -269,9 +311,6 @@ describe('createApp', () => {
|
||||
]
|
||||
</app/routes>
|
||||
]
|
||||
nav [
|
||||
<app/nav out=[core.reactElement] />
|
||||
]
|
||||
</app/layout>
|
||||
]
|
||||
elements [
|
||||
@@ -282,43 +321,6 @@ describe('createApp', () => {
|
||||
]
|
||||
</app>
|
||||
]
|
||||
apis [
|
||||
<api:app-theme out=[core.api.factory]>
|
||||
themes [
|
||||
<theme:app/light out=[core.theme.theme] />
|
||||
<theme:app/dark out=[core.theme.theme] />
|
||||
]
|
||||
</api:app-theme>
|
||||
<api:app-language out=[core.api.factory] />
|
||||
<api:icons out=[core.api.factory] />
|
||||
<api:translations out=[core.api.factory] />
|
||||
<api:components out=[core.api.factory]>
|
||||
components [
|
||||
<component:core.components.progress out=[core.component.component] />
|
||||
<component:core.components.errorBoundaryFallback out=[core.component.component] />
|
||||
<component:core.components.notFoundErrorPage out=[core.component.component] />
|
||||
]
|
||||
</api:components>
|
||||
<api:feature-flags out=[core.api.factory] />
|
||||
<api:core.discovery out=[core.api.factory] />
|
||||
<api:core.alert out=[core.api.factory] />
|
||||
<api:core.analytics out=[core.api.factory] />
|
||||
<api:core.error out=[core.api.factory] />
|
||||
<api:core.storage out=[core.api.factory] />
|
||||
<api:core.fetch out=[core.api.factory] />
|
||||
<api:core.oauthrequest out=[core.api.factory] />
|
||||
<api:core.auth.google out=[core.api.factory] />
|
||||
<api:core.auth.microsoft out=[core.api.factory] />
|
||||
<api:core.auth.github out=[core.api.factory] />
|
||||
<api:core.auth.okta out=[core.api.factory] />
|
||||
<api:core.auth.gitlab out=[core.api.factory] />
|
||||
<api:core.auth.onelogin out=[core.api.factory] />
|
||||
<api:core.auth.bitbucket out=[core.api.factory] />
|
||||
<api:core.auth.bitbucket-server out=[core.api.factory] />
|
||||
<api:core.auth.atlassian out=[core.api.factory] />
|
||||
<api:core.auth.vmware-cloud out=[core.api.factory] />
|
||||
<api:plugin.permission.api out=[core.api.factory] />
|
||||
]
|
||||
</root>"
|
||||
`);
|
||||
});
|
||||
@@ -354,4 +356,27 @@ describe('createApp', () => {
|
||||
|
||||
expect(screen.queryByText('Custom loading message')).toBeNull();
|
||||
});
|
||||
|
||||
it('should allow overriding the app plugin', async () => {
|
||||
const app = createApp({
|
||||
configLoader: () => new Promise(() => {}),
|
||||
features: [
|
||||
appPlugin.withOverrides({
|
||||
extensions: [
|
||||
appPlugin.getExtension('app/root').override({
|
||||
factory: () => [
|
||||
coreExtensionData.reactElement(
|
||||
<div>Custom app root element</div>,
|
||||
),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
await renderWithEffects(app.createRoot());
|
||||
|
||||
expect(screen.queryByText('Custom app root element')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
AppTreeApi,
|
||||
appTreeApiRef,
|
||||
coreExtensionData,
|
||||
ExtensionDefinition,
|
||||
FrontendFeature,
|
||||
RouteRef,
|
||||
ExternalRouteRef,
|
||||
@@ -34,10 +33,7 @@ import {
|
||||
createApiFactory,
|
||||
routeResolutionApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { App } from '../extensions/App';
|
||||
import { AppRoutes } from '../extensions/AppRoutes';
|
||||
import { AppLayout } from '../extensions/AppLayout';
|
||||
import { AppNav } from '../extensions/AppNav';
|
||||
|
||||
import {
|
||||
AnyApiFactory,
|
||||
ApiHolder,
|
||||
@@ -45,62 +41,31 @@ import {
|
||||
configApiRef,
|
||||
featureFlagsApiRef,
|
||||
identityApiRef,
|
||||
errorApiRef,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { getAvailableFeatures } from './discovery';
|
||||
import {
|
||||
ApiFactoryRegistry,
|
||||
ApiProvider,
|
||||
ApiResolver,
|
||||
} from '@backstage/core-app-api';
|
||||
import { ApiFactoryRegistry, ApiResolver } from '@backstage/core-app-api';
|
||||
|
||||
// TODO: Get rid of all of these
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { isProtectedApp } from '../../../core-app-api/src/app/isProtectedApp';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppThemeProvider } from '../../../core-app-api/src/app/AppThemeProvider';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { defaultConfigLoaderSync } from '../../../core-app-api/src/app/defaultConfigLoader';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { overrideBaseUrlConfigs } from '../../../core-app-api/src/app/overrideBaseUrlConfigs';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { apis as defaultApis } from '../../../app-defaults/src/defaults';
|
||||
|
||||
import {
|
||||
oauthRequestDialogAppRootElement,
|
||||
alertDisplayAppRootElement,
|
||||
} from '../extensions/elements';
|
||||
import { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode';
|
||||
|
||||
import { CreateAppRouteBinder } from '../routing';
|
||||
import { RouteResolver } from '../routing/RouteResolver';
|
||||
import { resolveRouteBindings } from '../routing/resolveRouteBindings';
|
||||
import { collectRouteIds } from '../routing/collectRouteIds';
|
||||
import {
|
||||
DefaultProgressComponent,
|
||||
DefaultErrorBoundaryComponent,
|
||||
DefaultNotFoundErrorPageComponent,
|
||||
} from '../extensions/components';
|
||||
import { InternalAppContext } from './InternalAppContext';
|
||||
import { AppRoot } from '../extensions/AppRoot';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { toInternalBackstagePlugin } from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides';
|
||||
import { stringifyError } from '@backstage/errors';
|
||||
import { getBasePath } from '../routing/getBasePath';
|
||||
import { AppThemeApi, DarkTheme, LightTheme } from '../extensions/AppThemeApi';
|
||||
import { IconsApi } from '../extensions/IconsApi';
|
||||
import { TranslationsApi } from '../extensions/TranslationsApi';
|
||||
import { ComponentsApi } from '../extensions/ComponentsApi';
|
||||
import { AppLanguageApi } from '../extensions/AppLanguageApi';
|
||||
import { FeatureFlagsApi } from '../extensions/FeatureFlagsApi';
|
||||
import { Root } from '../extensions/Root';
|
||||
import { resolveAppTree } from '../tree/resolveAppTree';
|
||||
import { resolveAppNodeSpecs } from '../tree/resolveAppNodeSpecs';
|
||||
@@ -108,35 +73,10 @@ import { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig';
|
||||
import { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry';
|
||||
|
||||
const DefaultApis = defaultApis.map(factory =>
|
||||
ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }),
|
||||
);
|
||||
|
||||
export const builtinExtensions = (
|
||||
[
|
||||
Root,
|
||||
App,
|
||||
AppRoot,
|
||||
AppRoutes,
|
||||
AppNav,
|
||||
AppLayout,
|
||||
DefaultProgressComponent,
|
||||
DefaultErrorBoundaryComponent,
|
||||
DefaultNotFoundErrorPageComponent,
|
||||
LightTheme,
|
||||
DarkTheme,
|
||||
oauthRequestDialogAppRootElement,
|
||||
alertDisplayAppRootElement,
|
||||
AppThemeApi,
|
||||
AppLanguageApi,
|
||||
IconsApi,
|
||||
TranslationsApi,
|
||||
ComponentsApi,
|
||||
FeatureFlagsApi,
|
||||
...DefaultApis,
|
||||
] as ExtensionDefinition[]
|
||||
).map(def => resolveExtensionDefinition(def));
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';
|
||||
import { BackstageRouteObject } from '../routing/types';
|
||||
import appPlugin from '@backstage/plugin-app';
|
||||
|
||||
function deduplicateFeatures(
|
||||
allFeatures: FrontendFeature[],
|
||||
@@ -271,6 +211,7 @@ class AppTreeApiProxy implements AppTreeApi {
|
||||
// Helps delay callers from reaching out to the API before the app tree has been materialized
|
||||
class RouteResolutionApiProxy implements RouteResolutionApi {
|
||||
#delegate: RouteResolutionApi | undefined;
|
||||
#routeObjects: BackstageRouteObject[] | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly tree: AppTree,
|
||||
@@ -307,9 +248,14 @@ class RouteResolutionApiProxy implements RouteResolutionApi {
|
||||
this.routeBindings,
|
||||
this.basePath,
|
||||
);
|
||||
this.#routeObjects = routeInfo.routeObjects;
|
||||
|
||||
return routeInfo;
|
||||
}
|
||||
|
||||
getRouteObjects() {
|
||||
return this.#routeObjects;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,24 +270,23 @@ export function createSpecializedApp(options?: {
|
||||
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
|
||||
}): { createRoot(): JSX.Element } {
|
||||
const {
|
||||
features: duplicatedFeatures = [],
|
||||
features: featuresWithoutApp = [],
|
||||
config = new ConfigReader({}, 'empty-config'),
|
||||
} = options ?? {};
|
||||
|
||||
const features = deduplicateFeatures(duplicatedFeatures);
|
||||
const features = deduplicateFeatures([appPlugin, ...featuresWithoutApp]);
|
||||
|
||||
const tree = resolveAppTree(
|
||||
'root',
|
||||
resolveAppNodeSpecs({
|
||||
features,
|
||||
builtinExtensions,
|
||||
builtinExtensions: [resolveExtensionDefinition(Root)],
|
||||
parameters: readAppExtensionsConfig(config),
|
||||
forbidden: new Set(['root']),
|
||||
}),
|
||||
);
|
||||
|
||||
const factories = createApiFactories({ tree });
|
||||
|
||||
const appTreeApi = new AppTreeApiProxy(tree);
|
||||
const routeResolutionApi = new RouteResolutionApiProxy(
|
||||
tree,
|
||||
@@ -364,29 +309,12 @@ export function createSpecializedApp(options?: {
|
||||
],
|
||||
});
|
||||
|
||||
for (const appNode of tree.root.edges.attachments.get('app') ?? []) {
|
||||
instantiateAppNodeTree(appNode, apiHolder);
|
||||
}
|
||||
// Now instantiate the entire tree, which will skip anything that's already been instantiated
|
||||
instantiateAppNodeTree(tree.root, apiHolder);
|
||||
|
||||
const routeInfo = routeResolutionApi.initialize();
|
||||
routeResolutionApi.initialize();
|
||||
appTreeApi.initialize();
|
||||
|
||||
if (isProtectedApp()) {
|
||||
const discoveryApi = apiHolder.get(discoveryApiRef);
|
||||
const errorApi = apiHolder.get(errorApiRef);
|
||||
const fetchApi = apiHolder.get(fetchApiRef);
|
||||
if (!discoveryApi || !errorApi || !fetchApi) {
|
||||
throw new Error(
|
||||
'App is running in protected mode but missing required APIs',
|
||||
);
|
||||
}
|
||||
appIdentityProxy.enableCookieAuth({
|
||||
discoveryApi,
|
||||
errorApi,
|
||||
fetchApi,
|
||||
});
|
||||
}
|
||||
|
||||
const featureFlagApi = apiHolder.get(featureFlagsApiRef);
|
||||
if (featureFlagApi) {
|
||||
for (const feature of features) {
|
||||
@@ -406,21 +334,9 @@ export function createSpecializedApp(options?: {
|
||||
}
|
||||
}
|
||||
|
||||
const rootEl = tree.root.edges.attachments
|
||||
.get('app')![0]
|
||||
.instance!.getData(coreExtensionData.reactElement);
|
||||
const rootEl = tree.root.instance!.getData(coreExtensionData.reactElement);
|
||||
|
||||
const AppComponent = () => (
|
||||
<ApiProvider apis={apiHolder}>
|
||||
<AppThemeProvider>
|
||||
<InternalAppContext.Provider
|
||||
value={{ appIdentityProxy, routeObjects: routeInfo.routeObjects }}
|
||||
>
|
||||
{rootEl}
|
||||
</InternalAppContext.Provider>
|
||||
</AppThemeProvider>
|
||||
</ApiProvider>
|
||||
);
|
||||
const AppComponent = () => rootEl;
|
||||
|
||||
return {
|
||||
createRoot() {
|
||||
|
||||
@@ -16,15 +16,13 @@
|
||||
|
||||
import React from 'react';
|
||||
import { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
createFrontendPlugin,
|
||||
} from '../wiring';
|
||||
import { createSpecializedApp } from '@backstage/frontend-app-api';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { renderInTestApp } from '@backstage/frontend-test-utils';
|
||||
|
||||
describe('AppRootWrapperBlueprint', () => {
|
||||
it('should return an extension with sensible defaults', () => {
|
||||
@@ -61,27 +59,20 @@ describe('AppRootWrapperBlueprint', () => {
|
||||
|
||||
it('should render the simple component wrapper', async () => {
|
||||
const extension = AppRootWrapperBlueprint.make({
|
||||
name: 'test',
|
||||
params: {
|
||||
Component: () => <div>Hello</div>,
|
||||
},
|
||||
});
|
||||
|
||||
const app = createSpecializedApp({
|
||||
features: [
|
||||
createFrontendPlugin({
|
||||
id: 'test',
|
||||
extensions: [extension],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
render(app.createRoot());
|
||||
renderInTestApp(<div />, { extensions: [extension] });
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Hello')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should render the complex component wrapper', async () => {
|
||||
const extension = AppRootWrapperBlueprint.makeWithOverrides({
|
||||
name: 'test',
|
||||
config: {
|
||||
schema: {
|
||||
name: z => z.string(),
|
||||
@@ -104,24 +95,17 @@ describe('AppRootWrapperBlueprint', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const app = createSpecializedApp({
|
||||
features: [
|
||||
createFrontendPlugin({
|
||||
id: 'test',
|
||||
extensions: [
|
||||
extension,
|
||||
createExtension({
|
||||
name: 'test-child',
|
||||
attachTo: { id: 'app-root-wrapper:test', input: 'children' },
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: () => [
|
||||
coreExtensionData.reactElement(<div>Its Me</div>),
|
||||
],
|
||||
}),
|
||||
],
|
||||
renderInTestApp(<div />, {
|
||||
extensions: [
|
||||
extension,
|
||||
createExtension({
|
||||
name: 'test-child',
|
||||
attachTo: { id: 'app-root-wrapper:test', input: 'children' },
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: () => [coreExtensionData.reactElement(<div>Its Me</div>)],
|
||||
}),
|
||||
],
|
||||
config: new MockConfigApi({
|
||||
config: {
|
||||
app: {
|
||||
extensions: [
|
||||
{
|
||||
@@ -129,11 +113,9 @@ describe('AppRootWrapperBlueprint', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
render(app.createRoot());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('Robin-1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Its Me')).toBeInTheDocument();
|
||||
|
||||
@@ -25,7 +25,7 @@ const iconsDataRef = createExtensionDataRef<{
|
||||
export const IconBundleBlueprint = createExtensionBlueprint({
|
||||
kind: 'icon-bundle',
|
||||
namespace: 'app',
|
||||
attachTo: { id: 'api:icons', input: 'icons' },
|
||||
attachTo: { id: 'api:app/icons', input: 'icons' },
|
||||
output: [iconsDataRef],
|
||||
config: {
|
||||
schema: {
|
||||
|
||||
@@ -17,15 +17,12 @@ import React from 'react';
|
||||
import { RouterBlueprint } from './RouterBlueprint';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { createSpecializedApp } from '@backstage/frontend-app-api';
|
||||
import {
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
createExtensionOverrides,
|
||||
} from '../wiring';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { PageBlueprint } from './PageBlueprint';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
|
||||
describe('RouterBlueprint', () => {
|
||||
it('should return an extension when calling make with sensible defaults', () => {
|
||||
@@ -72,24 +69,14 @@ describe('RouterBlueprint', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const app = createSpecializedApp({
|
||||
features: [
|
||||
createExtensionOverrides({
|
||||
extensions: [
|
||||
extension,
|
||||
PageBlueprint.make({
|
||||
namespace: 'test',
|
||||
params: {
|
||||
defaultPath: '/',
|
||||
loader: async () => <div data-testid="test-contents" />,
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const tester = createExtensionTester(extension);
|
||||
const Component = tester.get(RouterBlueprint.dataRefs.component);
|
||||
|
||||
const { getByTestId } = render(app.createRoot());
|
||||
const { getByTestId } = render(
|
||||
<Component>
|
||||
<div data-testid="test-contents" />
|
||||
</Component>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('test-contents')).toBeInTheDocument();
|
||||
@@ -124,44 +111,28 @@ describe('RouterBlueprint', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const app = createSpecializedApp({
|
||||
features: [
|
||||
createExtensionOverrides({
|
||||
extensions: [
|
||||
extension,
|
||||
createExtension({
|
||||
namespace: 'test',
|
||||
attachTo: {
|
||||
id: 'app-router-component:test/test',
|
||||
input: 'children',
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
*factory() {
|
||||
yield coreExtensionData.reactElement(<div />);
|
||||
},
|
||||
}),
|
||||
PageBlueprint.make({
|
||||
namespace: 'test',
|
||||
params: {
|
||||
defaultPath: '/',
|
||||
loader: async () => <div data-testid="test-contents" />,
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
config: new MockConfigApi({
|
||||
app: {
|
||||
extensions: [
|
||||
{
|
||||
'app-router-component:test/test': { config: { name: 'Robin' } },
|
||||
},
|
||||
],
|
||||
const tester = createExtensionTester(extension, {
|
||||
config: { name: 'Robin' },
|
||||
}).add(
|
||||
createExtension({
|
||||
namespace: 'test',
|
||||
attachTo: {
|
||||
id: 'app-router-component:test/test',
|
||||
input: 'children',
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
*factory() {
|
||||
yield coreExtensionData.reactElement(<div />);
|
||||
},
|
||||
}),
|
||||
});
|
||||
);
|
||||
const Component = tester.get(RouterBlueprint.dataRefs.component);
|
||||
|
||||
const { getByTestId } = render(app.createRoot());
|
||||
const { getByTestId } = render(
|
||||
<Component>
|
||||
<div data-testid="test-contents" />
|
||||
</Component>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('test-contents')).toBeInTheDocument();
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('ThemeBlueprint', () => {
|
||||
"$$type": "@backstage/ExtensionDefinition",
|
||||
"T": undefined,
|
||||
"attachTo": {
|
||||
"id": "api:app-theme",
|
||||
"id": "api:app/app-theme",
|
||||
"input": "themes",
|
||||
},
|
||||
"configSchema": undefined,
|
||||
|
||||
@@ -29,7 +29,7 @@ const themeDataRef = createExtensionDataRef<AppTheme>().with({
|
||||
export const ThemeBlueprint = createExtensionBlueprint({
|
||||
kind: 'theme',
|
||||
namespace: 'app',
|
||||
attachTo: { id: 'api:app-theme', input: 'themes' },
|
||||
attachTo: { id: 'api:app/app-theme', input: 'themes' },
|
||||
output: [themeDataRef],
|
||||
dataRefs: {
|
||||
theme: themeDataRef,
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('TranslationBlueprint', () => {
|
||||
"$$type": "@backstage/ExtensionDefinition",
|
||||
"T": undefined,
|
||||
"attachTo": {
|
||||
"id": "api:translations",
|
||||
"id": "api:app/translations",
|
||||
"input": "translations",
|
||||
},
|
||||
"configSchema": undefined,
|
||||
|
||||
@@ -28,7 +28,7 @@ const translationDataRef = createExtensionDataRef<
|
||||
*/
|
||||
export const TranslationBlueprint = createExtensionBlueprint({
|
||||
kind: 'translation',
|
||||
attachTo: { id: 'api:translations', input: 'translations' },
|
||||
attachTo: { id: 'api:app/translations', input: 'translations' },
|
||||
output: [translationDataRef],
|
||||
dataRefs: {
|
||||
translation: translationDataRef,
|
||||
|
||||
@@ -35,7 +35,7 @@ export function createComponentExtension<TProps extends {}>(options: {
|
||||
kind: 'component',
|
||||
namespace: options.ref.id,
|
||||
name: options.name,
|
||||
attachTo: { id: 'api:components', input: 'components' },
|
||||
attachTo: { id: 'api:app/components', input: 'components' },
|
||||
disabled: options.disabled,
|
||||
output: [createComponentExtension.componentDataRef],
|
||||
factory() {
|
||||
|
||||
@@ -127,7 +127,7 @@ function createTestAppRoot({
|
||||
config: JsonObject;
|
||||
}) {
|
||||
return createApp({
|
||||
features,
|
||||
features: [...features],
|
||||
configLoader: async () => ({ config: new MockConfigApi(config) }),
|
||||
}).createRoot();
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/frontend-app-api": "workspace:^",
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-app": "workspace:^",
|
||||
"@backstage/test-utils": "workspace:^",
|
||||
"@backstage/types": "workspace:^"
|
||||
},
|
||||
|
||||
@@ -27,12 +27,12 @@ import {
|
||||
coreExtensionData,
|
||||
RouteRef,
|
||||
useRouteRef,
|
||||
createExtensionInput,
|
||||
IconComponent,
|
||||
RouterBlueprint,
|
||||
NavItemBlueprint,
|
||||
FrontendFeature,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import appPlugin from '@backstage/plugin-app';
|
||||
|
||||
/**
|
||||
* Options to customize the behavior of the test app.
|
||||
@@ -92,38 +92,36 @@ const NavItem = (props: {
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAppNavExtension = createExtension({
|
||||
namespace: 'app',
|
||||
name: 'nav',
|
||||
attachTo: { id: 'app/layout', input: 'nav' },
|
||||
inputs: {
|
||||
items: createExtensionInput([NavItemBlueprint.dataRefs.target]),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory({ inputs }) {
|
||||
return [
|
||||
coreExtensionData.reactElement(
|
||||
<nav>
|
||||
<ul>
|
||||
{inputs.items.map((item, index) => {
|
||||
const { icon, title, routeRef } = item.get(
|
||||
NavItemBlueprint.dataRefs.target,
|
||||
);
|
||||
const appPluginOverride = appPlugin.withOverrides({
|
||||
extensions: [
|
||||
appPlugin.getExtension('app/nav').override({
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory(_originalFactory, { inputs }) {
|
||||
return [
|
||||
coreExtensionData.reactElement(
|
||||
<nav>
|
||||
<ul>
|
||||
{inputs.items.map((item, index) => {
|
||||
const { icon, title, routeRef } = item.get(
|
||||
NavItemBlueprint.dataRefs.target,
|
||||
);
|
||||
|
||||
return (
|
||||
<NavItem
|
||||
key={index}
|
||||
icon={icon}
|
||||
title={title}
|
||||
routeRef={routeRef}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>,
|
||||
),
|
||||
];
|
||||
},
|
||||
return (
|
||||
<NavItem
|
||||
key={index}
|
||||
icon={icon}
|
||||
title={title}
|
||||
routeRef={routeRef}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>,
|
||||
),
|
||||
];
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -152,7 +150,6 @@ export function renderInTestApp(
|
||||
Component: ({ children }) => <MemoryRouter>{children}</MemoryRouter>,
|
||||
},
|
||||
}),
|
||||
TestAppNavExtension,
|
||||
];
|
||||
|
||||
if (options?.mountedRoutes) {
|
||||
@@ -183,6 +180,7 @@ export function renderInTestApp(
|
||||
}
|
||||
|
||||
const features: FrontendFeature[] = [
|
||||
appPluginOverride,
|
||||
createExtensionOverrides({
|
||||
extensions,
|
||||
}),
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"role": "backend-plugin",
|
||||
"pluginId": "app",
|
||||
"pluginPackages": [
|
||||
"@backstage/plugin-app",
|
||||
"@backstage/plugin-app-backend",
|
||||
"@backstage/plugin-app-node"
|
||||
]
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"role": "node-library",
|
||||
"pluginId": "app",
|
||||
"pluginPackages": [
|
||||
"@backstage/plugin-app",
|
||||
"@backstage/plugin-app-backend",
|
||||
"@backstage/plugin-app-node"
|
||||
]
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,9 @@
|
||||
# app
|
||||
|
||||
Welcome to the app plugin!
|
||||
|
||||
_This plugin was created through the Backstage CLI_
|
||||
|
||||
## Getting started
|
||||
|
||||
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/app](http://localhost:3000/app).
|
||||
@@ -0,0 +1,681 @@
|
||||
## API Report File for "@backstage/plugin-app"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { AnyApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
|
||||
import { AppTheme } from '@backstage/frontend-plugin-api';
|
||||
import { BackstagePlugin } from '@backstage/frontend-plugin-api';
|
||||
import { ComponentRef } from '@backstage/frontend-plugin-api';
|
||||
import { ComponentType } from 'react';
|
||||
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionInput } from '@backstage/frontend-plugin-api';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/frontend-plugin-api';
|
||||
import { SignInPageProps } from '@backstage/core-plugin-api';
|
||||
import { TranslationMessages } from '@backstage/frontend-plugin-api';
|
||||
import { TranslationResource } from '@backstage/frontend-plugin-api';
|
||||
|
||||
// @public (undocumented)
|
||||
const appPlugin: BackstagePlugin<
|
||||
{},
|
||||
{},
|
||||
{
|
||||
[x: `component:app/${string}`]: ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
{
|
||||
ref: ComponentRef;
|
||||
impl: ComponentType;
|
||||
},
|
||||
'core.component.component',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
[x: string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{
|
||||
optional: boolean;
|
||||
singleton: boolean;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'component';
|
||||
namespace: string;
|
||||
name: string;
|
||||
}>;
|
||||
app: ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
JSX_2.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
root: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>,
|
||||
{
|
||||
singleton: true;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: undefined;
|
||||
namespace: 'app';
|
||||
name: undefined;
|
||||
}>;
|
||||
'api:app/app-language': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'app-language';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'app/layout': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
JSX_2.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
nav: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>,
|
||||
{
|
||||
singleton: true;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
content: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>,
|
||||
{
|
||||
singleton: true;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: undefined;
|
||||
namespace: 'app';
|
||||
name: 'layout';
|
||||
}>;
|
||||
'app/nav': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
JSX_2.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
items: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
{
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
routeRef: RouteRef<undefined>;
|
||||
},
|
||||
'core.nav-item.target',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
logos: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
{
|
||||
logoIcon?: JSX.Element | undefined;
|
||||
logoFull?: JSX.Element | undefined;
|
||||
},
|
||||
'core.nav-logo.logo-elements',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: true;
|
||||
optional: true;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: undefined;
|
||||
namespace: 'app';
|
||||
name: 'nav';
|
||||
}>;
|
||||
'app/root': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
JSX_2.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
router: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
ComponentType<{
|
||||
children?: ReactNode;
|
||||
}>,
|
||||
'app.router.wrapper',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: true;
|
||||
optional: true;
|
||||
}
|
||||
>;
|
||||
signInPage: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
ComponentType<SignInPageProps>,
|
||||
'core.sign-in-page.component',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: true;
|
||||
optional: true;
|
||||
}
|
||||
>;
|
||||
children: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>,
|
||||
{
|
||||
singleton: true;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
elements: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
wrappers: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
ComponentType<{
|
||||
children?: ReactNode;
|
||||
}>,
|
||||
'app.root.wrapper',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: undefined;
|
||||
namespace: 'app';
|
||||
name: 'root';
|
||||
}>;
|
||||
'app/routes': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
JSX_2.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
routes: ExtensionInput<
|
||||
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ConfigurableExtensionDataRef<
|
||||
RouteRef<AnyRouteRefParams>,
|
||||
'core.routing.ref',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: undefined;
|
||||
namespace: 'app';
|
||||
name: 'routes';
|
||||
}>;
|
||||
'api:app/app-theme': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
themes: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<AppTheme, 'core.theme.theme', {}>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'app-theme';
|
||||
}>;
|
||||
'theme:app/light': ExtensionDefinition<{
|
||||
kind: 'theme';
|
||||
namespace: 'app';
|
||||
name: 'light';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<AppTheme, 'core.theme.theme', {}>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'theme:app/dark': ExtensionDefinition<{
|
||||
kind: 'theme';
|
||||
namespace: 'app';
|
||||
name: 'dark';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<AppTheme, 'core.theme.theme', {}>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/components': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
components: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
{
|
||||
ref: ComponentRef;
|
||||
impl: ComponentType;
|
||||
},
|
||||
'core.component.component',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'components';
|
||||
}>;
|
||||
'api:app/icons': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
icons: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
{
|
||||
[x: string]: IconComponent_2;
|
||||
},
|
||||
'core.icons',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'icons';
|
||||
}>;
|
||||
'api:app/feature-flags': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'feature-flags';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/translations': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
translations: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
| TranslationResource<string>
|
||||
| TranslationMessages<
|
||||
string,
|
||||
{
|
||||
[x: string]: string;
|
||||
},
|
||||
boolean
|
||||
>,
|
||||
'core.translation.translation',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'translations';
|
||||
}>;
|
||||
'app-root-element:app/oauth-request-dialog': ExtensionDefinition<{
|
||||
kind: 'app-root-element';
|
||||
namespace: 'app';
|
||||
name: 'oauth-request-dialog';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
JSX_2.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'app-root-element:app/alert-display': ExtensionDefinition<{
|
||||
config: {
|
||||
transientTimeoutMs: number;
|
||||
anchorOrigin: {
|
||||
horizontal: 'center' | 'left' | 'right';
|
||||
vertical: 'top' | 'bottom';
|
||||
};
|
||||
};
|
||||
configInput: {
|
||||
anchorOrigin?:
|
||||
| {
|
||||
horizontal?: 'center' | 'left' | 'right' | undefined;
|
||||
vertical?: 'top' | 'bottom' | undefined;
|
||||
}
|
||||
| undefined;
|
||||
transientTimeoutMs?: number | undefined;
|
||||
};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
JSX_2.Element,
|
||||
'core.reactElement',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
[x: string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{
|
||||
optional: boolean;
|
||||
singleton: boolean;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'app-root-element';
|
||||
namespace: 'app';
|
||||
name: 'alert-display';
|
||||
}>;
|
||||
'api:app/discovery': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'discovery';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/alert': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'alert';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/analytics': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'analytics';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/error': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'error';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/storage': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'storage';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/fetch': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'fetch';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/oauth-request': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'oauth-request';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/google-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'google-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/microsoft-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'microsoft-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/github-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'github-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/okta-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'okta-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/gitlab-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'gitlab-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/onelogin-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'onelogin-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/bitbucket-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'bitbucket-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/bitbucket-server-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'bitbucket-server-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/atlassian-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'atlassian-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/vmware-cloud-auth': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'vmware-cloud-auth';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
'api:app/permission': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
namespace: undefined;
|
||||
name: 'permission';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
export default appPlugin;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: backstage-plugin-app
|
||||
title: '@backstage/plugin-app'
|
||||
spec:
|
||||
lifecycle: experimental
|
||||
type: backstage-frontend-plugin
|
||||
owner: maintainers
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "@backstage/plugin-app",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/app"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "frontend-plugin",
|
||||
"pluginId": "app",
|
||||
"pluginPackages": [
|
||||
"@backstage/plugin-app",
|
||||
"@backstage/plugin-app-backend",
|
||||
"@backstage/plugin-app-node"
|
||||
]
|
||||
},
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-permission-react": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/dev-utils": "workspace:^",
|
||||
"@backstage/frontend-test-utils": "workspace:^",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^15.0.0",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"msw": "^1.0.0",
|
||||
"react": "^16.13.1 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import {
|
||||
AlertApiForwarder,
|
||||
NoOpAnalyticsApi,
|
||||
ErrorApiForwarder,
|
||||
ErrorAlerter,
|
||||
GoogleAuth,
|
||||
GithubAuth,
|
||||
OktaAuth,
|
||||
GitlabAuth,
|
||||
MicrosoftAuth,
|
||||
BitbucketAuth,
|
||||
BitbucketServerAuth,
|
||||
OAuthRequestManager,
|
||||
WebStorage,
|
||||
UrlPatternDiscovery,
|
||||
OneLoginAuth,
|
||||
UnhandledErrorForwarder,
|
||||
AtlassianAuth,
|
||||
createFetchApi,
|
||||
FetchMiddlewares,
|
||||
VMwareCloudAuth,
|
||||
} from '../../../packages/core-app-api/src/apis/implementations';
|
||||
|
||||
import {
|
||||
createApiFactory,
|
||||
alertApiRef,
|
||||
analyticsApiRef,
|
||||
errorApiRef,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
identityApiRef,
|
||||
oauthRequestApiRef,
|
||||
googleAuthApiRef,
|
||||
githubAuthApiRef,
|
||||
oktaAuthApiRef,
|
||||
gitlabAuthApiRef,
|
||||
microsoftAuthApiRef,
|
||||
storageApiRef,
|
||||
configApiRef,
|
||||
oneloginAuthApiRef,
|
||||
bitbucketAuthApiRef,
|
||||
bitbucketServerAuthApiRef,
|
||||
atlassianAuthApiRef,
|
||||
vmwareCloudAuthApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { ApiBlueprint } from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
permissionApiRef,
|
||||
IdentityPermissionApi,
|
||||
} from '@backstage/plugin-permission-react';
|
||||
|
||||
export const apis = [
|
||||
ApiBlueprint.make({
|
||||
name: 'discovery',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: discoveryApiRef,
|
||||
deps: { configApi: configApiRef },
|
||||
factory: ({ configApi }) =>
|
||||
UrlPatternDiscovery.compile(
|
||||
`${configApi.getString('backend.baseUrl')}/api/{{ pluginId }}`,
|
||||
),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'alert',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: alertApiRef,
|
||||
deps: {},
|
||||
factory: () => new AlertApiForwarder(),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'analytics',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: analyticsApiRef,
|
||||
deps: {},
|
||||
factory: () => new NoOpAnalyticsApi(),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'error',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: errorApiRef,
|
||||
deps: { alertApi: alertApiRef },
|
||||
factory: ({ alertApi }) => {
|
||||
const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder());
|
||||
UnhandledErrorForwarder.forward(errorApi, { hidden: false });
|
||||
return errorApi;
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'storage',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: storageApiRef,
|
||||
deps: { errorApi: errorApiRef },
|
||||
factory: ({ errorApi }) => WebStorage.create({ errorApi }),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'fetch',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: fetchApiRef,
|
||||
deps: {
|
||||
configApi: configApiRef,
|
||||
identityApi: identityApiRef,
|
||||
discoveryApi: discoveryApiRef,
|
||||
},
|
||||
factory: ({ configApi, identityApi, discoveryApi }) => {
|
||||
return createFetchApi({
|
||||
middleware: [
|
||||
FetchMiddlewares.resolvePluginProtocol({
|
||||
discoveryApi,
|
||||
}),
|
||||
FetchMiddlewares.injectIdentityAuth({
|
||||
identityApi,
|
||||
config: configApi,
|
||||
}),
|
||||
],
|
||||
});
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'oauth-request',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: oauthRequestApiRef,
|
||||
deps: {},
|
||||
factory: () => new OAuthRequestManager(),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'google-auth',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: googleAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GoogleAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'microsoft-auth',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: microsoftAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
MicrosoftAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'github-auth',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: githubAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GithubAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
defaultScopes: ['read:user'],
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'okta-auth',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: oktaAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OktaAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'gitlab-auth',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: gitlabAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GitlabAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'onelogin-auth',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: oneloginAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OneLoginAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'bitbucket-auth',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: bitbucketAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
BitbucketAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
defaultScopes: ['account'],
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'bitbucket-server-auth',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: bitbucketServerAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
BitbucketServerAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
defaultScopes: ['REPO_READ'],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'atlassian-auth',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: atlassianAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) => {
|
||||
return AtlassianAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
});
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'vmware-cloud-auth',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: vmwareCloudAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) => {
|
||||
return VMwareCloudAuth.create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
});
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'permission',
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
api: permissionApiRef,
|
||||
deps: {
|
||||
discovery: discoveryApiRef,
|
||||
identity: identityApiRef,
|
||||
config: configApiRef,
|
||||
},
|
||||
factory: ({ config, discovery, identity }) =>
|
||||
IdentityPermissionApi.create({ config, discovery, identity }),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
] as const;
|
||||
+17
-7
@@ -21,6 +21,10 @@ import {
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { ApiProvider } from '../../../../packages/core-app-api/src';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppThemeProvider } from '../../../../packages/core-app-api/src/app/AppThemeProvider';
|
||||
|
||||
export const App = createExtension({
|
||||
namespace: 'app',
|
||||
@@ -31,11 +35,17 @@ export const App = createExtension({
|
||||
}),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: ({ node, inputs }) => [
|
||||
coreExtensionData.reactElement(
|
||||
<ExtensionBoundary node={node}>
|
||||
{inputs.root.get(coreExtensionData.reactElement)}
|
||||
</ExtensionBoundary>,
|
||||
),
|
||||
],
|
||||
factory: ({ node, apis, inputs }) => {
|
||||
return [
|
||||
coreExtensionData.reactElement(
|
||||
<ApiProvider apis={apis}>
|
||||
<AppThemeProvider>
|
||||
<ExtensionBoundary node={node}>
|
||||
{inputs.root.get(coreExtensionData.reactElement)}
|
||||
</ExtensionBoundary>
|
||||
</AppThemeProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
];
|
||||
},
|
||||
});
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppLanguageSelector } from '../../../core-app-api/src/apis/implementations/AppLanguageApi';
|
||||
import { AppLanguageSelector } from '../../../../packages/core-app-api/src/apis/implementations/AppLanguageApi';
|
||||
import { appLanguageApiRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { ApiBlueprint, createApiFactory } from '@backstage/frontend-plugin-api';
|
||||
|
||||
+2
-2
@@ -33,9 +33,9 @@ import {
|
||||
SidebarItem,
|
||||
} from '@backstage/core-components';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import LogoIcon from '../../../app/src/components/Root/LogoIcon';
|
||||
import LogoIcon from '../../../../packages/app/src/components/Root/LogoIcon';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import LogoFull from '../../../app/src/components/Root/LogoFull';
|
||||
import LogoFull from '../../../../packages/app/src/components/Root/LogoFull';
|
||||
|
||||
const useSidebarLogoStyles = makeStyles({
|
||||
root: {
|
||||
+72
-10
@@ -19,7 +19,6 @@ import React, {
|
||||
Fragment,
|
||||
PropsWithChildren,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
@@ -27,21 +26,31 @@ import {
|
||||
RouterBlueprint,
|
||||
SignInPageBlueprint,
|
||||
coreExtensionData,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
errorApiRef,
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
routeResolutionApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
DiscoveryApi,
|
||||
ErrorApi,
|
||||
FetchApi,
|
||||
IdentityApi,
|
||||
ProfileInfo,
|
||||
SignInPageProps,
|
||||
configApiRef,
|
||||
identityApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { InternalAppContext } from '../wiring/InternalAppContext';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';
|
||||
import { isProtectedApp } from '../../../../packages/core-app-api/src/app/isProtectedApp';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { RouteTracker } from '../routing/RouteTracker';
|
||||
import { getBasePath } from '../routing/getBasePath';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { RouteTracker } from '../../../../packages/frontend-app-api/src/routing/RouteTracker';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { getBasePath } from '../../../../packages/frontend-app-api/src/routing/getBasePath';
|
||||
|
||||
export const AppRoot = createExtension({
|
||||
namespace: 'app',
|
||||
@@ -65,7 +74,28 @@ export const AppRoot = createExtension({
|
||||
]),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory({ inputs }) {
|
||||
factory({ inputs, apis }) {
|
||||
if (isProtectedApp()) {
|
||||
const identityApi = apis.get(identityApiRef);
|
||||
if (!identityApi) {
|
||||
throw new Error('App requires an Identity API implementation');
|
||||
}
|
||||
const appIdentityProxy = toAppIdentityProxy(identityApi);
|
||||
const discoveryApi = apis.get(discoveryApiRef);
|
||||
const errorApi = apis.get(errorApiRef);
|
||||
const fetchApi = apis.get(fetchApiRef);
|
||||
if (!discoveryApi || !errorApi || !fetchApi) {
|
||||
throw new Error(
|
||||
'App is running in protected mode but missing required APIs',
|
||||
);
|
||||
}
|
||||
appIdentityProxy.enableCookieAuth({
|
||||
discoveryApi,
|
||||
errorApi,
|
||||
fetchApi,
|
||||
});
|
||||
}
|
||||
|
||||
let content: React.ReactNode = (
|
||||
<>
|
||||
{inputs.elements.map(el => (
|
||||
@@ -123,6 +153,33 @@ function SignInPageWrapper({
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
type AppIdentityProxy = IdentityApi & {
|
||||
enableCookieAuth(ctx: {
|
||||
errorApi: ErrorApi;
|
||||
fetchApi: FetchApi;
|
||||
discoveryApi: DiscoveryApi;
|
||||
}): void;
|
||||
setTarget(
|
||||
impl: IdentityApi & /* backwards compat stuff */ {
|
||||
getUserId?(): string;
|
||||
getIdToken?(): Promise<string | undefined>;
|
||||
getProfile?(): ProfileInfo;
|
||||
},
|
||||
options: { signOutTargetUrl: string },
|
||||
): void;
|
||||
};
|
||||
|
||||
function toAppIdentityProxy(identityApi: IdentityApi): AppIdentityProxy {
|
||||
if (!('enableCookieAuth' in identityApi)) {
|
||||
throw new Error('Unexpected Identity API implementation');
|
||||
}
|
||||
return identityApi as AppIdentityProxy;
|
||||
}
|
||||
|
||||
type RouteResolverProxy = {
|
||||
getRouteObjects(): any[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the {@link AppRouter} component.
|
||||
* @public
|
||||
@@ -157,12 +214,17 @@ export function AppRouter(props: AppRouterProps) {
|
||||
} = props;
|
||||
|
||||
const configApi = useApi(configApiRef);
|
||||
const appIdentityProxy = toAppIdentityProxy(useApi(identityApiRef));
|
||||
const routeResolutionsApi = useApi(routeResolutionApiRef);
|
||||
const basePath = getBasePath(configApi);
|
||||
const internalAppContext = useContext(InternalAppContext);
|
||||
if (!internalAppContext) {
|
||||
throw new Error('AppRouter must be rendered within the AppProvider');
|
||||
|
||||
// TODO: Private access for now, probably replace with path -> node lookup method on the API
|
||||
if (!('getRouteObjects' in routeResolutionsApi)) {
|
||||
throw new Error('Unexpected route resolution API implementation');
|
||||
}
|
||||
const { routeObjects, appIdentityProxy } = internalAppContext;
|
||||
const routeObjects = (
|
||||
routeResolutionsApi as RouteResolverProxy
|
||||
).getRouteObjects();
|
||||
|
||||
// If the app hasn't configured a sign-in page, we just continue as guest.
|
||||
if (!SignInPageComponent) {
|
||||
+2
-1
@@ -28,7 +28,8 @@ import {
|
||||
createApiFactory,
|
||||
appThemeApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { AppThemeSelector } from '@backstage/core-app-api';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppThemeSelector } from '../../../../packages/core-app-api/src/apis/implementations';
|
||||
|
||||
/**
|
||||
* Contains the themes installed into the app.
|
||||
+2
-1
@@ -21,7 +21,8 @@ import {
|
||||
createApiFactory,
|
||||
componentsApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { DefaultComponentsApi } from '../apis/implementations/ComponentsApi';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { DefaultComponentsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/ComponentsApi';
|
||||
|
||||
/**
|
||||
* Contains the shareable components installed into the app.
|
||||
+1
-1
@@ -20,7 +20,7 @@ import {
|
||||
featureFlagsApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { LocalStorageFeatureFlags } from '../../../core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags';
|
||||
import { LocalStorageFeatureFlags } from '../../../../packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags';
|
||||
|
||||
/**
|
||||
* Contains the shareable icons installed into the app.
|
||||
+3
-2
@@ -21,9 +21,10 @@ import {
|
||||
createApiFactory,
|
||||
iconsApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { DefaultIconsApi } from '../apis/implementations/IconsApi';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { icons as defaultIcons } from '../../../app-defaults/src/defaults';
|
||||
import { DefaultIconsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/IconsApi';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { icons as defaultIcons } from '../../../../packages/app-defaults/src/defaults';
|
||||
|
||||
/**
|
||||
* Contains the shareable icons installed into the app.
|
||||
+1
-1
@@ -25,7 +25,7 @@ import {
|
||||
} from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi';
|
||||
import { I18nextTranslationApi } from '../../../../packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi';
|
||||
|
||||
/**
|
||||
* Contains translations that are installed in the app.
|
||||
+1
-2
@@ -24,8 +24,7 @@ import {
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { ErrorPanel } from '@backstage/core-components';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { components as defaultComponents } from '../../../app-defaults/src/defaults';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { components as defaultComponents } from '../../../../packages/app-defaults/src/defaults';
|
||||
|
||||
export const DefaultProgressComponent = createComponentExtension({
|
||||
ref: coreComponentRefs.progress,
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
export { App } from './App';
|
||||
export { AppLanguageApi } from './AppLanguageApi';
|
||||
export { AppLayout } from './AppLayout';
|
||||
export { AppNav } from './AppNav';
|
||||
export { AppRoot } from './AppRoot';
|
||||
export { AppRoutes } from './AppRoutes';
|
||||
export { AppThemeApi, DarkTheme, LightTheme } from './AppThemeApi';
|
||||
export { ComponentsApi } from './ComponentsApi';
|
||||
export { IconsApi } from './IconsApi';
|
||||
export { FeatureFlagsApi } from './FeatureFlagsApi';
|
||||
export { TranslationsApi } from './TranslationsApi';
|
||||
export {
|
||||
DefaultProgressComponent,
|
||||
DefaultErrorBoundaryComponent,
|
||||
DefaultNotFoundErrorPageComponent,
|
||||
} from './components';
|
||||
export {
|
||||
oauthRequestDialogAppRootElement,
|
||||
alertDisplayAppRootElement,
|
||||
} from './elements';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
export { appPlugin as default } from './plugin';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2024 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 { appPlugin } from './plugin';
|
||||
|
||||
describe('app', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(appPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2024 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 { createFrontendPlugin } from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
App,
|
||||
AppLanguageApi,
|
||||
AppLayout,
|
||||
AppNav,
|
||||
AppRoot,
|
||||
AppRoutes,
|
||||
AppThemeApi,
|
||||
DarkTheme,
|
||||
LightTheme,
|
||||
ComponentsApi,
|
||||
IconsApi,
|
||||
FeatureFlagsApi,
|
||||
TranslationsApi,
|
||||
DefaultProgressComponent,
|
||||
DefaultNotFoundErrorPageComponent,
|
||||
DefaultErrorBoundaryComponent,
|
||||
oauthRequestDialogAppRootElement,
|
||||
alertDisplayAppRootElement,
|
||||
} from './extensions';
|
||||
import { apis } from './defaultApis';
|
||||
|
||||
/** @public */
|
||||
export const appPlugin = createFrontendPlugin({
|
||||
id: 'app',
|
||||
extensions: [
|
||||
...apis,
|
||||
App,
|
||||
AppLanguageApi,
|
||||
AppLayout,
|
||||
AppNav,
|
||||
AppRoot,
|
||||
AppRoutes,
|
||||
AppThemeApi,
|
||||
DarkTheme,
|
||||
LightTheme,
|
||||
ComponentsApi,
|
||||
IconsApi,
|
||||
FeatureFlagsApi,
|
||||
TranslationsApi,
|
||||
DefaultProgressComponent,
|
||||
DefaultNotFoundErrorPageComponent,
|
||||
DefaultErrorBoundaryComponent,
|
||||
oauthRequestDialogAppRootElement,
|
||||
alertDisplayAppRootElement,
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2024 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 '@testing-library/jest-dom';
|
||||
@@ -60,6 +60,8 @@ const roleRules = [
|
||||
targetRole: 'frontend-plugin',
|
||||
except: [
|
||||
// TODO(freben): Address these
|
||||
'@backstage/frontend-app-api',
|
||||
'@backstage/frontend-test-utils',
|
||||
'@backstage/plugin-api-docs',
|
||||
'@backstage/plugin-techdocs-addons-test-utils',
|
||||
],
|
||||
|
||||
@@ -4438,6 +4438,7 @@ __metadata:
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/frontend-plugin-api": "workspace:^"
|
||||
"@backstage/plugin-app": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
@@ -4488,6 +4489,7 @@ __metadata:
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/frontend-app-api": "workspace:^"
|
||||
"@backstage/frontend-plugin-api": "workspace:^"
|
||||
"@backstage/plugin-app": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@testing-library/jest-dom": ^6.0.0
|
||||
@@ -4682,6 +4684,34 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-app@workspace:^, @backstage/plugin-app@workspace:plugins/app":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-app@workspace:plugins/app"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/core-components": "workspace:^"
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
"@backstage/frontend-plugin-api": "workspace:^"
|
||||
"@backstage/frontend-test-utils": "workspace:^"
|
||||
"@backstage/plugin-permission-react": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@material-ui/core": ^4.9.13
|
||||
"@material-ui/icons": ^4.9.1
|
||||
"@material-ui/lab": ^4.0.0-alpha.61
|
||||
"@testing-library/jest-dom": ^6.0.0
|
||||
"@testing-library/react": ^15.0.0
|
||||
"@testing-library/user-event": ^14.0.0
|
||||
"@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
msw: ^1.0.0
|
||||
react: ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
react-use: ^17.2.4
|
||||
peerDependencies:
|
||||
react: ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
react-router-dom: 6.0.0-beta.0 || ^6.3.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-atlassian-provider@workspace:^, @backstage/plugin-auth-backend-module-atlassian-provider@workspace:plugins/auth-backend-module-atlassian-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-atlassian-provider@workspace:plugins/auth-backend-module-atlassian-provider"
|
||||
@@ -26731,6 +26761,7 @@ __metadata:
|
||||
"@backstage/frontend-plugin-api": "workspace:^"
|
||||
"@backstage/integration-react": "workspace:^"
|
||||
"@backstage/plugin-api-docs": "workspace:^"
|
||||
"@backstage/plugin-app": "workspace:^"
|
||||
"@backstage/plugin-app-visualizer": "workspace:^"
|
||||
"@backstage/plugin-auth-react": "workspace:^"
|
||||
"@backstage/plugin-catalog": "workspace:^"
|
||||
@@ -30324,9 +30355,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"is-map@npm:^2.0.1":
|
||||
version: 2.0.2
|
||||
resolution: "is-map@npm:2.0.2"
|
||||
checksum: ace3d0ecd667bbdefdb1852de601268f67f2db725624b1958f279316e13fecb8fa7df91fd60f690d7417b4ec180712f5a7ee967008e27c65cfd475cc84337728
|
||||
version: 2.0.3
|
||||
resolution: "is-map@npm:2.0.3"
|
||||
checksum: e6ce5f6380f32b141b3153e6ba9074892bbbbd655e92e7ba5ff195239777e767a976dcd4e22f864accaf30e53ebf961ab1995424aef91af68788f0591b7396cc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -30541,9 +30572,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"is-set@npm:^2.0.1":
|
||||
version: 2.0.2
|
||||
resolution: "is-set@npm:2.0.2"
|
||||
checksum: b64343faf45e9387b97a6fd32be632ee7b269bd8183701f3b3f5b71a7cf00d04450ed8669d0bd08753e08b968beda96fca73a10fd0ff56a32603f64deba55a57
|
||||
version: 2.0.3
|
||||
resolution: "is-set@npm:2.0.3"
|
||||
checksum: 36e3f8c44bdbe9496c9689762cc4110f6a6a12b767c5d74c0398176aa2678d4467e3bf07595556f2dba897751bde1422480212b97d973c7b08a343100b0c0dfe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -35645,12 +35676,12 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"object-is@npm:^1.0.1":
|
||||
version: 1.1.5
|
||||
resolution: "object-is@npm:1.1.5"
|
||||
version: 1.1.6
|
||||
resolution: "object-is@npm:1.1.6"
|
||||
dependencies:
|
||||
call-bind: ^1.0.2
|
||||
define-properties: ^1.1.3
|
||||
checksum: 989b18c4cba258a6b74dc1d74a41805c1a1425bce29f6cabb50dcb1a6a651ea9104a1b07046739a49a5bb1bc49727bcb00efd5c55f932f6ea04ec8927a7901fe
|
||||
call-bind: ^1.0.7
|
||||
define-properties: ^1.2.1
|
||||
checksum: 3ea22759967e6f2380a2cbbd0f737b42dc9ddb2dfefdb159a1b927fea57335e1b058b564bfa94417db8ad58cddab33621a035de6f5e5ad56d89f2dd03e66c6a1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -39044,7 +39075,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react@npm:^18.0.2":
|
||||
"react@npm:^16.13.1 || ^17.0.0 || ^18.0.0, react@npm:^18.0.2":
|
||||
version: 18.3.1
|
||||
resolution: "react@npm:18.3.1"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user