docs: plugin integration into catalog

This commit is contained in:
Ivan Shmidt
2020-08-31 23:18:06 +02:00
parent e57798cd88
commit 713060d1f3
8 changed files with 154 additions and 27 deletions
+7
View File
@@ -26,3 +26,10 @@ This helps the community know what plugins are in development.
You can also use this process if you have an idea for a good plugin but you hope
that someone else will pick up the work.
## Integrate into the catalog service
If your plugin isn't supposed to live as a standalone page, but rather needs to
be presented as a part of a catalog service (e.g. a separate tab or a card on an
"Overview" tab), then check out
[the instruction](integrating-plugin-into-service-catalog.md). on how to do it.
@@ -0,0 +1,125 @@
---
id: integrating-plugin-into-service-catalog
title: Integrate into the catalog service
---
> This is an advanced use case and currently is an experimental feature. Expect
> API to change over time
## Steps
1. [Create a plugin](#create-a-plugin)
1. [Export a router with relative routes](#export-a-router)
1. [Import and use router in the APP](#import-and-use-router-in-the-app)
### Create a plugin
Follow the [same process](create-a-plugin.md) as for standalone plugin. You
should have a separate package in a folder, which represents your plugin.
Example:
```
$ yarn create-plugin
> ? Enter an ID for the plugin [required] my-plugin
> ? Enter the owner(s) of the plugin. If specified, this will be added to CODEOWNERS for the plugin path. [optional]
Creating the plugin...
```
### Export a router
Now in the plugin you have a `Router.tsx` file in the `src` folder. By default
it contains only one example route. Create a routing structure needed for your
plugin, keeping in mind that the whole set of routes defined here are going to
be mounted under some different route in the App.
Example:
`my-plugin` consists of 2 different views - `/me` and `/about`. I envision
people integrating it into plugin catalog as a tab named "MyPlugin". Then, my
`Routes.tsx` for the plugin is going to look like:
```tsx
<Routes>
<Route path="/me" element={<MePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
```
(where MePage and AboutPage are 2 components defined in your plugin and imported
accordingly inside `Router.tsx`)
> Pay attention, if your `MePage` references the `AboutPage` it needs to do it
> through link to `about`, not `/about`. This allows react-router v6 to enable
> its relative routing mechanism. Read more -
> https://reacttraining.com/blog/react-router-v6-pre/#relative-route-path-and-link-to
### Import and use router in the APP
In the `app/src/components/catalog/EntityPage.tsx` (app === your folder,
containing backstage app) import your created Router:
```tsx
import { Router as MyPluginRouter } from '@backstage/plugin-my-plugin;
```
Now, you need to mount `MyPluginRouter` onto some route, for example if you had:
```tsx
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="/"
title="Overview"
element={<OverviewPage entity={entity} />}
/>
</EntityPageLayout>
);
```
after you add your code it becomes:
```tsx
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="/"
title="Overview"
element={<OverviewPage entity={entity} />}
/>
<EntityPageLayout.Content
path="/my-plugin"
title="My Plugin"
element={<MyPluginRouter entity={entity} />}
/>
</EntityPageLayout>
);
```
All of magic happens thanks to the `EntityPageLayout` component, which comes as
an export from `@backstage/plugin-catalog` package.
```tsx
type EntityPageLayoutContentProps = {
/**
* Going to be transformed into react-router v6
* path under the hood. Read more at https://reacttraining.com/blog/react-router-v6-pre
*/
path: string;
/**
* Gets transformed into the title for the tab
*/
title: string;
/**
* Element that is rendered when the location
* matches the path provided
*/
element: JSX.Element;
};
```
> The recommended pattern is to get the `entity` in the App and then pass it to
> the plugin's Router component as a prop. However if it inconvenient for you,
> use `useEntity` hook from `@backstage/plugin-catalog` directly inside your
> plugin.
+4 -1
View File
@@ -199,7 +199,7 @@
"title": "Backend plugin"
},
"plugins/call-existing-api": {
"title": "Call existing API"
"title": "Call Existing API"
},
"plugins/create-a-plugin": {
"title": "Create a Backstage Plugin"
@@ -210,6 +210,9 @@
"plugins/index": {
"title": "Intro"
},
"plugins/integrating-plugin-into-service-catalog": {
"title": "Integrate into the catalog service"
},
"plugins/plugin-development": {
"title": "Plugin Development in Backstage"
},
+1
View File
@@ -73,6 +73,7 @@
"plugins/create-a-plugin",
"plugins/plugin-development",
"plugins/structure-of-a-plugin",
"plugins/integrating-plugin-into-service-catalog",
{
"type": "subcategory",
"label": "Backends and APIs",
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GitHubActionsPage } from '@backstage/plugin-github-actions';
import { Router as GitHubActionsRouter } from '@backstage/plugin-github-actions';
import React from 'react';
import {
EntityPageLayout,
@@ -22,7 +22,7 @@ import {
} from '@backstage/plugin-catalog';
import { Entity } from '@backstage/catalog-model';
export const OverviewPage = ({ entity }: { entity: Entity }) => (
const OverviewPage = ({ entity }: { entity: Entity }) => (
<EntityMetadataCard entity={entity} />
);
@@ -36,12 +36,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout.Content
path="/ci-cd/*"
title="CI/CD"
element={<GitHubActionsPage entity={entity} />}
/>
<EntityPageLayout.Content
path="/docs/*"
title="Documentation"
element={<div>Much docs, such wow ({entity.metadata.name})🐶</div>}
element={<GitHubActionsRouter entity={entity} />}
/>
</EntityPageLayout>
);
@@ -56,12 +51,7 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout.Content
path="/ci-cd/*"
title="CI/CD"
element={<GitHubActionsPage entity={entity} />}
/>
<EntityPageLayout.Content
path="/docs/*"
title="Documentation"
element={<div>Much docs, such wow ({entity.metadata.name})🐶</div>}
element={<GitHubActionsRouter entity={entity} />}
/>
</EntityPageLayout>
);
@@ -69,7 +59,7 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="*"
path="/*"
title="Overview"
element={<OverviewPage entity={entity} />}
/>
@@ -78,10 +78,11 @@ export const Tabbed = {
});
// Add catch-all for incorrect sub-routes
routes.push({
path: '/*',
element: <Navigate to="." />,
});
if ((routes?.[0]?.path ?? '') !== '')
routes.push({
path: '/*',
element: <Navigate to={routes[0].path!} />,
});
const [matchedRoute] =
matchRoutes(routes as RouteObject[], `/${params['*']}`) ?? [];
@@ -16,17 +16,18 @@
import React from 'react';
import { Entity } from '@backstage/catalog-model';
import { Routes, Route } from 'react-router';
import { rootRouteRef, buildRouteRef } from './plugin';
import { WorkflowRunDetails } from './components/WorkflowRunDetails';
import { WorkflowRunsTable } from './components/WorkflowRunsTable';
import { GITHUB_ACTIONS_ANNOTATION } from './components/useProjectName';
import { rootRouteRef, buildRouteRef } from '../plugin';
import { WorkflowRunDetails } from './WorkflowRunDetails';
import { WorkflowRunsTable } from './WorkflowRunsTable';
import { GITHUB_ACTIONS_ANNOTATION } from './useProjectName';
import { WarningPanel } from '@backstage/core';
const isPluginApplicableToEntity = (entity: Entity) =>
Boolean(entity?.metadata?.annotations?.[GITHUB_ACTIONS_ANNOTATION]) &&
entity?.metadata?.annotations?.[GITHUB_ACTIONS_ANNOTATION] !== '';
export const GitHubActionsPage = ({ entity }: { entity: Entity }) =>
export const Router = ({ entity }: { entity: Entity }) =>
// TODO(shmidt-i): move warning to a separate standardized component
!isPluginApplicableToEntity(entity) ? (
<WarningPanel title=" GitHubActions plugin:">
`entity.metadata.annotations['
+1 -2
View File
@@ -16,6 +16,5 @@
export { plugin } from './plugin';
export * from './api';
export { Widget } from './components/Widget';
export { GitHubActionsPage } from './Router';
export { Router } from './components/Router';
export { GITHUB_ACTIONS_ANNOTATION } from './components/useProjectName';