fix(scaffolder): render TechDocs link on Template List page for TechDocs annotations

Show “View TechDocs” link when template has backstage.io/techdocs-ref or backstage.io/techdocs-entity.
Append backstage.io/techdocs-entity-path when set.
Use buildTechDocsURL from @backstage/plugin-techdocs-react.
Add tests covering both annotations and path handling.
Update dependencies to include @backstage/plugin-techdocs-common and @backstage/plugin-techdocs-react.
Add sample TechDocs scaffolding to notifications-demo template.
Closes #29076.

Signed-off-by: David Fankhänel <david.fankhaenel@aeb.com>
This commit is contained in:
David Fankhänel
2025-08-26 15:08:55 +02:00
parent a4a517d501
commit 0d415ae014
5 changed files with 150 additions and 7 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': patch
---
Render a TechDocs link on the Scaffolder Template List page when templates include either `backstage.io/techdocs-ref` or `backstage.io/techdocs-entity` annotations, using the shared `buildTechDocsURL` helper. Also adds tests to verify both annotations and optional `backstage.io/techdocs-entity-path` are respected.
+3
View File
@@ -72,6 +72,8 @@
"@backstage/plugin-permission-react": "workspace:^",
"@backstage/plugin-scaffolder-common": "workspace:^",
"@backstage/plugin-scaffolder-react": "workspace:^",
"@backstage/plugin-techdocs-common": "workspace:^",
"@backstage/plugin-techdocs-react": "workspace:^",
"@backstage/types": "workspace:^",
"@codemirror/language": "^6.0.0",
"@codemirror/legacy-modes": "^6.1.0",
@@ -109,6 +111,7 @@
"@backstage/dev-utils": "workspace:^",
"@backstage/plugin-catalog": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-techdocs": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.0.0",
@@ -27,13 +27,19 @@ import {
TestApiProvider,
mockApis,
} from '@backstage/test-utils';
import { rootRouteRef } from '../../../routes';
import { rootRouteRef, viewTechDocRouteRef } from '../../../routes';
import { TemplateListPage } from './TemplateListPage';
import {
TECHDOCS_ANNOTATION,
TECHDOCS_EXTERNAL_ANNOTATION,
TECHDOCS_EXTERNAL_PATH_ANNOTATION,
} from '@backstage/plugin-techdocs-common';
const mountedRoutes = {
mountedRoutes: {
'/': rootRouteRef,
'/catalog/:namespace/:kind/:name': entityRouteRef,
'/docs/:namespace/:kind/:name': viewTechDocRouteRef,
},
};
@@ -51,6 +57,127 @@ describe('TemplateListPage', () => {
],
});
describe('TechDocs link rendering', () => {
it('shows TechDocs link when template has backstage.io/techdocs-ref', async () => {
const mockCatalogApiWithDocs = catalogApiMock({
entities: [
{
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: {
name: 'tmpl-a',
annotations: { [TECHDOCS_ANNOTATION]: 'dir:.' },
},
spec: { type: 'service' },
},
],
});
const { findByText } = await renderInTestApp(
<TestApiProvider
apis={[
[catalogApiRef, mockCatalogApiWithDocs],
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: mockApis.storage(),
}),
],
[permissionApiRef, mockApis.permission()],
]}
>
<TemplateListPage />
</TestApiProvider>,
mountedRoutes,
);
expect(await findByText('View TechDocs')).toBeInTheDocument();
});
it('shows TechDocs link when template has backstage.io/techdocs-entity', async () => {
const mockCatalogApiWithExternal = catalogApiMock({
entities: [
{
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: {
name: 'tmpl-b',
annotations: {
[TECHDOCS_EXTERNAL_ANNOTATION]: 'component:default/other',
},
},
spec: { type: 'service' },
},
],
});
const { findByText } = await renderInTestApp(
<TestApiProvider
apis={[
[catalogApiRef, mockCatalogApiWithExternal],
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: mockApis.storage(),
}),
],
[permissionApiRef, mockApis.permission()],
]}
>
<TemplateListPage />
</TestApiProvider>,
mountedRoutes,
);
expect(await findByText('View TechDocs')).toBeInTheDocument();
});
it('appends path when backstage.io/techdocs-entity-path is set', async () => {
const mockCatalogApiWithPath = catalogApiMock({
entities: [
{
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: {
name: 'tmpl-c',
annotations: {
[TECHDOCS_EXTERNAL_ANNOTATION]: 'component:default/other',
[TECHDOCS_EXTERNAL_PATH_ANNOTATION]: '/guides/start',
},
},
spec: { type: 'service' },
},
],
});
const { findByText } = await renderInTestApp(
<TestApiProvider
apis={[
[catalogApiRef, mockCatalogApiWithPath],
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: mockApis.storage(),
}),
],
[permissionApiRef, mockApis.permission()],
]}
>
<TemplateListPage />
</TestApiProvider>,
mountedRoutes,
);
const link = (await findByText('View TechDocs')).closest('a')!;
expect(link).toHaveAttribute(
'href',
expect.stringMatching(
/\/docs\/default\/component\/other\/?(index\.html)?#?\/guides\/start|\/docs\/default\/component\/other\/guides\/start/,
),
);
});
});
it('should render the search bar for templates', async () => {
const { getByPlaceholderText } = await renderInTestApp(
<TestApiProvider
@@ -59,6 +59,11 @@ import {
useTranslationRef,
} from '@backstage/core-plugin-api/alpha';
import { scaffolderTranslationRef } from '../../../translation';
import { buildTechDocsURL } from '@backstage/plugin-techdocs-react';
import {
TECHDOCS_ANNOTATION,
TECHDOCS_EXTERNAL_ANNOTATION,
} from '@backstage/plugin-techdocs-common';
/**
* @alpha
@@ -144,18 +149,18 @@ export const TemplateListPage = (props: TemplateListPageProps) => {
const additionalLinksForEntity = useCallback(
(template: TemplateEntityV1beta3) => {
const { kind, namespace, name } = parseEntityRef(
stringifyEntityRef(template),
);
return template.metadata.annotations?.['backstage.io/techdocs-ref'] &&
viewTechDocsLink
const hasTechDocs =
!!template.metadata.annotations?.[TECHDOCS_ANNOTATION] ||
!!template.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION];
return hasTechDocs && viewTechDocsLink
? [
{
icon: app.getSystemIcon('docs') ?? DocsIcon,
text: t(
'templateListPage.additionalLinksForEntity.viewTechDocsTitle',
),
url: viewTechDocsLink({ kind, namespace, name }),
url: buildTechDocsURL(template, viewTechDocsLink),
},
]
: [];
+3
View File
@@ -6573,6 +6573,9 @@ __metadata:
"@backstage/plugin-permission-react": "workspace:^"
"@backstage/plugin-scaffolder-common": "workspace:^"
"@backstage/plugin-scaffolder-react": "workspace:^"
"@backstage/plugin-techdocs": "workspace:^"
"@backstage/plugin-techdocs-common": "workspace:^"
"@backstage/plugin-techdocs-react": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/types": "workspace:^"
"@codemirror/language": "npm:^6.0.0"