feat(techdocs): implement composable home page

Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
Phil Kuang
2021-08-05 16:37:23 -04:00
parent 10ef115554
commit 80582cbec4
18 changed files with 317 additions and 62 deletions
+7 -4
View File
@@ -23,8 +23,8 @@ import {
rootDocsRouteRef,
rootCatalogDocsRouteRef,
} from './routes';
import { TechDocsHome } from './home/components/TechDocsHome';
import { TechDocsPage } from './reader/components/TechDocsPage';
import { TechDocsIndexPage } from './home/components/TechDocsIndexPage';
import { TechDocsPage as TechDocsReaderPage } from './reader/components/TechDocsPage';
import { EntityPageDocs } from './EntityPageDocs';
import { MissingAnnotationEmptyState } from '@backstage/core-components';
@@ -33,8 +33,11 @@ const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref';
export const Router = () => {
return (
<Routes>
<Route path={`/${rootRouteRef.path}`} element={<TechDocsHome />} />
<Route path={`/${rootDocsRouteRef.path}`} element={<TechDocsPage />} />
<Route path={`/${rootRouteRef.path}`} element={<TechDocsIndexPage />} />
<Route
path={`/${rootDocsRouteRef.path}`}
element={<TechDocsReaderPage />}
/>
</Routes>
);
};
@@ -18,7 +18,7 @@ import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
import { screen } from '@testing-library/react';
import React from 'react';
import { TechDocsHome } from './TechDocsHome';
import { DefaultTechDocsHome } from './DefaultTechDocsHome';
import {
ApiProvider,
@@ -71,7 +71,7 @@ describe('TechDocs Home', () => {
it('should render a TechDocs home page', async () => {
await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<TechDocsHome />
<DefaultTechDocsHome />
</ApiProvider>,
);
@@ -35,11 +35,11 @@ import {
UserListPicker,
} from '@backstage/plugin-catalog-react';
import { EntityListDocsTable } from './EntityListDocsTable';
import { TechDocsHomeLayout } from './TechDocsHomeLayout';
import { TechDocsPageWrapper } from './TechDocsPageWrapper';
import { TechDocsPicker } from './TechDocsPicker';
import { DocsTableRow } from './types';
export const TechDocsHome = ({
export const DefaultTechDocsHome = ({
initialFilter = 'all',
columns,
actions,
@@ -49,7 +49,7 @@ export const TechDocsHome = ({
actions?: TableProps<DocsTableRow>['actions'];
}) => {
return (
<TechDocsHomeLayout>
<TechDocsPageWrapper>
<Content>
<ContentHeader title="">
<SupportButton>
@@ -70,6 +70,6 @@ export const TechDocsHome = ({
</FilteredEntityLayout>
</EntityListProvider>
</Content>
</TechDocsHomeLayout>
</TechDocsPageWrapper>
);
};
@@ -0,0 +1,82 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { renderInTestApp } from '@backstage/test-utils';
import { screen } from '@testing-library/react';
import React from 'react';
import { LegacyTechDocsHome } from './LegacyTechDocsHome';
import {
ApiProvider,
ApiRegistry,
ConfigReader,
} from '@backstage/core-app-api';
import { ConfigApi, configApiRef } from '@backstage/core-plugin-api';
jest.mock('@backstage/plugin-catalog-react', () => {
const actual = jest.requireActual('@backstage/plugin-catalog-react');
return {
...actual,
useOwnUser: () => 'test-user',
};
});
const mockCatalogApi = {
getEntityByName: jest.fn(),
getEntities: async () => ({
items: [
{
apiVersion: 'version',
kind: 'User',
metadata: {
name: 'owned',
namespace: 'default',
},
},
],
}),
} as Partial<CatalogApi>;
describe('Legacy TechDocs Home', () => {
const configApi: ConfigApi = new ConfigReader({
organization: {
name: 'My Company',
},
});
const apiRegistry = ApiRegistry.from([
[catalogApiRef, mockCatalogApi],
[configApiRef, configApi],
]);
it('should render a TechDocs home page', async () => {
await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<LegacyTechDocsHome />
</ApiProvider>,
);
// Header
expect(await screen.findByText('Documentation')).toBeInTheDocument();
expect(
await screen.findByText(/Documentation available in My Company/i),
).toBeInTheDocument();
// Explore Content
expect(await screen.findByTestId('docs-explore')).toBeDefined();
});
});
@@ -0,0 +1,55 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { PanelType, TechDocsCustomHome } from './TechDocsCustomHome';
export const LegacyTechDocsHome = () => {
const tabsConfig = [
{
label: 'Overview',
panels: [
{
title: 'Overview',
description:
'Explore your internal technical ecosystem through documentation.',
panelType: 'DocsCardGrid' as PanelType,
filterPredicate: () => true,
},
// uncomment this if you would like to have a secondary panel with owned documents
// {
// title: 'Owned',
// description: 'Explore your owned internal documentation.',
// panelType: 'DocsCardGrid' as PanelType,
// filterPredicate: 'ownedByUser',
// },
],
},
{
label: 'Owned Documents',
panels: [
{
title: 'Owned documents',
description: 'Access your documentation.',
panelType: 'DocsTable' as PanelType,
// ownedByUser filters out entities owned by signed in user
filterPredicate: 'ownedByUser',
},
],
},
];
return <TechDocsCustomHome tabsConfig={tabsConfig} />;
};
@@ -28,7 +28,7 @@ import {
import { Entity } from '@backstage/catalog-model';
import { DocsTable } from './DocsTable';
import { DocsCardGrid } from './DocsCardGrid';
import { TechDocsHomeLayout } from './TechDocsHomeLayout';
import { TechDocsPageWrapper } from './TechDocsPageWrapper';
import {
CodeSnippet,
@@ -149,17 +149,17 @@ export const TechDocsCustomHome = ({
if (loading) {
return (
<TechDocsHomeLayout>
<TechDocsPageWrapper>
<Content>
<Progress />
</Content>
</TechDocsHomeLayout>
</TechDocsPageWrapper>
);
}
if (error) {
return (
<TechDocsHomeLayout>
<TechDocsPageWrapper>
<Content>
<WarningPanel
severity="error"
@@ -168,12 +168,12 @@ export const TechDocsCustomHome = ({
<CodeSnippet language="text" text={error.toString()} />
</WarningPanel>
</Content>
</TechDocsHomeLayout>
</TechDocsPageWrapper>
);
}
return (
<TechDocsHomeLayout>
<TechDocsPageWrapper>
<HeaderTabs
selectedIndex={selectedTab}
onChange={index => setSelectedTab(index)}
@@ -192,6 +192,6 @@ export const TechDocsCustomHome = ({
/>
))}
</Content>
</TechDocsHomeLayout>
</TechDocsPageWrapper>
);
};
@@ -0,0 +1,44 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { useOutlet } from 'react-router';
import { TechDocsIndexPage } from './TechDocsIndexPage';
jest.mock('react-router', () => ({
...jest.requireActual('react-router'),
useOutlet: jest.fn().mockReturnValue('Route Children'),
}));
jest.mock('./LegacyTechDocsHome', () => ({
LegacyTechDocsHome: jest.fn().mockReturnValue('LegacyTechDocsHomeMock'),
}));
describe('TechDocsIndexPage', () => {
it('renders provided router element', async () => {
const { getByText } = await renderInTestApp(<TechDocsIndexPage />);
expect(getByText('Route Children')).toBeInTheDocument();
});
it('renders legacy TechDocs home when no router children are provided', async () => {
(useOutlet as jest.Mock).mockReturnValueOnce(null);
const { getByText } = await renderInTestApp(<TechDocsIndexPage />);
expect(getByText('LegacyTechDocsHomeMock')).toBeInTheDocument();
});
});
@@ -0,0 +1,25 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { useOutlet } from 'react-router';
import { LegacyTechDocsHome } from './LegacyTechDocsHome';
export const TechDocsIndexPage = () => {
const outlet = useOutlet();
return outlet || <LegacyTechDocsHome />;
};
@@ -23,7 +23,7 @@ type Props = {
children?: React.ReactNode;
};
export const TechDocsHomeLayout = ({ children }: Props) => {
export const TechDocsPageWrapper = ({ children }: Props) => {
const configApi = useApi(configApiRef);
const generatedSubtitle = `Documentation available in ${
configApi.getOptionalString('organization.name') ?? 'Backstage'
@@ -15,7 +15,8 @@
*/
export { EntityListDocsTable } from './EntityListDocsTable';
export { TechDocsHomeLayout } from './TechDocsHomeLayout';
export { DefaultTechDocsHome } from './DefaultTechDocsHome';
export { TechDocsPageWrapper } from './TechDocsPageWrapper';
export { TechDocsPicker } from './TechDocsPicker';
export type { PanelType } from './TechDocsCustomHome';
export type { DocsTableRow } from './types';
+3 -2
View File
@@ -21,7 +21,8 @@ export { TechDocsClient, TechDocsStorageClient } from './client';
export type { DocsTableRow, PanelType } from './home/components';
export {
EntityListDocsTable,
TechDocsHomeLayout,
DefaultTechDocsHome,
TechDocsPageWrapper,
TechDocsPicker,
} from './home/components';
export * from './components/DocsResultListItem';
@@ -30,7 +31,7 @@ export {
DocsTable,
EntityTechdocsContent,
TechDocsCustomHome,
TechDocsHome,
TechDocsIndexPage,
TechdocsPage,
techdocsPlugin as plugin,
techdocsPlugin,
+4 -2
View File
@@ -113,10 +113,12 @@ export const TechDocsCustomHome = techdocsPlugin.provide(
}),
);
export const TechDocsHome = techdocsPlugin.provide(
export const TechDocsIndexPage = techdocsPlugin.provide(
createRoutableExtension({
component: () =>
import('./home/components/TechDocsHome').then(m => m.TechDocsHome),
import('./home/components/TechDocsIndexPage').then(
m => m.TechDocsIndexPage,
),
mountPoint: rootRouteRef,
}),
);