refactor(EntityListDocsTable): support custom columns and actions

Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
Phil Kuang
2021-08-04 10:01:00 -04:00
parent a440d3b389
commit 10ef115554
10 changed files with 251 additions and 51 deletions
@@ -18,28 +18,34 @@ import React from 'react';
import { useCopyToClipboard } from 'react-use';
import { generatePath } from 'react-router-dom';
import { Entity } from '@backstage/catalog-model';
import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';
import {
formatEntityRefTitle,
getEntityRelations,
} from '@backstage/plugin-catalog-react';
import { rootDocsRouteRef } from '../../routes';
import {
Button,
EmptyState,
Link,
SubvalueCell,
Table,
TableColumn,
TableProps,
} from '@backstage/core-components';
import * as actionFactories from './actions';
import * as columnFactories from './columns';
import { DocsTableRow } from './types';
export const DocsTable = ({
entities,
title,
loading,
columns,
actions,
}: {
entities: Entity[] | undefined;
title?: string | undefined;
loading?: boolean | undefined;
columns?: TableColumn<DocsTableRow>[];
actions?: TableProps<DocsTableRow>['actions'];
}) => {
const [, copyToClipboard] = useCopyToClipboard();
@@ -47,6 +53,8 @@ export const DocsTable = ({
if (!entities) return null;
const documents = entities.map(entity => {
const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY);
return {
entity,
resolved: {
@@ -55,32 +63,18 @@ export const DocsTable = ({
kind: entity.kind,
name: entity.metadata.name,
}),
ownedByRelations,
ownedByRelationsTitle: ownedByRelations
.map(r => formatEntityRefTitle(r, { defaultKind: 'group' }))
.join(', '),
},
};
});
const columns = [
{
title: 'Document',
field: 'name',
highlight: true,
render: (row: DocsTableRow): React.ReactNode => (
<SubvalueCell
value={
<Link to={row.resolved.docsUrl}>{row.entity.metadata.name}</Link>
}
subvalue={row.entity.metadata.description}
/>
),
},
{
title: 'Owner',
field: 'entity.spec.owner',
},
{
title: 'Type',
field: 'entity.spec.type',
},
const defaultColumns: TableColumn<DocsTableRow>[] = [
columnFactories.createNameColumn(),
columnFactories.createOwnerColumn(),
columnFactories.createTypeColumn(),
];
const defaultActions: TableProps<DocsTableRow>['actions'] = [
@@ -99,7 +93,7 @@ export const DocsTable = ({
actionsColumnIndex: -1,
}}
data={documents}
columns={columns}
columns={columns || defaultColumns}
actions={actions || defaultActions}
title={
title
@@ -17,35 +17,40 @@
import React from 'react';
import { useCopyToClipboard } from 'react-use';
import { capitalize } from 'lodash';
import { CodeSnippet, WarningPanel } from '@backstage/core-components';
import {
favoriteEntityIcon,
favoriteEntityTooltip,
CodeSnippet,
TableColumn,
TableProps,
WarningPanel,
} from '@backstage/core-components';
import {
useEntityListProvider,
useStarredEntities,
} from '@backstage/plugin-catalog-react';
import * as actionFactories from './actions';
import { DocsTable } from './DocsTable';
import * as actionFactories from './actions';
import * as columnFactories from './columns';
import { DocsTableRow } from './types';
export const EntityListDocsTable = () => {
export const EntityListDocsTable = ({
columns,
actions,
}: {
columns?: TableColumn<DocsTableRow>[];
actions?: TableProps<DocsTableRow>['actions'];
}) => {
const { loading, error, entities, filters } = useEntityListProvider();
const { isStarredEntity, toggleStarredEntity } = useStarredEntities();
const [, copyToClipboard] = useCopyToClipboard();
const title = capitalize(filters.user?.value ?? 'all');
const actions = [
const defaultActions = [
actionFactories.createCopyDocsUrlAction(copyToClipboard),
({ entity }: DocsTableRow) => {
const isStarred = isStarredEntity(entity);
return {
cellStyle: { paddingLeft: '1em' },
icon: () => favoriteEntityIcon(isStarred),
tooltip: favoriteEntityTooltip(isStarred),
onClick: () => toggleStarredEntity(entity),
};
},
actionFactories.createStarEntityAction(
isStarredEntity,
toggleStarredEntity,
),
];
if (error) {
@@ -64,7 +69,11 @@ export const EntityListDocsTable = () => {
title={title}
entities={entities}
loading={loading}
actions={actions}
actions={actions || defaultActions}
columns={columns}
/>
);
};
EntityListDocsTable.columns = columnFactories;
EntityListDocsTable.actions = actionFactories;
@@ -19,6 +19,8 @@ import {
Content,
ContentHeader,
SupportButton,
TableColumn,
TableProps,
} from '@backstage/core-components';
import {
EntityListContainer,
@@ -29,13 +31,23 @@ import {
EntityListProvider,
EntityOwnerPicker,
EntityTagPicker,
UserListFilterKind,
UserListPicker,
} from '@backstage/plugin-catalog-react';
import { EntityListDocsTable } from './EntityListDocsTable';
import { TechDocsHomeLayout } from './TechDocsHomeLayout';
import { TechDocsPicker } from './TechDocsPicker';
import { DocsTableRow } from './types';
export const TechDocsHome = () => {
export const TechDocsHome = ({
initialFilter = 'all',
columns,
actions,
}: {
initialFilter?: UserListFilterKind;
columns?: TableColumn<DocsTableRow>[];
actions?: TableProps<DocsTableRow>['actions'];
}) => {
return (
<TechDocsHomeLayout>
<Content>
@@ -48,12 +60,12 @@ export const TechDocsHome = () => {
<FilteredEntityLayout>
<FilterContainer>
<TechDocsPicker />
<UserListPicker initialFilter="all" />
<UserListPicker initialFilter={initialFilter} />
<EntityOwnerPicker />
<EntityTagPicker />
</FilterContainer>
<EntityListContainer>
<EntityListDocsTable />
<EntityListDocsTable actions={actions} columns={columns} />
</EntityListContainer>
</FilteredEntityLayout>
</EntityListProvider>
@@ -16,6 +16,10 @@
import React from 'react';
import ShareIcon from '@material-ui/icons/Share';
import {
favoriteEntityIcon,
favoriteEntityTooltip,
} from '@backstage/plugin-catalog-react';
import { DocsTableRow } from './types';
export function createCopyDocsUrlAction(copyToClipboard: Function) {
@@ -25,8 +29,26 @@ export function createCopyDocsUrlAction(copyToClipboard: Function) {
tooltip: 'Click to copy documentation link to clipboard',
onClick: () =>
copyToClipboard(
`${window.location.href.split('/?')[0]}/${row.resolved.docsUrl}`,
`${window.location.origin}${window.location.pathname.replace(
/\/?$/,
'/',
)}${row.resolved.docsUrl}`,
),
};
};
}
export function createStarEntityAction(
isStarredEntity: Function,
toggleStarredEntity: Function,
) {
return ({ entity }: DocsTableRow) => {
const isStarred = isStarredEntity(entity);
return {
cellStyle: { paddingLeft: '1em' },
icon: () => favoriteEntityIcon(isStarred),
tooltip: favoriteEntityTooltip(isStarred),
onClick: () => toggleStarredEntity(entity),
};
};
}
@@ -0,0 +1,56 @@
/*
* 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 { Link, SubvalueCell, TableColumn } from '@backstage/core-components';
import { EntityRefLinks } from '@backstage/plugin-catalog-react';
import { DocsTableRow } from './types';
export function createNameColumn(): TableColumn<DocsTableRow> {
return {
title: 'Document',
field: 'entity.metadata.name',
highlight: true,
render: (row: DocsTableRow) => (
<SubvalueCell
value={
<Link to={row.resolved.docsUrl}>{row.entity.metadata.name}</Link>
}
subvalue={row.entity.metadata.description}
/>
),
};
}
export function createOwnerColumn(): TableColumn<DocsTableRow> {
return {
title: 'Owner',
field: 'resolved.ownedByRelationsTitle',
render: ({ resolved }) => (
<EntityRefLinks
entityRefs={resolved.ownedByRelations}
defaultKind="group"
/>
),
};
}
export function createTypeColumn(): TableColumn<DocsTableRow> {
return {
title: 'Type',
field: 'entity.spec.type',
};
}
@@ -18,3 +18,4 @@ export { EntityListDocsTable } from './EntityListDocsTable';
export { TechDocsHomeLayout } from './TechDocsHomeLayout';
export { TechDocsPicker } from './TechDocsPicker';
export type { PanelType } from './TechDocsCustomHome';
export type { DocsTableRow } from './types';
@@ -14,11 +14,13 @@
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { Entity, EntityName } from '@backstage/catalog-model';
export type DocsTableRow = {
entity: Entity;
resolved: {
docsUrl: string;
ownedByRelationsTitle: string;
ownedByRelations: EntityName[];
};
};
+2 -1
View File
@@ -18,7 +18,7 @@ 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';
export type { DocsTableRow, PanelType } from './home/components';
export {
EntityListDocsTable,
TechDocsHomeLayout,
@@ -30,6 +30,7 @@ export {
DocsTable,
EntityTechdocsContent,
TechDocsCustomHome,
TechDocsHome,
TechdocsPage,
techdocsPlugin as plugin,
techdocsPlugin,
+8
View File
@@ -113,6 +113,14 @@ export const TechDocsCustomHome = techdocsPlugin.provide(
}),
);
export const TechDocsHome = techdocsPlugin.provide(
createRoutableExtension({
component: () =>
import('./home/components/TechDocsHome').then(m => m.TechDocsHome),
mountPoint: rootRouteRef,
}),
);
export const TechDocsReaderPage = techdocsPlugin.provide(
createRoutableExtension({
component: () =>