Merge pull request #28876 from johnphilip283/update-techdocs-page-title
Update techdocs page title
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Adds the page name of techdoc to the document's title.
|
||||
+15
-1
@@ -17,7 +17,10 @@
|
||||
import React from 'react';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
|
||||
import { entityRouteRef } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
entityPresentationApiRef,
|
||||
entityRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
@@ -81,6 +84,16 @@ const techdocsStorageApiMock: jest.Mocked<typeof techdocsStorageApiRef.T> = {
|
||||
syncEntityDocs: jest.fn(),
|
||||
};
|
||||
|
||||
const entityPresentationApiMock: jest.Mocked<
|
||||
typeof entityPresentationApiRef.T
|
||||
> = {
|
||||
forEntity: jest.fn().mockReturnValue({
|
||||
snapshot: {
|
||||
primaryTitle: 'Test Entity',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const fetchApiMock = {
|
||||
fetch: jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
@@ -115,6 +128,7 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
[configApiRef, configApi],
|
||||
[techdocsApiRef, techdocsApiMock],
|
||||
[techdocsStorageApiRef, techdocsStorageApiMock],
|
||||
[entityPresentationApiRef, entityPresentationApiMock],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
|
||||
+60
-5
@@ -16,7 +16,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { CompoundEntityRef } from '@backstage/catalog-model';
|
||||
import { entityRouteRef } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
entityPresentationApiRef,
|
||||
entityRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
techdocsApiRef,
|
||||
TechDocsReaderPageProvider,
|
||||
@@ -26,6 +29,7 @@ import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { rootRouteRef } from '../../../routes';
|
||||
|
||||
import { TechDocsReaderPageHeader } from './TechDocsReaderPageHeader';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
|
||||
const mockEntityMetadata = {
|
||||
locationMetadata: {
|
||||
@@ -48,6 +52,16 @@ const mockTechDocsMetadata = {
|
||||
site_description: 'test-site-desc',
|
||||
};
|
||||
|
||||
const mockUseParams = jest.fn();
|
||||
mockUseParams.mockReturnValue({ '*': 'foo/bar/baz/' });
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
return {
|
||||
...(jest.requireActual('react-router-dom') as any),
|
||||
useParams: () => mockUseParams(),
|
||||
};
|
||||
});
|
||||
|
||||
const getEntityMetadata = jest.fn();
|
||||
const getTechDocsMetadata = jest.fn();
|
||||
|
||||
@@ -56,6 +70,18 @@ const techdocsApiMock = {
|
||||
getTechDocsMetadata,
|
||||
};
|
||||
|
||||
const forEntity = jest.fn();
|
||||
|
||||
forEntity.mockReturnValue({
|
||||
snapshot: {
|
||||
primaryTitle: 'Test Entity',
|
||||
},
|
||||
});
|
||||
|
||||
const entityPresentationApiMock = {
|
||||
forEntity,
|
||||
};
|
||||
|
||||
const Wrapper = ({
|
||||
entityRef = {
|
||||
kind: mockEntityMetadata.kind,
|
||||
@@ -67,7 +93,12 @@ const Wrapper = ({
|
||||
entityRef?: CompoundEntityRef;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<TestApiProvider apis={[[techdocsApiRef, techdocsApiMock]]}>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[techdocsApiRef, techdocsApiMock],
|
||||
[entityPresentationApiRef, entityPresentationApiMock],
|
||||
]}
|
||||
>
|
||||
<TechDocsReaderPageProvider entityRef={entityRef}>
|
||||
{children}
|
||||
</TechDocsReaderPageProvider>
|
||||
@@ -96,9 +127,10 @@ describe('<TechDocsReaderPageHeader />', () => {
|
||||
expect(rendered.getAllByText('test-site-name')).toHaveLength(2);
|
||||
expect(rendered.getByText('test-site-desc')).toBeDefined();
|
||||
|
||||
expect(
|
||||
rendered.getByRole('link', { name: 'test:test-namespace/test-name' }),
|
||||
).toHaveAttribute('href', '/catalog/test-namespace/test/test-name');
|
||||
expect(rendered.getByRole('link', { name: 'Test Entity' })).toHaveAttribute(
|
||||
'href',
|
||||
'/catalog/test-namespace/test/test-name',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a techdocs page header even if metadata is not loaded', async () => {
|
||||
@@ -152,4 +184,27 @@ describe('<TechDocsReaderPageHeader />', () => {
|
||||
|
||||
expect(rendered.container.innerHTML).not.toContain('header');
|
||||
});
|
||||
|
||||
it('The header title changes depending on the url params', async () => {
|
||||
getEntityMetadata.mockResolvedValue(mockEntityMetadata);
|
||||
getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata);
|
||||
|
||||
await renderInTestApp(
|
||||
<Wrapper>
|
||||
<TechDocsReaderPageHeader />
|
||||
</Wrapper>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name/*': entityRouteRef,
|
||||
'/docs': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(document.title).toEqual(
|
||||
'Backstage | Test Entity | Foo | Bar | Baz',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+31
-3
@@ -29,17 +29,23 @@ import {
|
||||
TechDocsMetadata,
|
||||
} from '@backstage/plugin-techdocs-react';
|
||||
import {
|
||||
entityPresentationApiRef,
|
||||
EntityRefLink,
|
||||
EntityRefLinks,
|
||||
getEntityRelations,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { RELATION_OWNED_BY, CompoundEntityRef } from '@backstage/catalog-model';
|
||||
import {
|
||||
RELATION_OWNED_BY,
|
||||
CompoundEntityRef,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { Header, HeaderLabel } from '@backstage/core-components';
|
||||
import { useRouteRef, configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
import { capitalize } from 'lodash';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
|
||||
import { rootRouteRef } from '../../../routes';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
const skeleton = <Skeleton animation="wave" variant="text" height={40} />;
|
||||
|
||||
@@ -68,6 +74,9 @@ export const TechDocsReaderPageHeader = (
|
||||
const addons = useTechDocsAddons();
|
||||
const configApi = useApi(configApiRef);
|
||||
|
||||
const entityPresentationApi = useApi(entityPresentationApiRef);
|
||||
const { '*': path = '' } = useParams();
|
||||
|
||||
const {
|
||||
title,
|
||||
setTitle,
|
||||
@@ -91,7 +100,6 @@ export const TechDocsReaderPageHeader = (
|
||||
}, [metadata, setTitle, setSubtitle]);
|
||||
|
||||
const appTitle = configApi.getOptional('app.title') || 'Backstage';
|
||||
const tabTitle = [title, subtitle, appTitle].filter(Boolean).join(' | ');
|
||||
|
||||
const { locationMetadata, spec } = entityMetadata || {};
|
||||
const lifecycle = spec?.lifecycle;
|
||||
@@ -157,6 +165,26 @@ export const TechDocsReaderPageHeader = (
|
||||
const noTdMetadata = !metadataLoading && metadata === undefined;
|
||||
if (noEntMetadata || noTdMetadata) return null;
|
||||
|
||||
const stringEntityRef = stringifyEntityRef(entityRef);
|
||||
|
||||
const entityDisplayName =
|
||||
entityPresentationApi.forEntity(stringEntityRef).snapshot.primaryTitle;
|
||||
|
||||
const removeTrailingSlash = (str: string) => str.replace(/\/$/, '');
|
||||
const normalizeAndSpace = (str: string) =>
|
||||
str.replace(/-/g, ' ').split(' ').map(capitalize).join(' ');
|
||||
|
||||
let techdocsTabTitleItems: string[] = [];
|
||||
|
||||
if (path !== '')
|
||||
techdocsTabTitleItems = removeTrailingSlash(path)
|
||||
.split('/')
|
||||
.slice(0, 3)
|
||||
.map(normalizeAndSpace);
|
||||
|
||||
const tabTitleItems = [appTitle, entityDisplayName, ...techdocsTabTitleItems];
|
||||
const tabTitle = tabTitleItems.join(' | ');
|
||||
|
||||
return (
|
||||
<Header
|
||||
type="Documentation"
|
||||
|
||||
Reference in New Issue
Block a user