Merge pull request #8931 from backstage/rugvip/entityrr
catalog-react: deprecate catalogRouteRef and deduplicate entityRouteRef
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
The `entityRouteRef` is now a well-known route that should be imported directly from `@backstage/plugin-catalog-react`. It is guaranteed to be globally unique across duplicate installations of the `@backstage/plugin-catalog-react`, starting at this version.
|
||||
|
||||
Deprecated `entityRoute` in favor of `entityRouteRef`.
|
||||
|
||||
Deprecated `rootRoute` and `catalogRouteRef`. If you want to refer to the catalog index page from a public plugin you now need to use an `ExternalRouteRef` instead. For private plugins it is possible to take the shortcut of referring directly to `catalogPlugin.routes.indexPage` instead.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Added an external route binding from the `org` plugin to the catalog index page.
|
||||
|
||||
This change is needed because `@backstage/plugin-org` now has a required external route that needs to be bound for the app to start.
|
||||
|
||||
To apply this change to an existing app, make the following change to `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
|
||||
+import { orgPlugin } from '@backstage/plugin-org';
|
||||
import { SearchPage } from '@backstage/plugin-search';
|
||||
```
|
||||
|
||||
And further down within the `createApp` call:
|
||||
|
||||
```diff
|
||||
bind(scaffolderPlugin.externalRoutes, {
|
||||
registerComponent: catalogImportPlugin.routes.importPage,
|
||||
});
|
||||
+ bind(orgPlugin.externalRoutes, {
|
||||
+ catalogIndex: catalogPlugin.routes.catalogIndex,
|
||||
+ });
|
||||
},
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-bazaar': patch
|
||||
---
|
||||
|
||||
Switched out internal usage of the `catalogRouteRef` from `@backstage/plugin-catalog-react`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-app-api': patch
|
||||
---
|
||||
|
||||
Added validation during the application startup that detects if there are any plugins present that have not had their required external routes bound. Failing the validation will cause a hard crash as it is a programmer error. It lets you detect early on that there are dangling routes, rather than having them cause an error later on.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
'@backstage/plugin-org': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Added a new and required `catalogIndex` external route. It should typically be linked to the `catalogIndex` route of the Catalog plugin:
|
||||
|
||||
```ts
|
||||
bind(orgPlugin.externalRoutes, {
|
||||
catalogIndex: catalogPlugin.routes.catalogIndex,
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
const React = require('react');
|
||||
|
||||
// This is an index of stable short-links to different doc sites
|
||||
// for example https://backstage.io/link?bind-routes
|
||||
const redirects = {
|
||||
'bind-routes':
|
||||
'/docs/plugins/composability#binding-external-routes-in-the-app',
|
||||
};
|
||||
const fallback = '/docs';
|
||||
|
||||
function Link() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
const redirects = ${JSON.stringify(redirects)};
|
||||
const target = redirects[window.location.search.slice(1)] || '${fallback}';
|
||||
window.location.href = target;
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = Link;
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
CostInsightsPage,
|
||||
CostInsightsProjectGrowthInstructionsPage,
|
||||
} from '@backstage/plugin-cost-insights';
|
||||
import { orgPlugin } from '@backstage/plugin-org';
|
||||
import { ExplorePage, explorePlugin } from '@backstage/plugin-explore';
|
||||
import { GcpProjectsPage } from '@backstage/plugin-gcp-projects';
|
||||
import { GraphiQLPage } from '@backstage/plugin-graphiql';
|
||||
@@ -125,6 +126,9 @@ const app = createApp({
|
||||
bind(scaffolderPlugin.externalRoutes, {
|
||||
registerComponent: catalogImportPlugin.routes.importPage,
|
||||
});
|
||||
bind(orgPlugin.externalRoutes, {
|
||||
catalogIndex: catalogPlugin.routes.catalogIndex,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -35,31 +35,9 @@ import {
|
||||
createRoutableExtension,
|
||||
analyticsApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { generateBoundRoutes, AppManager } from './AppManager';
|
||||
import { AppManager } from './AppManager';
|
||||
import { AppComponents, AppIcons } from './types';
|
||||
|
||||
describe('generateBoundRoutes', () => {
|
||||
it('runs happy path', () => {
|
||||
const external = { myRoute: createExternalRouteRef({ id: '1' }) };
|
||||
const ref = createRouteRef({ id: 'ref-1' });
|
||||
const result = generateBoundRoutes(({ bind }) => {
|
||||
bind(external, { myRoute: ref });
|
||||
});
|
||||
|
||||
expect(result.get(external.myRoute)).toBe(ref);
|
||||
});
|
||||
|
||||
it('throws on unknown keys', () => {
|
||||
const external = { myRoute: createExternalRouteRef({ id: '2' }) };
|
||||
const ref = createRouteRef({ id: 'ref-2' });
|
||||
expect(() =>
|
||||
generateBoundRoutes(({ bind }) => {
|
||||
bind(external, { someOtherRoute: ref } as any);
|
||||
}),
|
||||
).toThrow('Key someOtherRoute is not an existing external route');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration Test', () => {
|
||||
const noOpAnalyticsApi = createApiFactory(
|
||||
analyticsApiRef,
|
||||
@@ -545,8 +523,47 @@ describe('Integration Test', () => {
|
||||
});
|
||||
expect(errorLogs).toEqual([
|
||||
expect.stringContaining(
|
||||
'Parameter :thing is duplicated in path /test/:thing/some/:thing',
|
||||
'The above error occurred in the <Provider> component',
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should throw an error when required external plugin routes are not bound', () => {
|
||||
const app = new AppManager({
|
||||
apis: [],
|
||||
defaultApis: [],
|
||||
themes: [
|
||||
{
|
||||
id: 'light',
|
||||
title: 'Light Theme',
|
||||
variant: 'light',
|
||||
Provider: ({ children }) => <>{children}</>,
|
||||
},
|
||||
],
|
||||
icons,
|
||||
plugins: [],
|
||||
components,
|
||||
configLoader: async () => [],
|
||||
});
|
||||
|
||||
const Provider = app.getProvider();
|
||||
const Router = app.getRouter();
|
||||
const { error: errorLogs } = withLogCollector(() => {
|
||||
expect(() =>
|
||||
render(
|
||||
<Provider>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/test/:thing" element={<ExposedComponent />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</Provider>,
|
||||
),
|
||||
).toThrow(
|
||||
/^External route 'extRouteRef1' of the 'blob' plugin must be bound to a target route/,
|
||||
);
|
||||
});
|
||||
expect(errorLogs).toEqual([
|
||||
expect.stringContaining(
|
||||
'The above error occurred in the <Provider> component',
|
||||
),
|
||||
|
||||
@@ -45,9 +45,6 @@ import {
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
BackstagePlugin,
|
||||
RouteRef,
|
||||
SubRouteRef,
|
||||
ExternalRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { ApiFactoryRegistry, ApiResolver } from '../apis/system';
|
||||
import {
|
||||
@@ -64,7 +61,10 @@ import {
|
||||
} from '../routing/collectors';
|
||||
import { RoutingProvider } from '../routing/RoutingProvider';
|
||||
import { RouteTracker } from '../routing/RouteTracker';
|
||||
import { validateRoutes } from '../routing/validation';
|
||||
import {
|
||||
validateRouteParameters,
|
||||
validateRouteBindings,
|
||||
} from '../routing/validation';
|
||||
import { AppContextProvider } from './AppContext';
|
||||
import { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy';
|
||||
import {
|
||||
@@ -72,13 +72,13 @@ import {
|
||||
AppConfigLoader,
|
||||
AppContext,
|
||||
AppOptions,
|
||||
AppRouteBinder,
|
||||
BackstageApp,
|
||||
SignInPageProps,
|
||||
} from './types';
|
||||
import { AppThemeProvider } from './AppThemeProvider';
|
||||
import { defaultConfigLoader } from './defaultConfigLoader';
|
||||
import { ApiRegistry } from '../apis/system/ApiRegistry';
|
||||
import { resolveRouteBindings } from './resolveRouteBindings';
|
||||
|
||||
type CompatiblePlugin =
|
||||
| BackstagePlugin<any, any>
|
||||
@@ -86,35 +86,6 @@ type CompatiblePlugin =
|
||||
output(): Array<{ type: 'feature-flag'; name: string }>;
|
||||
});
|
||||
|
||||
export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) {
|
||||
const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();
|
||||
|
||||
if (bindRoutes) {
|
||||
const bind: AppRouteBinder = (
|
||||
externalRoutes,
|
||||
targetRoutes: { [name: string]: RouteRef | SubRouteRef },
|
||||
) => {
|
||||
for (const [key, value] of Object.entries(targetRoutes)) {
|
||||
const externalRoute = externalRoutes[key];
|
||||
if (!externalRoute) {
|
||||
throw new Error(`Key ${key} is not an existing external route`);
|
||||
}
|
||||
if (!value && !externalRoute.optional) {
|
||||
throw new Error(
|
||||
`External route ${key} is required but was undefined`,
|
||||
);
|
||||
}
|
||||
if (value) {
|
||||
result.set(externalRoute, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
bindRoutes({ bind });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app base path from the configured app baseUrl.
|
||||
*
|
||||
@@ -225,39 +196,57 @@ export class AppManager implements BackstageApp {
|
||||
getProvider(): ComponentType<{}> {
|
||||
const appContext = new AppContextImpl(this);
|
||||
|
||||
// We only validate routes once
|
||||
let routesHaveBeenValidated = false;
|
||||
|
||||
const Provider = ({ children }: PropsWithChildren<{}>) => {
|
||||
const appThemeApi = useMemo(
|
||||
() => AppThemeSelector.createWithStorage(this.themes),
|
||||
[],
|
||||
);
|
||||
|
||||
const { routePaths, routeParents, routeObjects, featureFlags } =
|
||||
useMemo(() => {
|
||||
const result = traverseElementTree({
|
||||
root: children,
|
||||
discoverers: [childDiscoverer, routeElementDiscoverer],
|
||||
collectors: {
|
||||
routePaths: routePathCollector,
|
||||
routeParents: routeParentCollector,
|
||||
routeObjects: routeObjectCollector,
|
||||
collectedPlugins: pluginCollector,
|
||||
featureFlags: featureFlagCollector,
|
||||
},
|
||||
});
|
||||
const {
|
||||
routePaths,
|
||||
routeParents,
|
||||
routeObjects,
|
||||
featureFlags,
|
||||
routeBindings,
|
||||
} = useMemo(() => {
|
||||
const result = traverseElementTree({
|
||||
root: children,
|
||||
discoverers: [childDiscoverer, routeElementDiscoverer],
|
||||
collectors: {
|
||||
routePaths: routePathCollector,
|
||||
routeParents: routeParentCollector,
|
||||
routeObjects: routeObjectCollector,
|
||||
collectedPlugins: pluginCollector,
|
||||
featureFlags: featureFlagCollector,
|
||||
},
|
||||
});
|
||||
|
||||
validateRoutes(result.routePaths, result.routeParents);
|
||||
// TODO(Rugvip): Restructure the public API so that we can get an immediate view of
|
||||
// the app, rather than having to wait for the provider to render.
|
||||
// For now we need to push the additional plugins we find during
|
||||
// collection and then make sure we initialize things afterwards.
|
||||
result.collectedPlugins.forEach(plugin => this.plugins.add(plugin));
|
||||
this.verifyPlugins(this.plugins);
|
||||
|
||||
// TODO(Rugvip): Restructure the public API so that we can get an immediate view of
|
||||
// the app, rather than having to wait for the provider to render.
|
||||
// For now we need to push the additional plugins we find during
|
||||
// collection and then make sure we initialize things afterwards.
|
||||
result.collectedPlugins.forEach(plugin => this.plugins.add(plugin));
|
||||
this.verifyPlugins(this.plugins);
|
||||
// Initialize APIs once all plugins are available
|
||||
this.getApiHolder();
|
||||
return {
|
||||
...result,
|
||||
routeBindings: resolveRouteBindings(this.bindRoutes),
|
||||
};
|
||||
}, [children]);
|
||||
|
||||
// Initialize APIs once all plugins are available
|
||||
this.getApiHolder();
|
||||
return result;
|
||||
}, [children]);
|
||||
if (!routesHaveBeenValidated) {
|
||||
routesHaveBeenValidated = true;
|
||||
validateRouteParameters(routePaths, routeParents);
|
||||
validateRouteBindings(
|
||||
routeBindings,
|
||||
this.plugins as Iterable<BackstagePlugin<any, any>>,
|
||||
);
|
||||
}
|
||||
|
||||
const loadedConfig = useConfigLoader(
|
||||
this.configLoader,
|
||||
@@ -318,7 +307,7 @@ export class AppManager implements BackstageApp {
|
||||
routePaths={routePaths}
|
||||
routeParents={routeParents}
|
||||
routeObjects={routeObjects}
|
||||
routeBindings={generateBoundRoutes(this.bindRoutes)}
|
||||
routeBindings={routeBindings}
|
||||
basePath={getBasePath(loadedConfig.api)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {
|
||||
createExternalRouteRef,
|
||||
createRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { resolveRouteBindings } from './resolveRouteBindings';
|
||||
|
||||
describe('resolveRouteBindings', () => {
|
||||
it('runs happy path', () => {
|
||||
const external = { myRoute: createExternalRouteRef({ id: '1' }) };
|
||||
const ref = createRouteRef({ id: 'ref-1' });
|
||||
const result = resolveRouteBindings(({ bind }) => {
|
||||
bind(external, { myRoute: ref });
|
||||
});
|
||||
|
||||
expect(result.get(external.myRoute)).toBe(ref);
|
||||
});
|
||||
|
||||
it('throws on unknown keys', () => {
|
||||
const external = { myRoute: createExternalRouteRef({ id: '2' }) };
|
||||
const ref = createRouteRef({ id: 'ref-2' });
|
||||
expect(() =>
|
||||
resolveRouteBindings(({ bind }) => {
|
||||
bind(external, { someOtherRoute: ref } as any);
|
||||
}),
|
||||
).toThrow('Key someOtherRoute is not an existing external route');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {
|
||||
RouteRef,
|
||||
SubRouteRef,
|
||||
ExternalRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { AppOptions, AppRouteBinder } from './types';
|
||||
|
||||
export function resolveRouteBindings(bindRoutes: AppOptions['bindRoutes']) {
|
||||
const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();
|
||||
|
||||
if (bindRoutes) {
|
||||
const bind: AppRouteBinder = (
|
||||
externalRoutes,
|
||||
targetRoutes: { [name: string]: RouteRef | SubRouteRef },
|
||||
) => {
|
||||
for (const [key, value] of Object.entries(targetRoutes)) {
|
||||
const externalRoute = externalRoutes[key];
|
||||
if (!externalRoute) {
|
||||
throw new Error(`Key ${key} is not an existing external route`);
|
||||
}
|
||||
if (!value && !externalRoute.optional) {
|
||||
throw new Error(
|
||||
`External route ${key} is required but was undefined`,
|
||||
);
|
||||
}
|
||||
if (value) {
|
||||
result.set(externalRoute, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
bindRoutes({ bind });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
routeParentCollector,
|
||||
routeObjectCollector,
|
||||
} from './collectors';
|
||||
import { validateRoutes } from './validation';
|
||||
import { validateRouteParameters } from './validation';
|
||||
import { RouteResolver } from './RouteResolver';
|
||||
import { AnyRouteRef, RouteFunc } from './types';
|
||||
import { AppContextProvider } from '../app/AppContext';
|
||||
@@ -323,7 +323,7 @@ describe('discovery', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => validateRoutes(routePaths, routeParents)).toThrow(
|
||||
expect(() => validateRouteParameters(routePaths, routeParents)).toThrow(
|
||||
'Parameter :id is duplicated in path /foo/:id/bar/:id',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,9 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
BackstagePlugin,
|
||||
ExternalRouteRef,
|
||||
RouteRef,
|
||||
SubRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { AnyRouteRef } from './types';
|
||||
|
||||
export function validateRoutes(
|
||||
// Validates that there is no duplication of route parameter names
|
||||
export function validateRouteParameters(
|
||||
routePaths: Map<AnyRouteRef, string>,
|
||||
routeParents: Map<AnyRouteRef, AnyRouteRef | undefined>,
|
||||
) {
|
||||
@@ -54,3 +61,30 @@ export function validateRoutes(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validates that all non-optional external routes have been bound
|
||||
export function validateRouteBindings(
|
||||
routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>,
|
||||
plugins: Iterable<BackstagePlugin<{}, Record<string, ExternalRouteRef>>>,
|
||||
) {
|
||||
for (const plugin of plugins) {
|
||||
if (!plugin.externalRoutes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [name, externalRouteRef] of Object.entries(
|
||||
plugin.externalRoutes,
|
||||
)) {
|
||||
if (externalRouteRef.optional) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!routeBindings.has(externalRouteRef)) {
|
||||
throw new Error(
|
||||
`External route '${name}' of the '${plugin.getId()}' plugin must be bound to a target route. ` +
|
||||
'See https://backstage.io/link?bind-routes for details.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
catalogImportPlugin,
|
||||
} from '@backstage/plugin-catalog-import';
|
||||
import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
|
||||
import { orgPlugin } from '@backstage/plugin-org';
|
||||
import { SearchPage } from '@backstage/plugin-search';
|
||||
import { TechRadarPage } from '@backstage/plugin-tech-radar';
|
||||
import {
|
||||
@@ -42,6 +43,9 @@ const app = createApp({
|
||||
bind(scaffolderPlugin.externalRoutes, {
|
||||
registerComponent: catalogImportPlugin.routes.importPage,
|
||||
});
|
||||
bind(orgPlugin.externalRoutes, {
|
||||
catalogIndex: catalogPlugin.routes.catalogIndex,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -44,10 +44,7 @@ import { Member, BazaarProject } from '../../types';
|
||||
import { bazaarApiRef } from '../../api';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
||||
import {
|
||||
catalogApiRef,
|
||||
catalogRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react';
|
||||
|
||||
import {
|
||||
parseEntityName,
|
||||
@@ -85,7 +82,7 @@ export const HomePageBazaarInfoCard = ({
|
||||
initEntity,
|
||||
}: Props) => {
|
||||
const classes = useStyles();
|
||||
const catalogLink = useRouteRef(catalogRouteRef);
|
||||
const entityLink = useRouteRef(entityRouteRef);
|
||||
const bazaarApi = useApi(bazaarApiRef);
|
||||
const identity = useApi(identityApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
@@ -156,7 +153,7 @@ export const HomePageBazaarInfoCard = ({
|
||||
const { name, kind, namespace } = parseEntityName(
|
||||
bazaarProject.value.entityRef,
|
||||
);
|
||||
return `${catalogLink()}/${namespace}/${kind}/${name}`;
|
||||
return entityLink({ kind, namespace, name });
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
@@ -102,7 +102,7 @@ export type CatalogReactUserListPickerClassKey =
|
||||
|
||||
// Warning: (ae-missing-release-tag) "catalogRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export const catalogRouteRef: RouteRef<undefined>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createDomainColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -592,7 +592,7 @@ export const EntityRefLinks: ({
|
||||
|
||||
// Warning: (ae-missing-release-tag) "entityRoute" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export const entityRoute: RouteRef<{
|
||||
name: string;
|
||||
kind: string;
|
||||
@@ -610,7 +610,7 @@ export function entityRouteParams(entity: Entity): {
|
||||
|
||||
// Warning: (ae-missing-release-tag) "entityRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export const entityRouteRef: RouteRef<{
|
||||
name: string;
|
||||
kind: string;
|
||||
@@ -813,7 +813,7 @@ export function reduceEntityFilters(
|
||||
|
||||
// Warning: (ae-missing-release-tag) "rootRoute" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export const rootRoute: RouteRef<undefined>;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -16,20 +16,43 @@
|
||||
|
||||
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
|
||||
import { createRouteRef } from '@backstage/core-plugin-api';
|
||||
import { getOrCreateGlobalSingleton } from '@backstage/version-bridge';
|
||||
|
||||
// TODO(Rugvip): Move these route refs back to the catalog plugin once we're all ported to using external routes
|
||||
/**
|
||||
* @deprecated Use an `ExternalRouteRef` instead, which can point to `catalogPlugin.routes.catalogIndex`.
|
||||
*/
|
||||
export const rootRoute = createRouteRef({
|
||||
id: 'catalog',
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated Use an `ExternalRouteRef` instead, which can point to `catalogPlugin.routes.catalogIndex`.
|
||||
*/
|
||||
export const catalogRouteRef = rootRoute;
|
||||
|
||||
export const entityRoute = createRouteRef({
|
||||
id: 'catalog:entity',
|
||||
params: ['namespace', 'kind', 'name'],
|
||||
});
|
||||
/**
|
||||
* A stable route ref that points to the catalog page for an individual entity.
|
||||
*
|
||||
* This `RouteRef` can be imported and used directly, and does not need to be referenced
|
||||
* via an `ExternalRouteRef`.
|
||||
*
|
||||
* If you want to replace the `EntityPage` from `@backstage/catalog-plugin` in your app,
|
||||
* you need to use the `entityRouteRef` as the mount point instead of your own.
|
||||
*/
|
||||
export const entityRouteRef = getOrCreateGlobalSingleton(
|
||||
'catalog:entity-route-ref',
|
||||
() =>
|
||||
createRouteRef({
|
||||
id: 'catalog:entity',
|
||||
params: ['namespace', 'kind', 'name'],
|
||||
}),
|
||||
);
|
||||
|
||||
export const entityRouteRef = entityRoute;
|
||||
/**
|
||||
* @deprecated use `entityRouteRef` instead.
|
||||
*/
|
||||
export const entityRoute = entityRouteRef;
|
||||
|
||||
// Utility function to get suitable route params for entityRoute, given an
|
||||
// entity instance
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { GroupEntity } from '@backstage/catalog-model';
|
||||
import { InfoCardVariants } from '@backstage/core-components';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
@@ -74,7 +75,12 @@ export const MembersListCard: (_props: {
|
||||
// Warning: (ae-missing-release-tag) "orgPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
const orgPlugin: BackstagePlugin<{}, {}>;
|
||||
const orgPlugin: BackstagePlugin<
|
||||
{},
|
||||
{
|
||||
catalogIndex: ExternalRouteRef<undefined, false>;
|
||||
}
|
||||
>;
|
||||
export { orgPlugin };
|
||||
export { orgPlugin as plugin };
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ import { ApiProvider } from '@backstage/core-app-api';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
catalogRouteRef,
|
||||
EntityProvider,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
|
||||
@@ -31,6 +30,7 @@ import {
|
||||
} from '@backstage/theme';
|
||||
import { Grid, ThemeProvider } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { catalogIndexRouteRef } from '../../../routes';
|
||||
import { OwnershipCard } from './OwnershipCard';
|
||||
|
||||
export default {
|
||||
@@ -100,7 +100,7 @@ export const Default = () =>
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: { '/catalog': catalogRouteRef },
|
||||
mountedRoutes: { '/catalog': catalogIndexRouteRef },
|
||||
},
|
||||
);
|
||||
|
||||
@@ -134,6 +134,6 @@ export const Themed = () =>
|
||||
</ApiProvider>
|
||||
</ThemeProvider>,
|
||||
{
|
||||
mountedRoutes: { '/catalog': catalogRouteRef },
|
||||
mountedRoutes: { '/catalog': catalogIndexRouteRef },
|
||||
},
|
||||
);
|
||||
|
||||
@@ -23,11 +23,11 @@ import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
EntityProvider,
|
||||
catalogRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { queryByText } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { catalogIndexRouteRef } from '../../../routes';
|
||||
import { OwnershipCard } from './OwnershipCard';
|
||||
|
||||
const items = [
|
||||
@@ -159,7 +159,7 @@ describe('OwnershipCard', () => {
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/create': catalogRouteRef,
|
||||
'/create': catalogIndexRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -205,7 +205,7 @@ describe('OwnershipCard', () => {
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/create': catalogRouteRef,
|
||||
'/create': catalogIndexRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -236,7 +236,7 @@ describe('OwnershipCard', () => {
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/create': catalogRouteRef,
|
||||
'/create': catalogIndexRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -282,7 +282,7 @@ describe('OwnershipCard', () => {
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/create': catalogRouteRef,
|
||||
'/create': catalogIndexRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
catalogRouteRef,
|
||||
formatEntityRefTitle,
|
||||
isOwnerOf,
|
||||
useEntity,
|
||||
@@ -42,6 +41,7 @@ import qs from 'qs';
|
||||
import React from 'react';
|
||||
import pluralize from 'pluralize';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { catalogIndexRouteRef } from '../../../routes';
|
||||
|
||||
type EntityTypeProps = {
|
||||
kind: string;
|
||||
@@ -138,7 +138,7 @@ export const OwnershipCard = ({
|
||||
}) => {
|
||||
const { entity } = useEntity();
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const catalogLink = useRouteRef(catalogRouteRef);
|
||||
const catalogLink = useRouteRef(catalogIndexRouteRef);
|
||||
|
||||
const {
|
||||
loading,
|
||||
|
||||
@@ -17,9 +17,13 @@ import {
|
||||
createComponentExtension,
|
||||
createPlugin,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { catalogIndexRouteRef } from './routes';
|
||||
|
||||
export const orgPlugin = createPlugin({
|
||||
id: 'org',
|
||||
externalRoutes: {
|
||||
catalogIndex: catalogIndexRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const EntityGroupProfileCard = orgPlugin.provide(
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { createExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const catalogIndexRouteRef = createExternalRouteRef({
|
||||
id: 'catalog-index',
|
||||
});
|
||||
Reference in New Issue
Block a user