diff --git a/.changeset/itchy-moons-start.md b/.changeset/itchy-moons-start.md
new file mode 100644
index 0000000000..e5768b58b2
--- /dev/null
+++ b/.changeset/itchy-moons-start.md
@@ -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.
diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json
index ff56a19175..df75a65656 100644
--- a/plugins/scaffolder/package.json
+++ b/plugins/scaffolder/package.json
@@ -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",
diff --git a/plugins/scaffolder/src/alpha/components/TemplateListPage/TemplateListPage.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateListPage/TemplateListPage.test.tsx
index a16474a4cf..da544cb2c0 100644
--- a/plugins/scaffolder/src/alpha/components/TemplateListPage/TemplateListPage.test.tsx
+++ b/plugins/scaffolder/src/alpha/components/TemplateListPage/TemplateListPage.test.tsx
@@ -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(
+
+
+ ,
+ 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(
+
+
+ ,
+ 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(
+
+
+ ,
+ 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(
{
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),
},
]
: [];
diff --git a/yarn.lock b/yarn.lock
index 5487f23662..aae8a0ef0f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"