Skip to content

Commit 787bc08

Browse files
committed
Finish TechDocs migration to composability API.
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
1 parent 5254737 commit 787bc08

File tree

21 files changed

+293
-35
lines changed

21 files changed

+293
-35
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@backstage/plugin-catalog': patch
3+
---
4+
5+
The entity `<AboutCard />` now uses an external route ref to link to TechDocs
6+
sites. This external route must now be bound in order for the "View TechDocs"
7+
link to continue working. See the [create-app changelog][cacl] for details.
8+
9+
[cacl]: https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
'@backstage/create-app': patch
3+
---
4+
5+
Wire up TechDocs, which now relies on the composability API for routing.
6+
7+
First, ensure you've mounted `<TechDocsReaderPage />`. If you already updated
8+
to use the composable `<TechDocsIndexPage />` (see below), no action is
9+
necessary. Otherwise, update your `App.tsx` so that `<TechDocsReaderPage />` is
10+
mounted:
11+
12+
```diff
13+
<Route path="/docs" element={<TechdocsPage />} />
14+
+ <Route
15+
+ path="/docs/:namespace/:kind/:name/*"
16+
+ element={<TechDocsReaderPage />}
17+
+ />
18+
```
19+
20+
Next, ensure links from the Catalog Entity Page to its TechDocs site are bound:
21+
22+
```diff
23+
bindRoutes({ bind }) {
24+
bind(catalogPlugin.externalRoutes, {
25+
createComponent: scaffolderPlugin.routes.root,
26+
+ viewTechDoc: techdocsPlugin.routes.docRoot,
27+
});
28+
```
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@backstage/plugin-techdocs': minor
3+
---
4+
5+
The TechDocs plugin has completed the migration to the Composability API. In
6+
order to update to this version, please ensure you've made all necessary
7+
changes to your `App.tsx` file as outlined in the [create-app changelog][cacl].
8+
9+
[cacl]: https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md

