From 3d7b1c9f046878c7883acc26f4e9272d150f7f36 Mon Sep 17 00:00:00 2001 From: Matteo Barone Date: Mon, 14 Jun 2021 15:15:13 +0200 Subject: [PATCH 1/4] Add possibility to customize actions in CatalogTable Signed-off-by: Matteo Barone --- .changeset/sour-donuts-tickle.md | 5 ++ .../CatalogPage/CatalogPage.test.tsx | 79 +++++++++++++++++++ .../components/CatalogPage/CatalogPage.tsx | 5 +- .../components/CatalogTable/CatalogTable.tsx | 57 +++---------- .../src/components/CatalogTable/actions.tsx | 67 ++++++++++++++++ plugins/catalog/src/index.ts | 3 + 6 files changed, 171 insertions(+), 45 deletions(-) create mode 100644 .changeset/sour-donuts-tickle.md create mode 100644 plugins/catalog/src/components/CatalogTable/actions.tsx diff --git a/.changeset/sour-donuts-tickle.md b/.changeset/sour-donuts-tickle.md new file mode 100644 index 0000000000..a8035263eb --- /dev/null +++ b/.changeset/sour-donuts-tickle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Add possibility to customize actions in CatalogTable diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index e6a1f1f687..a9cf96d02c 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -27,6 +27,8 @@ import { identityApiRef, ProfileInfo, storageApiRef, + TableColumn, + TableProps, } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { @@ -37,7 +39,9 @@ import { import { fireEvent, screen } from '@testing-library/react'; import React from 'react'; import { createComponentRouteRef } from '../../routes'; +import { EntityRow } from '../CatalogTable/types'; import { CatalogPage } from './CatalogPage'; +import DashboardIcon from '@material-ui/icons/Dashboard'; describe('CatalogPage', () => { const catalogApi: Partial = { @@ -128,6 +132,81 @@ describe('CatalogPage', () => { ), ); + it('should render the default column of the grid', async () => { + const { getAllByRole } = await renderWrapped(); + + const columnHeader = getAllByRole('button').filter( + c => c.tagName === 'SPAN', + ); + const columnHeaderLabels = columnHeader.map(c => c.textContent); + + expect(columnHeaderLabels).toEqual([ + 'Name', + 'System', + 'Owner', + 'Type', + 'Lifecycle', + 'Description', + 'Tags', + 'Actions', + ]); + }); + + it('should render the custom column passed as prop', async () => { + const columns: TableColumn[] = [ + { title: 'Foo', field: 'entity.foo' }, + { title: 'Bar', field: 'entity.bar' }, + { title: 'Baz', field: 'entity.spec.lifecycle' }, + ]; + const { getAllByRole } = await renderWrapped( + , + ); + + const columnHeader = getAllByRole('button').filter( + c => c.tagName === 'SPAN', + ); + const columnHeaderLabels = columnHeader.map(c => c.textContent); + + expect(columnHeaderLabels).toEqual(['Foo', 'Bar', 'Baz', 'Actions']); + }); + + it('should render the default actions of an item in the grid', async () => { + const { findByTitle, findByText } = await renderWrapped(); + expect(await findByText(/Owned \(1\)/)).toBeInTheDocument(); + expect(await findByTitle(/View/)).toBeInTheDocument(); + expect(await findByTitle(/Edit/)).toBeInTheDocument(); + expect(await findByTitle(/Add to favorites/)).toBeInTheDocument(); + }); + + it('should render the custom actions of an item passed as prop', async () => { + const actions: TableProps['actions'] = [ + () => { + return { + icon: () => , + tooltip: 'Foo Action', + disabled: false, + onClick: () => jest.fn(), + }; + }, + () => { + return { + icon: () => , + tooltip: 'Bar Action', + disabled: true, + onClick: () => jest.fn(), + }; + }, + ]; + + const { findByTitle, findByText } = await renderWrapped( + , + ); + expect(await findByText(/Owned \(1\)/)).toBeInTheDocument(); + expect(await findByTitle(/Foo Action/)).toBeInTheDocument(); + expect(await findByTitle(/Bar Action/)).toBeInTheDocument(); + expect((await findByTitle(/Bar Action/)).firstChild).toBeDisabled(); + }); + // this test right now causes some red lines in the log output when running tests // related to some theme issues in mui-table // https://github.com/mbrn/material-table/issues/1293 diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index d3ade03fe4..aa312f9b6a 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -21,6 +21,7 @@ import { ContentHeader, SupportButton, TableColumn, + TableProps, } from '@backstage/core'; import { EntityKindPicker, @@ -53,11 +54,13 @@ const useStyles = makeStyles(theme => ({ export type CatalogPageProps = { initiallySelectedFilter?: UserListFilterKind; columns?: TableColumn[]; + actions?: TableProps['actions']; }; export const CatalogPage = ({ initiallySelectedFilter = 'owned', columns, + actions, }: CatalogPageProps) => { const styles = useStyles(); @@ -78,7 +81,7 @@ export const CatalogPage = ({ - + diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 6d9f0c21bd..f7049ead9d 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -23,21 +23,14 @@ import { } from '@backstage/core'; import { formatEntityRefTitle, - getEntityMetadataEditUrl, - getEntityMetadataViewUrl, getEntityRelations, useEntityListProvider, useStarredEntities, } from '@backstage/plugin-catalog-react'; -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 * as actionFactories from './actions'; import { EntityRow } from './types'; const defaultColumns: TableColumn[] = [ @@ -52,9 +45,10 @@ const defaultColumns: TableColumn[] = [ type CatalogTableProps = { columns?: TableColumn[]; + actions?: TableProps['actions']; }; -export const CatalogTable = ({ columns }: CatalogTableProps) => { +export const CatalogTable = ({ columns, actions }: CatalogTableProps) => { const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const { loading, error, entities, filters } = useEntityListProvider(); @@ -75,40 +69,15 @@ export const CatalogTable = ({ columns }: CatalogTableProps) => { ); } - const actions: TableProps['actions'] = [ - ({ entity }) => { - const url = getEntityMetadataViewUrl(entity); - return { - icon: () => , - tooltip: 'View', - disabled: !url, - onClick: () => { - if (!url) return; - window.open(url, '_blank'); - }, - }; - }, - ({ entity }) => { - const url = getEntityMetadataEditUrl(entity); - return { - icon: () => , - tooltip: 'Edit', - disabled: !url, - onClick: () => { - if (!url) return; - window.open(url, '_blank'); - }, - }; - }, - ({ entity }) => { - const isStarred = isStarredEntity(entity); - return { - cellStyle: { paddingLeft: '1em' }, - icon: () => favouriteEntityIcon(isStarred), - tooltip: favouriteEntityTooltip(isStarred), - onClick: () => toggleStarredEntity(entity), - }; - }, + const defaultActions: TableProps['actions'] = [ + ({ entity }) => actionFactories.createViewUrlAction(entity), + ({ entity }) => actionFactories.createEditUrlAction(entity), + ({ entity }) => + actionFactories.createStarredAction( + entity, + isStarredEntity, + toggleStarredEntity, + ), ]; const rows = entities.map(entity => { @@ -159,7 +128,7 @@ export const CatalogTable = ({ columns }: CatalogTableProps) => { }} title={`${titlePreamble} (${entities.length})`} data={rows} - actions={actions} + actions={actions || defaultActions} /> ); }; diff --git a/plugins/catalog/src/components/CatalogTable/actions.tsx b/plugins/catalog/src/components/CatalogTable/actions.tsx new file mode 100644 index 0000000000..dfbb0202ea --- /dev/null +++ b/plugins/catalog/src/components/CatalogTable/actions.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Entity } from '@backstage/catalog-model'; +import { + getEntityMetadataViewUrl, + getEntityMetadataEditUrl, +} from '@backstage/plugin-catalog-react'; +import OpenInNew from '@material-ui/icons/OpenInNew'; +import Edit from '@material-ui/icons/Edit'; +import { + favouriteEntityIcon, + favouriteEntityTooltip, +} from '../FavouriteEntity/FavouriteEntity'; + +export function createViewUrlAction(entity: Entity) { + const url = getEntityMetadataViewUrl(entity); + return { + icon: () => , + tooltip: 'View', + disabled: !url, + onClick: () => { + if (!url) return; + window.open(url, '_blank'); + }, + }; +} + +export function createEditUrlAction(entity: Entity) { + const url = getEntityMetadataEditUrl(entity); + return { + icon: () => , + tooltip: 'Edit', + disabled: !url, + onClick: () => { + if (!url) return; + window.open(url, '_blank'); + }, + }; +} + +export function createStarredAction( + entity: Entity, + isStarredEntity: (entity: Entity) => boolean, + toggleStarredEntity: (entity: Entity) => void, +) { + const isStarred = isStarredEntity(entity); + return { + cellStyle: { paddingLeft: '1em' }, + icon: () => favouriteEntityIcon(isStarred), + tooltip: favouriteEntityTooltip(isStarred), + onClick: () => toggleStarredEntity(entity), + }; +} diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 0a40d50036..249d636551 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -41,3 +41,6 @@ export { EntityLinksCard, EntitySystemDiagramCard, } from './plugin'; +export type { EntityRow } from './components/CatalogTable/types'; +export * from './components/CatalogTable/columns'; +export * from './components/CatalogTable/actions'; From 4c46c8e9f9b10fb658594120dc4e08381921e9f3 Mon Sep 17 00:00:00 2001 From: Matteo Barone Date: Thu, 17 Jun 2021 17:33:30 +0200 Subject: [PATCH 2/4] removed exported EntityRow type after rebase from master Signed-off-by: Matteo Barone --- plugins/catalog/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 249d636551..3d5f932896 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -41,6 +41,5 @@ export { EntityLinksCard, EntitySystemDiagramCard, } from './plugin'; -export type { EntityRow } from './components/CatalogTable/types'; export * from './components/CatalogTable/columns'; export * from './components/CatalogTable/actions'; From 63a8205a726590542fc02170ec666c0735527408 Mon Sep 17 00:00:00 2001 From: Matteo Barone Date: Fri, 18 Jun 2021 09:48:11 +0200 Subject: [PATCH 3/4] removed actions factories and define defaultActions in component itself Signed-off-by: Matteo Barone --- .../components/CatalogTable/CatalogTable.tsx | 50 +++++++++++--- .../src/components/CatalogTable/actions.tsx | 67 ------------------- plugins/catalog/src/index.ts | 1 - 3 files changed, 41 insertions(+), 77 deletions(-) delete mode 100644 plugins/catalog/src/components/CatalogTable/actions.tsx diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index f7049ead9d..6515211074 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -23,14 +23,21 @@ import { } from '@backstage/core'; import { formatEntityRefTitle, + getEntityMetadataEditUrl, + getEntityMetadataViewUrl, getEntityRelations, useEntityListProvider, useStarredEntities, } from '@backstage/plugin-catalog-react'; +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 * as actionFactories from './actions'; import { EntityRow } from './types'; const defaultColumns: TableColumn[] = [ @@ -70,14 +77,39 @@ export const CatalogTable = ({ columns, actions }: CatalogTableProps) => { } const defaultActions: TableProps['actions'] = [ - ({ entity }) => actionFactories.createViewUrlAction(entity), - ({ entity }) => actionFactories.createEditUrlAction(entity), - ({ entity }) => - actionFactories.createStarredAction( - entity, - isStarredEntity, - toggleStarredEntity, - ), + ({ entity }) => { + const url = getEntityMetadataViewUrl(entity); + return { + icon: () => , + tooltip: 'View', + disabled: !url, + onClick: () => { + if (!url) return; + window.open(url, '_blank'); + }, + }; + }, + ({ entity }) => { + const url = getEntityMetadataEditUrl(entity); + return { + icon: () => , + tooltip: 'Edit', + disabled: !url, + onClick: () => { + if (!url) return; + window.open(url, '_blank'); + }, + }; + }, + ({ entity }) => { + const isStarred = isStarredEntity(entity); + return { + cellStyle: { paddingLeft: '1em' }, + icon: () => favouriteEntityIcon(isStarred), + tooltip: favouriteEntityTooltip(isStarred), + onClick: () => toggleStarredEntity(entity), + }; + }, ]; const rows = entities.map(entity => { diff --git a/plugins/catalog/src/components/CatalogTable/actions.tsx b/plugins/catalog/src/components/CatalogTable/actions.tsx deleted file mode 100644 index dfbb0202ea..0000000000 --- a/plugins/catalog/src/components/CatalogTable/actions.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2021 Spotify AB - * - * 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 { Entity } from '@backstage/catalog-model'; -import { - getEntityMetadataViewUrl, - getEntityMetadataEditUrl, -} from '@backstage/plugin-catalog-react'; -import OpenInNew from '@material-ui/icons/OpenInNew'; -import Edit from '@material-ui/icons/Edit'; -import { - favouriteEntityIcon, - favouriteEntityTooltip, -} from '../FavouriteEntity/FavouriteEntity'; - -export function createViewUrlAction(entity: Entity) { - const url = getEntityMetadataViewUrl(entity); - return { - icon: () => , - tooltip: 'View', - disabled: !url, - onClick: () => { - if (!url) return; - window.open(url, '_blank'); - }, - }; -} - -export function createEditUrlAction(entity: Entity) { - const url = getEntityMetadataEditUrl(entity); - return { - icon: () => , - tooltip: 'Edit', - disabled: !url, - onClick: () => { - if (!url) return; - window.open(url, '_blank'); - }, - }; -} - -export function createStarredAction( - entity: Entity, - isStarredEntity: (entity: Entity) => boolean, - toggleStarredEntity: (entity: Entity) => void, -) { - const isStarred = isStarredEntity(entity); - return { - cellStyle: { paddingLeft: '1em' }, - icon: () => favouriteEntityIcon(isStarred), - tooltip: favouriteEntityTooltip(isStarred), - onClick: () => toggleStarredEntity(entity), - }; -} diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 3d5f932896..c6feb9c7c0 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -42,4 +42,3 @@ export { EntitySystemDiagramCard, } from './plugin'; export * from './components/CatalogTable/columns'; -export * from './components/CatalogTable/actions'; From d25cedcc1e60efce3cfdf01cdb4ad3d6f0a3be2b Mon Sep 17 00:00:00 2001 From: Matteo Barone Date: Tue, 22 Jun 2021 09:38:11 +0200 Subject: [PATCH 4/4] updated changeset Signed-off-by: Matteo Barone --- .changeset/sour-donuts-tickle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/sour-donuts-tickle.md b/.changeset/sour-donuts-tickle.md index a8035263eb..5cc3323662 100644 --- a/.changeset/sour-donuts-tickle.md +++ b/.changeset/sour-donuts-tickle.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog': patch --- -Add possibility to customize actions in CatalogTable +Adds an optional `actions` prop to `CatalogTable` and `CatalogPage` to support supplying custom actions for each entity row in the table. This uses the default actions if not provided.