From 10ef115554e5b2c29a4fe357cb9325cfd0295761 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Wed, 4 Aug 2021 10:01:00 -0400 Subject: [PATCH] refactor(EntityListDocsTable): support custom columns and actions Signed-off-by: Phil Kuang --- plugins/techdocs/api-report.md | 101 +++++++++++++++++- .../src/home/components/DocsTable.tsx | 46 ++++---- .../home/components/EntityListDocsTable.tsx | 41 ++++--- .../src/home/components/TechDocsHome.tsx | 18 +++- .../techdocs/src/home/components/actions.tsx | 24 ++++- .../techdocs/src/home/components/columns.tsx | 56 ++++++++++ plugins/techdocs/src/home/components/index.ts | 1 + plugins/techdocs/src/home/components/types.ts | 4 +- plugins/techdocs/src/index.ts | 3 +- plugins/techdocs/src/plugin.ts | 8 ++ 10 files changed, 251 insertions(+), 51 deletions(-) create mode 100644 plugins/techdocs/src/home/components/columns.tsx diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index 95d78475fb..edae3d9585 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -17,6 +17,54 @@ 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'; +import { TableColumn } from '@backstage/core-components'; +import { TableProps } from '@backstage/core-components'; +import { UserListFilterKind } from '@backstage/plugin-catalog-react'; + +// Warning: (ae-missing-release-tag) "createCopyDocsUrlAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +function createCopyDocsUrlAction( + copyToClipboard: Function, +): ( + row: DocsTableRow, +) => { + icon: () => JSX.Element; + tooltip: string; + onClick: () => any; +}; + +// Warning: (ae-missing-release-tag) "createNameColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +function createNameColumn(): TableColumn; + +// Warning: (ae-missing-release-tag) "createOwnerColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +function createOwnerColumn(): TableColumn; + +// Warning: (ae-missing-release-tag) "createStarEntityAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +function createStarEntityAction( + isStarredEntity: Function, + toggleStarredEntity: Function, +): ({ + entity, +}: DocsTableRow) => { + cellStyle: { + paddingLeft: string; + }; + icon: () => JSX.Element; + tooltip: string; + onClick: () => any; +}; + +// Warning: (ae-missing-release-tag) "createTypeColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +function createTypeColumn(): TableColumn; // Warning: (ae-missing-release-tag) "DocsCardGrid" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -45,11 +93,13 @@ export const DocsTable: ({ entities, title, loading, + columns, actions, }: { entities: Entity[] | undefined; title?: string | undefined; loading?: boolean | undefined; + columns?: TableColumn[] | undefined; actions?: | ( | Action @@ -62,6 +112,18 @@ export const DocsTable: ({ | undefined; }) => JSX.Element | null; +// Warning: (ae-missing-release-tag) "DocsTableRow" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type DocsTableRow = { + entity: Entity; + resolved: { + docsUrl: string; + ownedByRelationsTitle: string; + ownedByRelations: EntityName[]; + }; +}; + // 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) // @@ -71,7 +133,17 @@ 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; +export const EntityListDocsTable: { + ({ + columns, + actions, + }: { + columns?: TableColumn[] | undefined; + actions?: TableProps['actions']; + }): JSX.Element; + columns: typeof columnFactories; + actions: typeof actionFactories; +}; // Warning: (ae-missing-release-tag) "EntityTechdocsContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -163,6 +235,28 @@ export const TechDocsCustomHome: ({ tabsConfig: TabsConfig; }) => JSX.Element; +// Warning: (ae-missing-release-tag) "TechDocsHome" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const TechDocsHome: ({ + initialFilter, + columns, + actions, +}: { + initialFilter?: UserListFilterKind | undefined; + columns?: TableColumn[] | undefined; + actions?: + | ( + | Action + | { + action: (rowData: DocsTableRow) => Action; + position: string; + } + | ((rowData: DocsTableRow) => Action) + )[] + | undefined; +}) => 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) // @@ -286,8 +380,9 @@ export class TechDocsStorageClient implements TechDocsStorageApi { // Warnings were encountered during analysis: // -// 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 +// src/home/components/EntityListDocsTable.d.ts:11:5 - (ae-forgotten-export) The symbol "columnFactories" needs to be exported by the entry point index.d.ts +// src/home/components/EntityListDocsTable.d.ts:12:5 - (ae-forgotten-export) The symbol "actionFactories" needs to be exported by the entry point index.d.ts +// src/plugin.d.ts:24:5 - (ae-forgotten-export) The symbol "TabsConfig" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/techdocs/src/home/components/DocsTable.tsx b/plugins/techdocs/src/home/components/DocsTable.tsx index 68e6405265..29b63f5441 100644 --- a/plugins/techdocs/src/home/components/DocsTable.tsx +++ b/plugins/techdocs/src/home/components/DocsTable.tsx @@ -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[]; actions?: TableProps['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 => ( - {row.entity.metadata.name} - } - subvalue={row.entity.metadata.description} - /> - ), - }, - { - title: 'Owner', - field: 'entity.spec.owner', - }, - { - title: 'Type', - field: 'entity.spec.type', - }, + const defaultColumns: TableColumn[] = [ + columnFactories.createNameColumn(), + columnFactories.createOwnerColumn(), + columnFactories.createTypeColumn(), ]; const defaultActions: TableProps['actions'] = [ @@ -99,7 +93,7 @@ export const DocsTable = ({ actionsColumnIndex: -1, }} data={documents} - columns={columns} + columns={columns || defaultColumns} actions={actions || defaultActions} title={ title diff --git a/plugins/techdocs/src/home/components/EntityListDocsTable.tsx b/plugins/techdocs/src/home/components/EntityListDocsTable.tsx index 11e49fa2d4..b94e7adace 100644 --- a/plugins/techdocs/src/home/components/EntityListDocsTable.tsx +++ b/plugins/techdocs/src/home/components/EntityListDocsTable.tsx @@ -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[]; + actions?: TableProps['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; diff --git a/plugins/techdocs/src/home/components/TechDocsHome.tsx b/plugins/techdocs/src/home/components/TechDocsHome.tsx index ef69bcdc44..0aa1b87282 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsHome.tsx @@ -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[]; + actions?: TableProps['actions']; +}) => { return ( @@ -48,12 +60,12 @@ export const TechDocsHome = () => { - + - + diff --git a/plugins/techdocs/src/home/components/actions.tsx b/plugins/techdocs/src/home/components/actions.tsx index e26415b049..b737e99d14 100644 --- a/plugins/techdocs/src/home/components/actions.tsx +++ b/plugins/techdocs/src/home/components/actions.tsx @@ -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), + }; + }; +} diff --git a/plugins/techdocs/src/home/components/columns.tsx b/plugins/techdocs/src/home/components/columns.tsx new file mode 100644 index 0000000000..5cdcc66c37 --- /dev/null +++ b/plugins/techdocs/src/home/components/columns.tsx @@ -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 { + return { + title: 'Document', + field: 'entity.metadata.name', + highlight: true, + render: (row: DocsTableRow) => ( + {row.entity.metadata.name} + } + subvalue={row.entity.metadata.description} + /> + ), + }; +} + +export function createOwnerColumn(): TableColumn { + return { + title: 'Owner', + field: 'resolved.ownedByRelationsTitle', + render: ({ resolved }) => ( + + ), + }; +} + +export function createTypeColumn(): TableColumn { + return { + title: 'Type', + field: 'entity.spec.type', + }; +} diff --git a/plugins/techdocs/src/home/components/index.ts b/plugins/techdocs/src/home/components/index.ts index 392aa2e82f..a56b80f9a1 100644 --- a/plugins/techdocs/src/home/components/index.ts +++ b/plugins/techdocs/src/home/components/index.ts @@ -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'; diff --git a/plugins/techdocs/src/home/components/types.ts b/plugins/techdocs/src/home/components/types.ts index 324cb59f96..6fd29f1716 100644 --- a/plugins/techdocs/src/home/components/types.ts +++ b/plugins/techdocs/src/home/components/types.ts @@ -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[]; }; }; diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index 9a93e2371c..7c3bf607b4 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -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, diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index 39544500bd..2e031176e4 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -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: () =>