diff --git a/docs/plugins/index.md b/docs/plugins/index.md
index bb8951f959..857b8f7fcf 100644
--- a/docs/plugins/index.md
+++ b/docs/plugins/index.md
@@ -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.
diff --git a/docs/plugins/integrating-plugin-into-service-catalog.md b/docs/plugins/integrating-plugin-into-service-catalog.md
new file mode 100644
index 0000000000..7819737c52
--- /dev/null
+++ b/docs/plugins/integrating-plugin-into-service-catalog.md
@@ -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
+
+ } />
+ } />
+
+```
+
+(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 }) => (
+
+ }
+ />
+
+);
+```
+
+after you add your code it becomes:
+
+```tsx
+const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
+
+ }
+ />
+ }
+ />
+
+);
+```
+
+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.
diff --git a/microsite/i18n/en.json b/microsite/i18n/en.json
index d2593a13ce..1f04f7430b 100644
--- a/microsite/i18n/en.json
+++ b/microsite/i18n/en.json
@@ -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"
},
diff --git a/microsite/sidebars.json b/microsite/sidebars.json
index b07f326086..7576296b7a 100644
--- a/microsite/sidebars.json
+++ b/microsite/sidebars.json
@@ -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",
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 4368d8e26b..1c7a08e362 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -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 }) => (
);
@@ -36,12 +36,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
}
- />
- Much docs, such wow ({entity.metadata.name})🐶}
+ element={}
/>
);
@@ -56,12 +51,7 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
}
- />
- Much docs, such wow ({entity.metadata.name})🐶}
+ element={}
/>
);
@@ -69,7 +59,7 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
}
/>
diff --git a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx b/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx
index 6f3e5e1fcf..ad0b9d067c 100644
--- a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx
+++ b/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx
@@ -78,10 +78,11 @@ export const Tabbed = {
});
// Add catch-all for incorrect sub-routes
- routes.push({
- path: '/*',
- element: ,
- });
+ if ((routes?.[0]?.path ?? '') !== '')
+ routes.push({
+ path: '/*',
+ element: ,
+ });
const [matchedRoute] =
matchRoutes(routes as RouteObject[], `/${params['*']}`) ?? [];
diff --git a/plugins/github-actions/src/Router.tsx b/plugins/github-actions/src/components/Router.tsx
similarity index 80%
rename from plugins/github-actions/src/Router.tsx
rename to plugins/github-actions/src/components/Router.tsx
index c508302e6d..80637b75f0 100644
--- a/plugins/github-actions/src/Router.tsx
+++ b/plugins/github-actions/src/components/Router.tsx
@@ -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) ? (
`entity.metadata.annotations['
diff --git a/plugins/github-actions/src/index.ts b/plugins/github-actions/src/index.ts
index 0bb5862d8b..7f8b968c16 100644
--- a/plugins/github-actions/src/index.ts
+++ b/plugins/github-actions/src/index.ts
@@ -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';