packages/app/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar';
5555
import {
5656
DefaultTechDocsHome,
5757
TechDocsIndexPage,
58+
techdocsPlugin,
5859
TechDocsReaderPage,
5960
} from '@backstage/plugin-techdocs';
6061
import { UserSettingsPage } from '@backstage/plugin-user-settings';
@@ -93,6 +94,7 @@ const app = createApp({
9394
bindRoutes({ bind }) {
9495
bind(catalogPlugin.externalRoutes, {
9596
createComponent: scaffolderPlugin.routes.root,
97+
viewTechDoc: techdocsPlugin.routes.docRoot,
9698
});
9799
bind(apiDocsPlugin.externalRoutes, {
98100
createComponent: scaffolderPlugin.routes.root,

packages/create-app/templates/default-app/packages/app/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar';
1616
import {
1717
DefaultTechDocsHome,
1818
TechDocsIndexPage,
19+
techdocsPlugin,
1920
TechDocsReaderPage,
2021
} from '@backstage/plugin-techdocs';
2122
import { UserSettingsPage } from '@backstage/plugin-user-settings';
@@ -31,6 +32,7 @@ const app = createApp({
3132
bindRoutes({ bind }) {
3233
bind(catalogPlugin.externalRoutes, {
3334
createComponent: scaffolderPlugin.routes.root,
35+
viewTechDoc: techdocsPlugin.routes.docRoot,
3436
});
3537
bind(apiDocsPlugin.externalRoutes, {
3638
createComponent: scaffolderPlugin.routes.root,

plugins/catalog/api-report.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ const catalogPlugin: BackstagePlugin<
128128
},
129129
{
130130
createComponent: ExternalRouteRef<undefined, true>;
131+
viewTechDoc: ExternalRouteRef<
132+
{
133+
name: string;
134+
kind: string;
135+
namespace: string;
136+
},
137+
true
138+
>;
131139
}
132140
>;
133141
export { catalogPlugin };
@@ -399,7 +407,7 @@ export const Router: ({
399407
// src/components/EntityLayout/EntityLayout.d.ts:43:5 - (ae-forgotten-export) The symbol "EntityLayoutProps" needs to be exported by the entry point index.d.ts
400408
// src/components/EntityLayout/EntityLayout.d.ts:44:5 - (ae-forgotten-export) The symbol "SubRoute" needs to be exported by the entry point index.d.ts
401409
// src/components/EntityPageLayout/EntityPageLayout.d.ts:17:5 - (ae-forgotten-export) The symbol "EntityPageLayoutProps" needs to be exported by the entry point index.d.ts
402-
// src/plugin.d.ts:17:5 - (ae-forgotten-export) The symbol "ColumnBreakpoints" needs to be exported by the entry point index.d.ts
410+
// src/plugin.d.ts:22:5 - (ae-forgotten-export) The symbol "ColumnBreakpoints" needs to be exported by the entry point index.d.ts
403411

404412
// (No @packageDocumentation comment for this package)
405413
```

plugins/catalog/src/components/AboutCard/AboutCard.test.tsx

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
ApiRegistry,
3030
ConfigReader,
3131
} from '@backstage/core-app-api';
32+
import { viewTechDocRouteRef } from '../../routes';
3233

3334
describe('<AboutCard />', () => {
3435
it('renders info', async () => {
@@ -203,4 +204,138 @@ describe('<AboutCard />', () => {
203204
);
204205
expect(getByText('View Source').closest('a')).not.toHaveAttribute('href');
205206
});
207+
208+
it('renders techdocs link', async () => {
209+
const entity = {
210+
apiVersion: 'v1',
211+
kind: 'Component',
212+
metadata: {
213+
name: 'software',
214+
annotations: {
215+
'backstage.io/techdocs-ref': './',
216+
},
217+
},
218+
spec: {
219+
owner: 'guest',
220+
type: 'service',
221+
lifecycle: 'production',
222+
},
223+
};
224+
const apis = ApiRegistry.with(
225+
scmIntegrationsApiRef,
226+
ScmIntegrationsApi.fromConfig(
227+
new ConfigReader({
228+
integrations: {
229+
github: [
230+
{
231+
host: 'github.com',
232+
token: '...',
233+
},
234+
],
235+
},
236+
}),
237+
),
238+
);
239+
240+
const { getByText } = await renderInTestApp(
241+
<ApiProvider apis={apis}>
242+
<EntityProvider entity={entity}>
243+
<AboutCard />
244+
</EntityProvider>
245+
</ApiProvider>,
246+
{
247+
mountedRoutes: {
248+
'/docs/:namespace/:kind/:name': viewTechDocRouteRef,
249+
},
250+
},
251+
);
252+
253+
expect(getByText('View TechDocs').closest('a')).toHaveAttribute(
254+
'href',
255+
'/docs/default/Component/software',
256+
);
257+
});
258+
259+
it('renders disabled techdocs link when no docs exist', async () => {
260+
const entity = {
261+
apiVersion: 'v1',
262+
kind: 'Component',
263+
metadata: {
264+
name: 'software',
265+
},
266+
spec: {
267+
owner: 'guest',
268+
type: 'service',
269+
lifecycle: 'production',
270+
},
271+
};
272+
const apis = ApiRegistry.with(
273+
scmIntegrationsApiRef,
274+
ScmIntegrationsApi.fromConfig(
275+
new ConfigReader({
276+
integrations: {
277+
github: [
278+
{
279+
host: 'github.com',
280+
token: '...',
281+
},
282+
],
283+
},
284+
}),
285+
),
286+
);
287+
288+
const { getByText } = await renderInTestApp(
289+
<ApiProvider apis={apis}>
290+
<EntityProvider entity={entity}>
291+
<AboutCard />
292+
</EntityProvider>
293+
</ApiProvider>,
294+
);
295+
296+
expect(getByText('View TechDocs').closest('a')).not.toHaveAttribute('href');
297+
});
298+
299+
it('renders disbaled techdocs link when route is not bound', async () => {
300+
const entity = {
301+
apiVersion: 'v1',
302+
kind: 'Component',
303+
metadata: {
304+
name: 'software',
305+
annotations: {
306+
'backstage.io/techdocs-ref': './',
307+
},
308+
},
309+
spec: {
310+
owner: 'guest',
311+
type: 'service',
312+
lifecycle: 'production',
313+
},
314+
};
315+
const apis = ApiRegistry.with(
316+
scmIntegrationsApiRef,
317+
ScmIntegrationsApi.fromConfig(
318+
new ConfigReader({
319+
integrations: {
320+
github: [
321+
{
322+
host: 'github.com',
323+
token: '...',
324+
},
325+
],
326+
},
327+
}),
328+
),
329+
);
330+
331+
const { getByText } = await renderInTestApp(
332+
<ApiProvider apis={apis}>
333+
<EntityProvider entity={entity}>
334+
<AboutCard />
335+
</EntityProvider>
336+
</ApiProvider>,
337+
);
338+
339+
expect(getByText('View TechDocs').closest('a')).not.toHaveAttribute('href');
340+
});
206341
});

plugins/catalog/src/components/AboutCard/AboutCard.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ import {
4949
IconLinkVerticalProps,
5050
InfoCardVariants,
5151
} from '@backstage/core-components';
52-
import { useApi } from '@backstage/core-plugin-api';
52+
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
53+
import { viewTechDocRouteRef } from '../../routes';
5354

5455
const useStyles = makeStyles({
5556
gridItemCard: {
@@ -81,6 +82,8 @@ export function AboutCard({ variant }: AboutCardProps) {
8182
const classes = useStyles();
8283
const { entity } = useEntity();
8384
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
85+
const viewTechdocLink = useRouteRef(viewTechDocRouteRef);
86+
8487
const entitySourceLocation = getEntitySourceLocation(
8588
entity,
8689
scmIntegrationsApi,
@@ -105,11 +108,17 @@ export function AboutCard({ variant }: AboutCardProps) {
105108
};
106109
const viewInTechDocs: IconLinkVerticalProps = {
107110
label: 'View TechDocs',
108-
disabled: !entity.metadata.annotations?.['backstage.io/techdocs-ref'],
111+
disabled:
112+
!entity.metadata.annotations?.['backstage.io/techdocs-ref'] ||
113+
!viewTechdocLink,
109114
icon: <DocsIcon />,
110-
href: `/docs/${entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE}/${
111-
entity.kind
112-
}/${entity.metadata.name}`,
115+
href:
116+
viewTechdocLink &&
117+
viewTechdocLink({
118+
namespace: entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE,
119+
kind: entity.kind,
120+
name: entity.metadata.name,
121+
}),
113122
};
114123
const viewApi: IconLinkVerticalProps = {
115124
title: hasApis ? '' : 'No APIs available',

plugins/catalog/src/plugin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
entityRouteRef,
2222
} from '@backstage/plugin-catalog-react';
2323
import { CatalogClientWrapper } from './CatalogClientWrapper';
24-
import { createComponentRouteRef } from './routes';
24+
import { createComponentRouteRef, viewTechDocRouteRef } from './routes';
2525
import {
2626
createApiFactory,
2727
createComponentExtension,
@@ -50,6 +50,7 @@ export const catalogPlugin = createPlugin({
5050
},
5151
externalRoutes: {
5252
createComponent: createComponentRouteRef,
53+
viewTechDoc: viewTechDocRouteRef,
5354
},
5455
});
5556

plugins/catalog/src/routes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ export const createComponentRouteRef = createExternalRouteRef({
2020
id: 'create-component',
2121
optional: true,
2222
});
23+
24+
export const viewTechDocRouteRef = createExternalRouteRef({
25+
id: 'view-techdoc',
26+
optional: true,
27+
params: ['namespace', 'kind', 'name'],
28+
});

0 commit comments

Comments
 (0)