diff --git a/.changeset/sour-donuts-tickle.md b/.changeset/sour-donuts-tickle.md new file mode 100644 index 0000000000..5cc3323662 --- /dev/null +++ b/.changeset/sour-donuts-tickle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +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. diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 2edf61acbd..5082f0b6c2 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 c62504a334..c4b3cf1c70 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 044dae30c5..5674d4e59c 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -52,9 +52,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,7 +76,7 @@ export const CatalogTable = ({ columns }: CatalogTableProps) => { ); } - const actions: TableProps['actions'] = [ + const defaultActions: TableProps['actions'] = [ ({ entity }) => { const url = getEntityMetadataViewUrl(entity); return { @@ -159,7 +160,7 @@ export const CatalogTable = ({ columns }: CatalogTableProps) => { }} title={`${titlePreamble} (${entities.length})`} data={rows} - actions={actions} + actions={actions || defaultActions} /> ); }; diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 0a852343d8..f2f28679a5 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -41,3 +41,4 @@ export { EntityLinksCard, EntitySystemDiagramCard, } from './plugin'; +export * from './components/CatalogTable/columns';