Merge pull request #19882 from backstage/mob/extensionInstances
frontend-app-api: Internal restructure
This commit is contained in:
@@ -20,4 +20,4 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { createApp } from './createApp';
|
||||
export * from './wiring';
|
||||
|
||||
+19
-31
@@ -20,10 +20,10 @@ import {
|
||||
BackstagePlugin,
|
||||
coreExtensionData,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { Core } from './extensions/Core';
|
||||
import { CoreRoutes } from './extensions/CoreRoutes';
|
||||
import { CoreLayout } from './extensions/CoreLayout';
|
||||
import { CoreNav } from './extensions/CoreNav';
|
||||
import { Core } from '../extensions/Core';
|
||||
import { CoreRoutes } from '../extensions/CoreRoutes';
|
||||
import { CoreLayout } from '../extensions/CoreLayout';
|
||||
import { CoreNav } from '../extensions/CoreNav';
|
||||
import {
|
||||
createExtensionInstance,
|
||||
ExtensionInstance,
|
||||
@@ -32,8 +32,8 @@ import {
|
||||
ExtensionInstanceParameters,
|
||||
mergeExtensionParameters,
|
||||
readAppExtensionParameters,
|
||||
} from './wiring/parameters';
|
||||
import { RoutingProvider } from './routing/RoutingContext';
|
||||
} from './parameters';
|
||||
import { RoutingProvider } from '../routing/RoutingContext';
|
||||
import {
|
||||
AnyApiFactory,
|
||||
ApiHolder,
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
BackstagePlugin as LegacyBackstagePlugin,
|
||||
featureFlagsApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { getAvailablePlugins } from './wiring/discovery';
|
||||
import { getAvailablePlugins } from './discovery';
|
||||
import {
|
||||
ApiFactoryRegistry,
|
||||
ApiProvider,
|
||||
@@ -57,22 +57,22 @@ import {
|
||||
|
||||
// TODO: Get rid of all of these
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppThemeProvider } from '../../core-app-api/src/app/AppThemeProvider';
|
||||
import { AppThemeProvider } from '../../../core-app-api/src/app/AppThemeProvider';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppContextProvider } from '../../core-app-api/src/app/AppContext';
|
||||
import { AppContextProvider } from '../../../core-app-api/src/app/AppContext';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { LocalStorageFeatureFlags } from '../../core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags';
|
||||
import { LocalStorageFeatureFlags } from '../../../core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { defaultConfigLoaderSync } from '../../core-app-api/src/app/defaultConfigLoader';
|
||||
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';
|
||||
import { overrideBaseUrlConfigs } from '../../../core-app-api/src/app/overrideBaseUrlConfigs';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import {
|
||||
apis as defaultApis,
|
||||
components as defaultComponents,
|
||||
icons as defaultIcons,
|
||||
themes as defaultThemes,
|
||||
} from '../../app-defaults/src/defaults';
|
||||
} from '../../../app-defaults/src/defaults';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
/** @public */
|
||||
@@ -174,13 +174,8 @@ export function createApp(options: {
|
||||
return {
|
||||
createRoot() {
|
||||
const rootComponents = rootInstances
|
||||
.map(
|
||||
e =>
|
||||
e.data.get(
|
||||
coreExtensionData.reactComponent.id,
|
||||
) as typeof coreExtensionData.reactComponent.T,
|
||||
)
|
||||
.filter(Boolean);
|
||||
.map(e => e.getData(coreExtensionData.reactComponent))
|
||||
.filter((x): x is React.ComponentType => !!x);
|
||||
return (
|
||||
<ApiProvider apis={apiHolder}>
|
||||
<AppContextProvider appContext={appContext}>
|
||||
@@ -254,13 +249,8 @@ function createApiHolder(
|
||||
const apiFactories =
|
||||
coreExtension.attachments
|
||||
.get('apis')
|
||||
?.map(
|
||||
e =>
|
||||
e.data.get(
|
||||
coreExtensionData.apiFactory.id,
|
||||
) as typeof coreExtensionData.apiFactory.T,
|
||||
)
|
||||
.filter(Boolean) ?? [];
|
||||
?.map(e => e.getData(coreExtensionData.apiFactory))
|
||||
.filter((x): x is AnyApiFactory => !!x) ?? [];
|
||||
|
||||
for (const factory of apiFactories) {
|
||||
factoryRegistry.register('default', factory);
|
||||
@@ -307,10 +297,8 @@ export function extractRouteInfoFromInstanceTree(
|
||||
const results = new Map<RouteRef, string>();
|
||||
|
||||
function visit(current: ExtensionInstance, basePath: string) {
|
||||
const routePath = current.data.get(coreExtensionData.routePath.id) ?? '';
|
||||
const routeRef = current.data.get(
|
||||
coreExtensionData.routeRef.id,
|
||||
) as RouteRef;
|
||||
const routePath = current.getData(coreExtensionData.routePath) ?? '';
|
||||
const routeRef = current.getData(coreExtensionData.routeRef);
|
||||
|
||||
// TODO: join paths in a more robust way
|
||||
const fullPath = basePath + routePath;
|
||||
+15
-7
@@ -14,21 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BackstagePlugin, Extension } from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
BackstagePlugin,
|
||||
Extension,
|
||||
ExtensionDataRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
|
||||
/** @internal */
|
||||
export interface ExtensionInstance {
|
||||
readonly $$type: '@backstage/ExtensionInstance';
|
||||
|
||||
readonly id: string;
|
||||
/**
|
||||
* Maps extension data ref IDs to extensions produced.
|
||||
* Get concrete value for the given extension data reference. Returns undefined if no value is available.
|
||||
*/
|
||||
readonly data: Map<string, unknown>;
|
||||
getData<T>(ref: ExtensionDataRef<T>): T | undefined;
|
||||
/**
|
||||
* Maps input names to the actual instances given to them.
|
||||
*/
|
||||
readonly attachments: Map<string, ExtensionInstance[]>;
|
||||
readonly $$type: 'extension-instance';
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -70,7 +75,7 @@ export function createExtensionInstance(options: {
|
||||
({ extensionData: pointData }, inputName) => {
|
||||
// TODO: validation
|
||||
return (attachments.get(inputName) ?? []).map(attachment =>
|
||||
mapValues(pointData, ref => attachment.data.get(ref.id)),
|
||||
mapValues(pointData, ref => attachment.getData(ref)),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -82,9 +87,12 @@ export function createExtensionInstance(options: {
|
||||
}
|
||||
|
||||
return {
|
||||
$$type: '@backstage/ExtensionInstance',
|
||||
id: options.extension.id,
|
||||
data: extensionData,
|
||||
getData<T>(ref: ExtensionDataRef<T>): T | undefined {
|
||||
return extensionData.get(ref.id) as T | undefined;
|
||||
},
|
||||
|
||||
attachments,
|
||||
$$type: 'extension-instance',
|
||||
};
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export function getAvailablePlugins(): BackstagePlugin[] {
|
||||
|
||||
function isBackstagePlugin(obj: unknown): obj is BackstagePlugin {
|
||||
if (obj !== null && typeof obj === 'object' && '$$type' in obj) {
|
||||
return obj.$$type === 'backstage-plugin';
|
||||
return obj.$$type === '@backstage/BackstagePlugin';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2023 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 { createApp } from './createApp';
|
||||
@@ -24,7 +24,7 @@ export type AnyExtensionDataMap = {
|
||||
// @public (undocumented)
|
||||
export interface BackstagePlugin {
|
||||
// (undocumented)
|
||||
$$type: 'backstage-plugin';
|
||||
$$type: '@backstage/BackstagePlugin';
|
||||
// (undocumented)
|
||||
extensions: Extension<unknown>[];
|
||||
// (undocumented)
|
||||
@@ -176,7 +176,7 @@ export function createSchemaFromZod<TOutput, TInput>(
|
||||
// @public (undocumented)
|
||||
export interface Extension<TConfig> {
|
||||
// (undocumented)
|
||||
$$type: 'extension';
|
||||
$$type: '@backstage/Extension';
|
||||
// (undocumented)
|
||||
at: string;
|
||||
// (undocumented)
|
||||
@@ -268,7 +268,7 @@ export type ExtensionDataRef<
|
||||
id: string;
|
||||
T: TData;
|
||||
config: TConfig;
|
||||
$$type: 'extension-data';
|
||||
$$type: '@backstage/ExtensionDataRef';
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('createApiExtension', () => {
|
||||
});
|
||||
|
||||
expect(extension).toEqual({
|
||||
$$type: 'extension',
|
||||
$$type: '@backstage/Extension',
|
||||
id: 'apis.test',
|
||||
at: 'core/apis',
|
||||
disabled: false,
|
||||
@@ -39,7 +39,7 @@ describe('createApiExtension', () => {
|
||||
inputs: {},
|
||||
output: {
|
||||
api: expect.objectContaining({
|
||||
$$type: 'extension-data',
|
||||
$$type: '@backstage/ExtensionDataRef',
|
||||
id: 'core.api.factory',
|
||||
config: {},
|
||||
}),
|
||||
@@ -63,9 +63,9 @@ describe('createApiExtension', () => {
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// boo
|
||||
expect(extension).toEqual({
|
||||
$$type: 'extension',
|
||||
$$type: '@backstage/Extension',
|
||||
id: 'apis.test',
|
||||
at: 'core/apis',
|
||||
disabled: false,
|
||||
@@ -73,7 +73,7 @@ describe('createApiExtension', () => {
|
||||
inputs: {},
|
||||
output: {
|
||||
api: expect.objectContaining({
|
||||
$$type: 'extension-data',
|
||||
$$type: '@backstage/ExtensionDataRef',
|
||||
id: 'core.api.factory',
|
||||
config: {},
|
||||
}),
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('createPageExtension', () => {
|
||||
component: async () => <div />,
|
||||
}),
|
||||
).toEqual({
|
||||
$$type: 'extension',
|
||||
$$type: '@backstage/Extension',
|
||||
id: 'test',
|
||||
at: 'core.routes/routes',
|
||||
configSchema: expect.anything(),
|
||||
@@ -61,7 +61,7 @@ describe('createPageExtension', () => {
|
||||
component: async () => <div />,
|
||||
}),
|
||||
).toEqual({
|
||||
$$type: 'extension',
|
||||
$$type: '@backstage/Extension',
|
||||
id: 'test',
|
||||
at: 'other/place',
|
||||
configSchema: expect.anything(),
|
||||
@@ -86,7 +86,7 @@ describe('createPageExtension', () => {
|
||||
component: async () => <div />,
|
||||
}),
|
||||
).toEqual({
|
||||
$$type: 'extension',
|
||||
$$type: '@backstage/Extension',
|
||||
id: 'test',
|
||||
at: 'core.routes/routes',
|
||||
configSchema: expect.anything(),
|
||||
|
||||
@@ -87,7 +87,7 @@ export function createExtension<
|
||||
return {
|
||||
...options,
|
||||
disabled: options.disabled ?? false,
|
||||
$$type: 'extension',
|
||||
$$type: '@backstage/Extension',
|
||||
inputs: options.inputs ?? {},
|
||||
factory({ bind, config, inputs }) {
|
||||
// TODO: Simplify this, but TS wouldn't infer the input type for some reason
|
||||
|
||||
@@ -22,7 +22,7 @@ export type ExtensionDataRef<
|
||||
id: string;
|
||||
T: TData;
|
||||
config: TConfig;
|
||||
$$type: 'extension-data';
|
||||
$$type: '@backstage/ExtensionDataRef';
|
||||
};
|
||||
|
||||
/** @public */
|
||||
@@ -40,7 +40,7 @@ export function createExtensionDataRef<TData>(
|
||||
): ConfigurableExtensionDataRef<TData> {
|
||||
return {
|
||||
id,
|
||||
$$type: 'extension-data',
|
||||
$$type: '@backstage/ExtensionDataRef',
|
||||
config: {},
|
||||
optional() {
|
||||
return { ...this, config: { ...this.config, optional: true } };
|
||||
|
||||
@@ -24,7 +24,7 @@ export interface PluginOptions {
|
||||
|
||||
/** @public */
|
||||
export interface BackstagePlugin {
|
||||
$$type: 'backstage-plugin';
|
||||
$$type: '@backstage/BackstagePlugin';
|
||||
id: string;
|
||||
extensions: Extension<unknown>[];
|
||||
}
|
||||
@@ -33,7 +33,7 @@ export interface BackstagePlugin {
|
||||
export function createPlugin(options: PluginOptions): BackstagePlugin {
|
||||
return {
|
||||
...options,
|
||||
$$type: 'backstage-plugin',
|
||||
$$type: '@backstage/BackstagePlugin',
|
||||
extensions: options.extensions ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export type AnyExtensionDataMap = {
|
||||
|
||||
/** @public */
|
||||
export interface Extension<TConfig> {
|
||||
$$type: 'extension';
|
||||
$$type: '@backstage/Extension';
|
||||
id: string;
|
||||
at: string;
|
||||
disabled: boolean;
|
||||
|
||||
Reference in New Issue
Block a user