app-api: merge in createApp from core along with icons

Co-authored-by: Juan Lulkin <jmaiz@spotify.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-03-10 17:58:34 +01:00
parent 53712e1fc8
commit a6cfc1c409
16 changed files with 510 additions and 144 deletions
+2
View File
@@ -29,7 +29,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/components": "^0.1.0",
"@backstage/config": "^0.1.3",
"@backstage/plugin-api": "^0.1.0",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { IconComponent } from '../../icons';
import { IconComponent } from '@backstage/plugin-api';
import { Observable } from '../../types';
import { ApiRef, createApiRef } from '../system';
+4 -4
View File
@@ -20,7 +20,7 @@ import { render, screen } from '@testing-library/react';
import React, { PropsWithChildren } from 'react';
import { BrowserRouter, Routes } from 'react-router-dom';
import { createRoutableExtension } from '../extensions';
import { defaultSystemIcons } from '../icons';
import { defaultAppIcons } from './icons';
import { createPlugin } from '../plugin';
import { useRouteRef } from '../routing/hooks';
import {
@@ -163,7 +163,7 @@ describe('Integration Test', () => {
theme: lightTheme,
},
],
icons: defaultSystemIcons,
icons: defaultAppIcons,
plugins: [],
components,
bindRoutes: ({ bind }) => {
@@ -214,7 +214,7 @@ describe('Integration Test', () => {
theme: lightTheme,
},
],
icons: defaultSystemIcons,
icons: defaultAppIcons,
plugins: [],
components,
bindRoutes: ({ bind }) => {
@@ -257,7 +257,7 @@ describe('Integration Test', () => {
theme: lightTheme,
},
],
icons: defaultSystemIcons,
icons: defaultAppIcons,
plugins: [],
components,
bindRoutes: ({ bind }) => {
+5 -5
View File
@@ -23,6 +23,7 @@ import React, {
} from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { useAsync } from 'react-use';
import { IconComponent } from '@backstage/plugin-api';
import {
AnyApiFactory,
ApiHolder,
@@ -48,7 +49,6 @@ import {
routeElementDiscoverer,
traverseElementTree,
} from '../extensions/traversal';
import { IconComponent, IconComponentMap, IconKey } from '../icons';
import { BackstagePlugin } from '../plugin';
import { AnyRoutes } from '../plugin/types';
import { RouteRef, ExternalRouteRef } from '../routing';
@@ -103,7 +103,7 @@ export function generateBoundRoutes(
type FullAppOptions = {
apis: Iterable<AnyApiFactory>;
icons: IconComponentMap;
icons: NonNullable<AppOptions['icons']>;
plugins: BackstagePlugin<any, any>[];
components: AppComponents;
themes: AppTheme[];
@@ -156,7 +156,7 @@ class AppContextImpl implements AppContext {
return this.app.getPlugins();
}
getSystemIcon(key: IconKey): IconComponent | undefined {
getSystemIcon(key: string): IconComponent | undefined {
return this.app.getSystemIcon(key);
}
@@ -188,7 +188,7 @@ export class PrivateAppImpl implements BackstageApp {
private configApi?: ConfigApi;
private readonly apis: Iterable<AnyApiFactory>;
private readonly icons: IconComponentMap;
private readonly icons: NonNullable<AppOptions['icons']>;
private readonly plugins: BackstagePlugin<any, any>[];
private readonly components: AppComponents;
private readonly themes: AppTheme[];
@@ -213,7 +213,7 @@ export class PrivateAppImpl implements BackstageApp {
return this.plugins;
}
getSystemIcon(key: IconKey): IconComponent | undefined {
getSystemIcon(key: string): IconComponent | undefined {
return this.icons[key];
}
@@ -0,0 +1,94 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { defaultConfigLoader } from './createApp';
(process as any).env = { NODE_ENV: 'test' };
const anyEnv = process.env as any;
const anyWindow = window as any;
describe('defaultConfigLoader', () => {
afterEach(() => {
delete anyEnv.APP_CONFIG;
delete anyWindow.__APP_CONFIG__;
});
it('loads static config', async () => {
anyEnv.APP_CONFIG = [
{ data: { my: 'config' }, context: 'a' },
{ data: { my: 'override-config' }, context: 'b' },
];
const configs = await defaultConfigLoader();
expect(configs).toEqual([
{ data: { my: 'config' }, context: 'a' },
{ data: { my: 'override-config' }, context: 'b' },
]);
});
it('loads runtime config', async () => {
anyEnv.APP_CONFIG = [
{ data: { my: 'override-config' }, context: 'a' },
{ data: { my: 'config' }, context: 'b' },
];
const configs = await (defaultConfigLoader as any)(
'{"my":"runtime-config"}',
);
expect(configs).toEqual([
{ data: { my: 'override-config' }, context: 'a' },
{ data: { my: 'config' }, context: 'b' },
{ data: { my: 'runtime-config' }, context: 'env' },
]);
});
it('fails to load invalid missing config', async () => {
await expect(defaultConfigLoader()).rejects.toThrow(
'No static configuration provided',
);
});
it('fails to load invalid static config', async () => {
anyEnv.APP_CONFIG = { my: 'invalid-config' };
await expect(defaultConfigLoader()).rejects.toThrow(
'Static configuration has invalid format',
);
});
it('fails to load bad runtime config', async () => {
anyEnv.APP_CONFIG = [{ data: { my: 'config' }, context: 'a' }];
await expect((defaultConfigLoader as any)('}')).rejects.toThrow(
'Failed to load runtime configuration, SyntaxError: Unexpected token } in JSON at position 0',
);
});
it('loads config from window.__APP_CONFIG__', async () => {
anyEnv.APP_CONFIG = [
{ data: { my: 'config' }, context: 'a' },
{ data: { my: 'override-config' }, context: 'b' },
];
const windowConfig = { app: { configKey: 'config-value' } };
anyWindow.__APP_CONFIG__ = windowConfig;
const configs = await defaultConfigLoader();
expect(configs).toEqual([
...anyEnv.APP_CONFIG,
{ context: 'window', data: windowConfig },
]);
});
});
+145
View File
@@ -0,0 +1,145 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { AppOptions, BootErrorPageProps, AppConfigLoader } from './types';
import { defaultAppIcons } from './icons';
import { BrowserRouter, MemoryRouter } from 'react-router-dom';
import LightIcon from '@material-ui/icons/WbSunny';
import DarkIcon from '@material-ui/icons/Brightness2';
import { ErrorPage, Progress } from '@backstage/components';
import { defaultApis } from './defaultApis';
import { lightTheme, darkTheme } from '@backstage/theme';
import { AppConfig, JsonObject } from '@backstage/config';
import { PrivateAppImpl } from './App';
/**
* The default config loader, which expects that config is available at compile-time
* in `process.env.APP_CONFIG`. APP_CONFIG should be an array of config objects as
* returned by the config loader.
*
* It will also load runtime config from the __APP_INJECTED_RUNTIME_CONFIG__ string,
* which can be rewritten at runtime to contain an additional JSON config object.
* If runtime config is present, it will be placed first in the config array, overriding
* other config values.
*/
export const defaultConfigLoader: AppConfigLoader = async (
// This string may be replaced at runtime to provide additional config.
// It should be replaced by a JSON-serialized config object.
// It's a param so we can test it, but at runtime this will always fall back to default.
runtimeConfigJson: string = '__APP_INJECTED_RUNTIME_CONFIG__',
) => {
const appConfig = process.env.APP_CONFIG;
if (!appConfig) {
throw new Error('No static configuration provided');
}
if (!Array.isArray(appConfig)) {
throw new Error('Static configuration has invalid format');
}
const configs = (appConfig.slice() as unknown) as AppConfig[];
// Avoiding this string also being replaced at runtime
if (runtimeConfigJson !== '__app_injected_runtime_config__'.toUpperCase()) {
try {
const data = JSON.parse(runtimeConfigJson) as JsonObject;
if (Array.isArray(data)) {
configs.push(...data);
} else {
configs.push({ data, context: 'env' });
}
} catch (error) {
throw new Error(`Failed to load runtime configuration, ${error}`);
}
}
const windowAppConfig = (window as any).__APP_CONFIG__;
if (windowAppConfig) {
configs.push({
context: 'window',
data: windowAppConfig,
});
}
return configs;
};
// createApp is defined in core, and not core-api, since we need access
// to the components inside core to provide defaults.
// The actual implementation of the app class still lives in core-api,
// as it needs to be used by dev- and test-utils.
/**
* Creates a new Backstage App.
*/
export function createApp(options?: AppOptions) {
const DefaultNotFoundPage = () => (
<ErrorPage status="404" statusMessage="PAGE NOT FOUND" />
);
const DefaultBootErrorPage = ({ step, error }: BootErrorPageProps) => {
let message = '';
if (step === 'load-config') {
message = `The configuration failed to load, someone should have a look at this error: ${error.message}`;
}
// TODO: figure out a nicer way to handle routing on the error page, when it can be done.
return (
<MemoryRouter>
<ErrorPage status="501" statusMessage={message} />
</MemoryRouter>
);
};
const apis = options?.apis ?? [];
const icons = { ...defaultAppIcons, ...options?.icons };
const plugins = options?.plugins ?? [];
const components = {
NotFoundErrorPage: DefaultNotFoundPage,
BootErrorPage: DefaultBootErrorPage,
Progress: Progress,
Router: BrowserRouter,
...options?.components,
};
const themes = options?.themes ?? [
{
id: 'light',
title: 'Light Theme',
variant: 'light',
theme: lightTheme,
icon: <LightIcon />,
},
{
id: 'dark',
title: 'Dark Theme',
variant: 'dark',
theme: darkTheme,
icon: <DarkIcon />,
},
];
const configLoader = options?.configLoader ?? defaultConfigLoader;
const app = new PrivateAppImpl({
apis,
icons,
plugins,
components,
themes,
configLoader,
defaultApis,
bindRoutes: options?.bindRoutes,
});
app.verify();
return app;
}
+220
View File
@@ -0,0 +1,220 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
alertApiRef,
errorApiRef,
AlertApiForwarder,
ErrorApiForwarder,
ErrorAlerter,
discoveryApiRef,
GoogleAuth,
GithubAuth,
OAuth2,
OktaAuth,
GitlabAuth,
Auth0Auth,
MicrosoftAuth,
oauthRequestApiRef,
OAuthRequestManager,
googleAuthApiRef,
githubAuthApiRef,
oauth2ApiRef,
oktaAuthApiRef,
gitlabAuthApiRef,
auth0AuthApiRef,
microsoftAuthApiRef,
storageApiRef,
WebStorage,
createApiFactory,
configApiRef,
UrlPatternDiscovery,
samlAuthApiRef,
SamlAuth,
oneloginAuthApiRef,
OneLoginAuth,
oidcAuthApiRef,
} from '../apis';
import OAuth2Icon from '@material-ui/icons/AcUnit';
export const defaultApis = [
createApiFactory({
api: discoveryApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) =>
UrlPatternDiscovery.compile(
`${configApi.getString('backend.baseUrl')}/api/{{ pluginId }}`,
),
}),
createApiFactory(alertApiRef, new AlertApiForwarder()),
createApiFactory({
api: errorApiRef,
deps: { alertApi: alertApiRef },
factory: ({ alertApi }) =>
new ErrorAlerter(alertApi, new ErrorApiForwarder()),
}),
createApiFactory({
api: storageApiRef,
deps: { errorApi: errorApiRef },
factory: ({ errorApi }) => WebStorage.create({ errorApi }),
}),
createApiFactory(oauthRequestApiRef, new OAuthRequestManager()),
createApiFactory({
api: googleAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
GoogleAuth.create({
discoveryApi,
oauthRequestApi,
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: microsoftAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
MicrosoftAuth.create({
discoveryApi,
oauthRequestApi,
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: githubAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
GithubAuth.create({
discoveryApi,
oauthRequestApi,
defaultScopes: ['read:user'],
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: oktaAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
OktaAuth.create({
discoveryApi,
oauthRequestApi,
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: gitlabAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
GitlabAuth.create({
discoveryApi,
oauthRequestApi,
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: auth0AuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
Auth0Auth.create({
discoveryApi,
oauthRequestApi,
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: oauth2ApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
OAuth2.create({
discoveryApi,
oauthRequestApi,
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: samlAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, configApi }) =>
SamlAuth.create({
discoveryApi,
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: oneloginAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
OneLoginAuth.create({
discoveryApi,
oauthRequestApi,
environment: configApi.getOptionalString('auth.environment'),
}),
}),
createApiFactory({
api: oidcAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
OAuth2.create({
discoveryApi,
oauthRequestApi,
provider: {
id: 'oidc',
title: 'Your Identity Provider',
icon: OAuth2Icon,
},
environment: configApi.getOptionalString('auth.environment'),
}),
}),
];
@@ -14,10 +14,20 @@
* limitations under the License.
*/
import { ComponentType } from 'react';
import { SvgIconProps } from '@material-ui/core';
import {
BrokenImageIcon,
ChatIcon,
DashboardIcon,
EmailIcon,
GitHubIcon,
GroupIcon,
HelpIcon,
UserIcon,
WarningIcon,
} from '@backstage/components';
import { IconComponent } from '@backstage/plugin-api';
export type SystemIconKey =
type AppIconsKey =
| 'brokenImage'
| 'chat'
| 'dashboard'
@@ -28,6 +38,16 @@ export type SystemIconKey =
| 'user'
| 'warning';
export type IconComponent = ComponentType<SvgIconProps>;
export type IconKey = SystemIconKey | string;
export type IconComponentMap = { [key in IconKey]: IconComponent };
export type AppIcons = { [key in AppIconsKey]: IconComponent };
export const defaultAppIcons: AppIcons = {
brokenImage: BrokenImageIcon,
chat: ChatIcon,
dashboard: DashboardIcon,
email: EmailIcon,
github: GitHubIcon,
group: GroupIcon,
help: HelpIcon,
user: UserIcon,
warning: WarningIcon,
};
+5 -4
View File
@@ -15,13 +15,14 @@
*/
import { ComponentType } from 'react';
import { IconComponent, IconComponentMap, IconKey } from '../icons';
import { AnyExternalRoutes, BackstagePlugin } from '../plugin/types';
import { ExternalRouteRef, RouteRef } from '../routing';
import { AnyApiFactory } from '../apis';
import { AppTheme, ProfileInfo } from '../apis/definitions';
import { AppConfig } from '@backstage/config';
import { IconComponent } from '@backstage/plugin-api';
import { SubRouteRef } from '../routing/types';
import { AppIcons } from './icons';
export type BootErrorPageProps = {
step: 'load-config';
@@ -125,7 +126,7 @@ export type AppOptions = {
/**
* Supply icons to override the default ones.
*/
icons?: IconComponentMap;
icons?: AppIcons & { [key in string]: IconComponent };
/**
* A list of all plugins to include in the app.
@@ -202,7 +203,7 @@ export type BackstageApp = {
/**
* Get a common or custom icon for this app.
*/
getSystemIcon(key: IconKey): IconComponent | undefined;
getSystemIcon(key: string): IconComponent | undefined;
/**
* Provider component that should wrap the Router created with getRouter()
@@ -233,7 +234,7 @@ export type AppContext = {
/**
* Get a common or custom icon for this app.
*/
getSystemIcon(key: IconKey): IconComponent | undefined;
getSystemIcon(key: string): IconComponent | undefined;
/**
* Get the components registered for various purposes in the app.
-60
View File
@@ -1,60 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { SvgIconProps } from '@material-ui/core';
import MuiBrokenImageIcon from '@material-ui/icons/BrokenImage';
import MuiChatIcon from '@material-ui/icons/Chat';
import MuiDashboardIcon from '@material-ui/icons/Dashboard';
import MuiEmailIcon from '@material-ui/icons/Email';
import MuiGitHubIcon from '@material-ui/icons/GitHub';
import MuiHelpIcon from '@material-ui/icons/Help';
import MuiPeopleIcon from '@material-ui/icons/People';
import MuiPersonIcon from '@material-ui/icons/Person';
import MuiWarningIcon from '@material-ui/icons/Warning';
import React from 'react';
import { useApp } from '../app/AppContext';
import { IconComponent, IconComponentMap, SystemIconKey } from './types';
export const defaultSystemIcons: IconComponentMap = {
brokenImage: MuiBrokenImageIcon,
chat: MuiChatIcon,
dashboard: MuiDashboardIcon,
email: MuiEmailIcon,
github: MuiGitHubIcon,
group: MuiPeopleIcon,
help: MuiHelpIcon,
user: MuiPersonIcon,
warning: MuiWarningIcon,
};
const overridableSystemIcon = (key: SystemIconKey): IconComponent => {
const Component = (props: SvgIconProps) => {
const app = useApp();
const Icon = app.getSystemIcon(key);
return Icon ? <Icon {...props} /> : <MuiBrokenImageIcon {...props} />;
};
return Component;
};
export const BrokenImageIcon = overridableSystemIcon('brokenImage');
export const ChatIcon = overridableSystemIcon('chat');
export const DashboardIcon = overridableSystemIcon('dashboard');
export const EmailIcon = overridableSystemIcon('email');
export const GitHubIcon = overridableSystemIcon('github');
export const GroupIcon = overridableSystemIcon('group');
export const HelpIcon = overridableSystemIcon('help');
export const UserIcon = overridableSystemIcon('user');
export const WarningIcon = overridableSystemIcon('warning');
-18
View File
@@ -1,18 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* 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 * from './icons';
export * from './types';
+6 -4
View File
@@ -14,7 +14,9 @@
* limitations under the License.
*/
export * from './public';
import * as privateExports from './private';
export default privateExports;
export * from './apis';
export * from './app';
export * from './extensions';
export * from './plugin';
export * from './routing';
export * from './types';
-17
View File
@@ -1,17 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { PrivateAppImpl } from './app/App';
-23
View File
@@ -1,23 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* 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 * from './apis';
export * from './app';
export * from './extensions';
export * from './icons';
export * from './plugin';
export * from './routing';
export * from './types';
+1 -1
View File
@@ -23,7 +23,7 @@ import {
ParamKeys,
OptionalParams,
} from './types';
import { IconComponent } from '../icons';
import { IconComponent } from '@backstage/plugin-api';
// TODO(Rugvip): Remove this in the next breaking release, it's exported but unused
export type RouteRefConfig<Params extends AnyParams> = {
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { IconComponent } from '../icons';
import { IconComponent } from '@backstage/plugin-api';
import { getOrCreateGlobalSingleton } from '../lib/globalObject';
export type AnyParams = { [param in string]: string } | undefined;