refactor(techdocs): rework TechDocsHome to use EntityListProvider

Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
Phil Kuang
2021-07-13 17:16:51 -04:00
parent c7be91b571
commit a440d3b389
24 changed files with 451 additions and 120 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': minor
---
Rework `TechDocsHome` to use `EntityListProvider` with support for starring docs and filtering on owned, starred, owner, and tags.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-catalog': patch
'@backstage/plugin-catalog-react': patch
---
Move and rename `FavoriteEntity` component to `catalog-react`
+26 -2
View File
@@ -10,9 +10,11 @@ import { AsyncState } from 'react-use/lib/useAsync';
import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
import { CatalogApi } from '@backstage/catalog-client';
import { ComponentEntity } from '@backstage/catalog-model';
import { ComponentProps } from 'react';
import { Context } from 'react';
import { Entity } from '@backstage/catalog-model';
import { EntityName } from '@backstage/catalog-model';
import { IconButton } from '@material-ui/core';
import { LinkProps } from '@backstage/core-components';
import { PropsWithChildren } from 'react';
import { default as React_2 } from 'react';
@@ -116,7 +118,10 @@ export const EntityContext: Context<EntityLoadingStatus>;
//
// @public (undocumented)
export type EntityFilter = {
getCatalogFilters?: () => Record<string, string | string[]>;
getCatalogFilters?: () => Record<
string,
string | symbol | (string | symbol)[]
>;
filterEntity?: (entity: Entity) => boolean;
toQueryValue?: () => string | string[];
};
@@ -618,6 +623,25 @@ export class EntityTypeFilter implements EntityFilter {
// @public (undocumented)
export const EntityTypePicker: () => JSX.Element | null;
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "FavoriteEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export const FavoriteEntity: (props: Props_2) => JSX.Element;
// Warning: (ae-missing-release-tag) "favoriteEntityIcon" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const favoriteEntityIcon: (isStarred: boolean) => JSX.Element;
// Warning: (ae-missing-release-tag) "favoriteEntityTooltip" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const favoriteEntityTooltip: (
isStarred: boolean,
) => 'Remove from favorites' | 'Add to favorites';
// Warning: (ae-missing-release-tag) "formatEntityRefTitle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -677,7 +701,7 @@ export const MockEntityListContextProvider: ({
// @public (undocumented)
export function reduceCatalogFilters(
filters: EntityFilter[],
): Record<string, string | string[]>;
): Record<string, string | symbol | (string | symbol)[]>;
// Warning: (ae-missing-release-tag) "reduceEntityFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -15,7 +15,7 @@
*/
import React, { ComponentProps } from 'react';
import { useStarredEntities } from '@backstage/plugin-catalog-react';
import { useStarredEntities } from '../../hooks/useStarredEntities';
import { IconButton, Tooltip, withStyles } from '@material-ui/core';
import StarBorder from '@material-ui/icons/StarBorder';
import Star from '@material-ui/icons/Star';
@@ -29,17 +29,17 @@ const YellowStar = withStyles({
},
})(Star);
export const favouriteEntityTooltip = (isStarred: boolean) =>
export const favoriteEntityTooltip = (isStarred: boolean) =>
isStarred ? 'Remove from favorites' : 'Add to favorites';
export const favouriteEntityIcon = (isStarred: boolean) =>
export const favoriteEntityIcon = (isStarred: boolean) =>
isStarred ? <YellowStar /> : <StarBorder />;
/**
* IconButton for showing if a current entity is starred and adding/removing it from the favourite entities
* IconButton for showing if a current entity is starred and adding/removing it from the favorite entities
* @param props MaterialUI IconButton props extended by required `entity` prop
*/
export const FavouriteEntity = (props: Props) => {
export const FavoriteEntity = (props: Props) => {
const { toggleStarredEntity, isStarredEntity } = useStarredEntities();
const isStarred = isStarredEntity(props.entity);
return (
@@ -48,8 +48,8 @@ export const FavouriteEntity = (props: Props) => {
{...props}
onClick={() => toggleStarredEntity(props.entity)}
>
<Tooltip title={favouriteEntityTooltip(isStarred)}>
{favouriteEntityIcon(isStarred)}
<Tooltip title={favoriteEntityTooltip(isStarred)}>
{favoriteEntityIcon(isStarred)}
</Tooltip>
</IconButton>
);
@@ -0,0 +1,21 @@
/*
* 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.
*/
export {
favoriteEntityTooltip,
favoriteEntityIcon,
FavoriteEntity,
} from './FavoriteEntity';
@@ -22,4 +22,5 @@ export * from './EntitySearchBar';
export * from './EntityTable';
export * from './EntityTagPicker';
export * from './EntityTypePicker';
export * from './FavoriteEntity';
export * from './UserListPicker';
+4 -1
View File
@@ -23,7 +23,10 @@ export type EntityFilter = {
* { field: 'kind', values: ['component'] }
* { field: 'metadata.name', values: ['component-1', 'component-2'] }
*/
getCatalogFilters?: () => Record<string, string | string[]>;
getCatalogFilters?: () => Record<
string,
string | symbol | (string | symbol)[]
>;
/**
* Filter entities on the frontend after a catalog-backend request. This function will be called
+2 -2
View File
@@ -19,13 +19,13 @@ import { EntityFilter } from '../types';
export function reduceCatalogFilters(
filters: EntityFilter[],
): Record<string, string | string[]> {
): Record<string, string | symbol | (string | symbol)[]> {
return filters.reduce((compoundFilter, filter) => {
return {
...compoundFilter,
...(filter.getCatalogFilters ? filter.getCatalogFilters() : {}),
};
}, {} as Record<string, string | string[]>);
}, {} as Record<string, string | symbol | (string | symbol)[]>);
}
export function reduceEntityFilters(
@@ -15,6 +15,8 @@
*/
import { RELATION_OWNED_BY, RELATION_PART_OF } from '@backstage/catalog-model';
import {
favoriteEntityIcon,
favoriteEntityTooltip,
formatEntityRefTitle,
getEntityMetadataEditUrl,
getEntityMetadataViewUrl,
@@ -26,10 +28,6 @@ import Edit from '@material-ui/icons/Edit';
import OpenInNew from '@material-ui/icons/OpenInNew';
import { capitalize } from 'lodash';
import React from 'react';
import {
favouriteEntityIcon,
favouriteEntityTooltip,
} from '../FavouriteEntity/FavouriteEntity';
import * as columnFactories from './columns';
import { EntityRow } from './types';
import {
@@ -105,8 +103,8 @@ export const CatalogTable = ({ columns, actions }: CatalogTableProps) => {
const isStarred = isStarredEntity(entity);
return {
cellStyle: { paddingLeft: '1em' },
icon: () => favouriteEntityIcon(isStarred),
tooltip: favouriteEntityTooltip(isStarred),
icon: () => favoriteEntityIcon(isStarred),
tooltip: favoriteEntityTooltip(isStarred),
onClick: () => toggleStarredEntity(entity),
};
},
@@ -35,6 +35,7 @@ import {
import {
EntityContext,
EntityRefLinks,
FavoriteEntity,
getEntityRelations,
useEntityCompoundName,
} from '@backstage/plugin-catalog-react';
@@ -43,7 +44,6 @@ import { Alert } from '@material-ui/lab';
import React, { useContext, useState } from 'react';
import { useNavigate } from 'react-router';
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
type SubRoute = {
@@ -79,7 +79,7 @@ const EntityLayoutTitle = ({
>
{title}
</Box>
{entity && <FavouriteEntity entity={entity} />}
{entity && <FavoriteEntity entity={entity} />}
</Box>
);
};
@@ -21,6 +21,7 @@ import {
import {
EntityContext,
EntityRefLinks,
FavoriteEntity,
getEntityRelations,
useEntityCompoundName,
} from '@backstage/plugin-catalog-react';
@@ -28,7 +29,6 @@ import { Box } from '@material-ui/core';
import React, { useContext, useState } from 'react';
import { useNavigate } from 'react-router';
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
import { Tabbed } from './Tabbed';
@@ -54,7 +54,7 @@ const EntityPageTitle = ({
}) => (
<Box display="inline-flex" alignItems="center" height="1em">
{title}
{entity && <FavouriteEntity entity={entity} />}
{entity && <FavoriteEntity entity={entity} />}
</Box>
);
+35 -3
View File
@@ -5,6 +5,7 @@
```ts
/// <reference types="react" />
import { Action } from '@material-table/core';
import { ApiRef } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { Config } from '@backstage/config';
@@ -14,6 +15,7 @@ import { Entity } from '@backstage/catalog-model';
import { EntityName } from '@backstage/catalog-model';
import { IdentityApi } from '@backstage/core-plugin-api';
import { LocationSpec } from '@backstage/catalog-model';
import { default as React_2 } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
// Warning: (ae-missing-release-tag) "DocsCardGrid" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -42,16 +44,34 @@ export const DocsResultListItem: ({
export const DocsTable: ({
entities,
title,
loading,
actions,
}: {
entities: Entity[] | undefined;
title?: string | undefined;
loading?: boolean | undefined;
actions?:
| (
| Action<DocsTableRow>
| {
action: (rowData: DocsTableRow) => Action<DocsTableRow>;
position: string;
}
| ((rowData: DocsTableRow) => Action<DocsTableRow>)
)[]
| undefined;
}) => JSX.Element | null;
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "EmbeddedDocsRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const EmbeddedDocsRouter: (_props: Props) => JSX.Element;
export const EmbeddedDocsRouter: (_props: Props_2) => JSX.Element;
// Warning: (ae-missing-release-tag) "EntityListDocsTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const EntityListDocsTable: () => JSX.Element;
// Warning: (ae-missing-release-tag) "EntityTechdocsContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -69,7 +89,7 @@ export type PanelType = 'DocsCardGrid' | 'DocsTable';
// Warning: (ae-missing-release-tag) "Reader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const Reader: ({ entityId, onReady }: Props_2) => JSX.Element;
export const Reader: ({ entityId, onReady }: Props_3) => JSX.Element;
// Warning: (ae-missing-release-tag) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -143,11 +163,22 @@ export const TechDocsCustomHome: ({
tabsConfig: TabsConfig;
}) => JSX.Element;
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "TechDocsHomeLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const TechDocsHomeLayout: ({ children }: Props) => JSX.Element;
// Warning: (ae-missing-release-tag) "TechdocsPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const TechdocsPage: () => JSX.Element;
// Warning: (ae-missing-release-tag) "TechDocsPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const TechDocsPicker: () => null;
// Warning: (ae-missing-release-tag) "techdocsPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -255,7 +286,8 @@ export class TechDocsStorageClient implements TechDocsStorageApi {
// Warnings were encountered during analysis:
//
// src/plugin.d.ts:18:5 - (ae-forgotten-export) The symbol "TabsConfig" needs to be exported by the entry point index.d.ts
// src/plugin.d.ts:17:5 - (ae-forgotten-export) The symbol "DocsTableRow" needs to be exported by the entry point index.d.ts
// src/plugin.d.ts:23:5 - (ae-forgotten-export) The symbol "TabsConfig" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)
```
+3
View File
@@ -38,14 +38,17 @@
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.9",
"@backstage/integration-react": "^0.1.6",
"@backstage/plugin-catalog": "^0.6.10",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@material-ui/styles": "^4.10.0",
"@types/react": "^16.9",
"dompurify": "^2.2.9",
"event-source-polyfill": "^1.0.25",
"lodash": "^4.17.21",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-lazylog": "^4.5.2",
@@ -18,24 +18,29 @@ import React from 'react';
import { useCopyToClipboard } from 'react-use';
import { generatePath } from 'react-router-dom';
import { IconButton, Tooltip } from '@material-ui/core';
import ShareIcon from '@material-ui/icons/Share';
import { Entity } from '@backstage/catalog-model';
import { rootDocsRouteRef } from '../../routes';
import {
Table,
EmptyState,
Button,
SubvalueCell,
EmptyState,
Link,
SubvalueCell,
Table,
TableProps,
} from '@backstage/core-components';
import * as actionFactories from './actions';
import { DocsTableRow } from './types';
export const DocsTable = ({
entities,
title,
loading,
actions,
}: {
entities: Entity[] | undefined;
title?: string | undefined;
loading?: boolean | undefined;
actions?: TableProps<DocsTableRow>['actions'];
}) => {
const [, copyToClipboard] = useCopyToClipboard();
@@ -43,15 +48,14 @@ export const DocsTable = ({
const documents = entities.map(entity => {
return {
name: entity.metadata.name,
description: entity.metadata.description,
owner: entity?.spec?.owner,
type: entity?.spec?.type,
docsUrl: generatePath(rootDocsRouteRef.path, {
namespace: entity.metadata.namespace ?? 'default',
kind: entity.kind,
name: entity.metadata.name,
}),
entity,
resolved: {
docsUrl: generatePath(rootDocsRouteRef.path, {
namespace: entity.metadata.namespace ?? 'default',
kind: entity.kind,
name: entity.metadata.name,
}),
},
};
});
@@ -60,49 +64,43 @@ export const DocsTable = ({
title: 'Document',
field: 'name',
highlight: true,
render: (row: any): React.ReactNode => (
render: (row: DocsTableRow): React.ReactNode => (
<SubvalueCell
value={<Link to={row.docsUrl}>{row.name}</Link>}
subvalue={row.description}
value={
<Link to={row.resolved.docsUrl}>{row.entity.metadata.name}</Link>
}
subvalue={row.entity.metadata.description}
/>
),
},
{
title: 'Owner',
field: 'owner',
field: 'entity.spec.owner',
},
{
title: 'Type',
field: 'type',
},
{
title: 'Actions',
width: '10%',
render: (row: any) => (
<Tooltip title="Click to copy documentation link to clipboard">
<IconButton
onClick={() =>
copyToClipboard(`${window.location.href}/${row.docsUrl}`)
}
>
<ShareIcon />
</IconButton>
</Tooltip>
),
field: 'entity.spec.type',
},
];
const defaultActions: TableProps<DocsTableRow>['actions'] = [
actionFactories.createCopyDocsUrlAction(copyToClipboard),
];
return (
<>
{documents && documents.length > 0 ? (
<Table
{loading || (documents && documents.length > 0) ? (
<Table<DocsTableRow>
isLoading={loading}
options={{
paging: true,
pageSize: 20,
search: true,
actionsColumnIndex: -1,
}}
data={documents}
columns={columns}
actions={actions || defaultActions}
title={
title
? `${title} (${documents.length})`
@@ -0,0 +1,70 @@
/*
* 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 { useCopyToClipboard } from 'react-use';
import { capitalize } from 'lodash';
import { CodeSnippet, WarningPanel } from '@backstage/core-components';
import {
favoriteEntityIcon,
favoriteEntityTooltip,
useEntityListProvider,
useStarredEntities,
} from '@backstage/plugin-catalog-react';
import * as actionFactories from './actions';
import { DocsTable } from './DocsTable';
import { DocsTableRow } from './types';
export const EntityListDocsTable = () => {
const { loading, error, entities, filters } = useEntityListProvider();
const { isStarredEntity, toggleStarredEntity } = useStarredEntities();
const [, copyToClipboard] = useCopyToClipboard();
const title = capitalize(filters.user?.value ?? 'all');
const actions = [
actionFactories.createCopyDocsUrlAction(copyToClipboard),
({ entity }: DocsTableRow) => {
const isStarred = isStarredEntity(entity);
return {
cellStyle: { paddingLeft: '1em' },
icon: () => favoriteEntityIcon(isStarred),
tooltip: favoriteEntityTooltip(isStarred),
onClick: () => toggleStarredEntity(entity),
};
},
];
if (error) {
return (
<WarningPanel
severity="error"
title="Could not load available documentation."
>
<CodeSnippet language="text" text={error.toString()} />
</WarningPanel>
);
}
return (
<DocsTable
title={title}
entities={entities}
loading={loading}
actions={actions}
/>
);
};
@@ -28,20 +28,19 @@ import {
import { Entity } from '@backstage/catalog-model';
import { DocsTable } from './DocsTable';
import { DocsCardGrid } from './DocsCardGrid';
import { TechDocsHomeLayout } from './TechDocsHomeLayout';
import {
CodeSnippet,
Content,
Header,
HeaderTabs,
Page,
Progress,
WarningPanel,
SupportButton,
ContentHeader,
} from '@backstage/core-components';
import { ConfigApi, configApiRef, useApi } from '@backstage/core-plugin-api';
import { useApi } from '@backstage/core-plugin-api';
const panels = {
DocsTable: DocsTable,
@@ -122,7 +121,6 @@ export const TechDocsCustomHome = ({
}) => {
const [selectedTab, setSelectedTab] = useState<number>(0);
const catalogApi: CatalogApi = useApi(catalogApiRef);
const configApi: ConfigApi = useApi(configApiRef);
const {
value: entities,
@@ -147,27 +145,21 @@ export const TechDocsCustomHome = ({
});
});
const generatedSubtitle = `Documentation available in ${
configApi.getOptionalString('organization.name') ?? 'Backstage'
}`;
const currentTabConfig = tabsConfig[selectedTab];
if (loading) {
return (
<Page themeId="documentation">
<Header title="Documentation" subtitle={generatedSubtitle} />
<TechDocsHomeLayout>
<Content>
<Progress />
</Content>
</Page>
</TechDocsHomeLayout>
);
}
if (error) {
return (
<Page themeId="documentation">
<Header title="Documentation" subtitle={generatedSubtitle} />
<TechDocsHomeLayout>
<Content>
<WarningPanel
severity="error"
@@ -176,13 +168,12 @@ export const TechDocsCustomHome = ({
<CodeSnippet language="text" text={error.toString()} />
</WarningPanel>
</Content>
</Page>
</TechDocsHomeLayout>
);
}
return (
<Page themeId="documentation">
<Header title="Documentation" subtitle={generatedSubtitle} />
<TechDocsHomeLayout>
<HeaderTabs
selectedIndex={selectedTab}
onChange={index => setSelectedTab(index)}
@@ -201,6 +192,6 @@ export const TechDocsCustomHome = ({
/>
))}
</Content>
</Page>
</TechDocsHomeLayout>
);
};
@@ -15,7 +15,7 @@
*/
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { renderInTestApp } from '@backstage/test-utils';
import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
import { screen } from '@testing-library/react';
import React from 'react';
import { TechDocsHome } from './TechDocsHome';
@@ -25,7 +25,11 @@ import {
ApiRegistry,
ConfigReader,
} from '@backstage/core-app-api';
import { ConfigApi, configApiRef } from '@backstage/core-plugin-api';
import {
ConfigApi,
configApiRef,
storageApiRef,
} from '@backstage/core-plugin-api';
jest.mock('@backstage/plugin-catalog-react', () => {
const actual = jest.requireActual('@backstage/plugin-catalog-react');
@@ -36,7 +40,7 @@ jest.mock('@backstage/plugin-catalog-react', () => {
});
const mockCatalogApi = {
getEntityByName: jest.fn(),
getEntityByName: () => Promise.resolve(),
getEntities: async () => ({
items: [
{
@@ -61,6 +65,7 @@ describe('TechDocs Home', () => {
const apiRegistry = ApiRegistry.from([
[catalogApiRef, mockCatalogApi],
[configApiRef, configApi],
[storageApiRef, MockStorageApi.create()],
]);
it('should render a TechDocs home page', async () => {
@@ -75,8 +80,5 @@ describe('TechDocs Home', () => {
expect(
await screen.findByText(/Documentation available in My Company/i),
).toBeInTheDocument();
// Explore Content
expect(await screen.findByTestId('docs-explore')).toBeDefined();
});
});
@@ -15,41 +15,49 @@
*/
import React from 'react';
import { PanelType, TechDocsCustomHome } from './TechDocsCustomHome';
import {
Content,
ContentHeader,
SupportButton,
} from '@backstage/core-components';
import {
EntityListContainer,
FilterContainer,
FilteredEntityLayout,
} from '@backstage/plugin-catalog';
import {
EntityListProvider,
EntityOwnerPicker,
EntityTagPicker,
UserListPicker,
} from '@backstage/plugin-catalog-react';
import { EntityListDocsTable } from './EntityListDocsTable';
import { TechDocsHomeLayout } from './TechDocsHomeLayout';
import { TechDocsPicker } from './TechDocsPicker';
export const TechDocsHome = () => {
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} />;
return (
<TechDocsHomeLayout>
<Content>
<ContentHeader title="">
<SupportButton>
Discover documentation in your ecosystem.
</SupportButton>
</ContentHeader>
<EntityListProvider>
<FilteredEntityLayout>
<FilterContainer>
<TechDocsPicker />
<UserListPicker initialFilter="all" />
<EntityOwnerPicker />
<EntityTagPicker />
</FilterContainer>
<EntityListContainer>
<EntityListDocsTable />
</EntityListContainer>
</FilteredEntityLayout>
</EntityListProvider>
</Content>
</TechDocsHomeLayout>
);
};
@@ -0,0 +1,41 @@
/*
* 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 { PageWithHeader } from '@backstage/core-components';
import { useApi, configApiRef } from '@backstage/core-plugin-api';
type Props = {
children?: React.ReactNode;
};
export const TechDocsHomeLayout = ({ children }: Props) => {
const configApi = useApi(configApiRef);
const generatedSubtitle = `Documentation available in ${
configApi.getOptionalString('organization.name') ?? 'Backstage'
}`;
return (
<PageWithHeader
title="Documentation"
subtitle={generatedSubtitle}
themeId="documentation"
>
{children}
</PageWithHeader>
);
};
@@ -0,0 +1,47 @@
/*
* 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 { useEffect } from 'react';
import {
CATALOG_FILTER_EXISTS,
DefaultEntityFilters,
EntityFilter,
useEntityListProvider,
} from '@backstage/plugin-catalog-react';
class TechDocsFilter implements EntityFilter {
getCatalogFilters(): Record<string, string | symbol | (string | symbol)[]> {
return {
'metadata.annotations.backstage.io/techdocs-ref': CATALOG_FILTER_EXISTS,
};
}
}
type CustomFilters = DefaultEntityFilters & {
techdocs?: TechDocsFilter;
};
export const TechDocsPicker = () => {
const { updateFilters } = useEntityListProvider<CustomFilters>();
useEffect(() => {
updateFilters({
techdocs: new TechDocsFilter(),
});
}, [updateFilters]);
return null;
};
@@ -0,0 +1,32 @@
/*
* 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 ShareIcon from '@material-ui/icons/Share';
import { DocsTableRow } from './types';
export function createCopyDocsUrlAction(copyToClipboard: Function) {
return (row: DocsTableRow) => {
return {
icon: () => <ShareIcon fontSize="small" />,
tooltip: 'Click to copy documentation link to clipboard',
onClick: () =>
copyToClipboard(
`${window.location.href.split('/?')[0]}/${row.resolved.docsUrl}`,
),
};
};
}
@@ -0,0 +1,20 @@
/*
* 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.
*/
export { EntityListDocsTable } from './EntityListDocsTable';
export { TechDocsHomeLayout } from './TechDocsHomeLayout';
export { TechDocsPicker } from './TechDocsPicker';
export type { PanelType } from './TechDocsCustomHome';
@@ -0,0 +1,24 @@
/*
* 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 { Entity } from '@backstage/catalog-model';
export type DocsTableRow = {
entity: Entity;
resolved: {
docsUrl: string;
};
};
+6 -1
View File
@@ -18,7 +18,12 @@ export * from './api';
export { techdocsApiRef, techdocsStorageApiRef } from './api';
export type { TechDocsApi, TechDocsStorageApi } from './api';
export { TechDocsClient, TechDocsStorageClient } from './client';
export type { PanelType } from './home/components/TechDocsCustomHome';
export type { PanelType } from './home/components';
export {
EntityListDocsTable,
TechDocsHomeLayout,
TechDocsPicker,
} from './home/components';
export * from './components/DocsResultListItem';
export {
DocsCardGrid,