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:
@@ -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.
|
||||
@@ -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",
|
||||
|
||||
+128
-1
@@ -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),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user