Add possibility to customize actions in CatalogTable

Signed-off-by: Matteo Barone <matteo.barone@klarna.com>
This commit is contained in:
Matteo Barone
2021-06-14 15:15:13 +02:00
parent ae501c04df
commit 3d7b1c9f04
6 changed files with 171 additions and 45 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': patch
---
Add possibility to customize actions in CatalogTable
@@ -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<CatalogApi> = {
@@ -128,6 +132,81 @@ describe('CatalogPage', () => {
),
);
it('should render the default column of the grid', async () => {
const { getAllByRole } = await renderWrapped(<CatalogPage />);
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<EntityRow>[] = [
{ title: 'Foo', field: 'entity.foo' },
{ title: 'Bar', field: 'entity.bar' },
{ title: 'Baz', field: 'entity.spec.lifecycle' },
];
const { getAllByRole } = await renderWrapped(
<CatalogPage columns={columns} />,
);
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(<CatalogPage />);
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<EntityRow>['actions'] = [
() => {
return {
icon: () => <DashboardIcon fontSize="small" />,
tooltip: 'Foo Action',
disabled: false,
onClick: () => jest.fn(),
};
},
() => {
return {
icon: () => <DashboardIcon fontSize="small" />,
tooltip: 'Bar Action',
disabled: true,
onClick: () => jest.fn(),
};
},
];
const { findByTitle, findByText } = await renderWrapped(
<CatalogPage actions={actions} />,
);
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
@@ -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<EntityRow>[];
actions?: TableProps<EntityRow>['actions'];
};
export const CatalogPage = ({
initiallySelectedFilter = 'owned',
columns,
actions,
}: CatalogPageProps) => {
const styles = useStyles();
@@ -78,7 +81,7 @@ export const CatalogPage = ({
<EntityLifecyclePicker />
<EntityTagPicker />
</div>
<CatalogTable columns={columns} />
<CatalogTable columns={columns} actions={actions} />
</EntityListProvider>
</div>
</Content>
@@ -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<EntityRow>[] = [
@@ -52,9 +45,10 @@ const defaultColumns: TableColumn<EntityRow>[] = [
type CatalogTableProps = {
columns?: TableColumn<EntityRow>[];
actions?: TableProps<EntityRow>['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<EntityRow>['actions'] = [
({ entity }) => {
const url = getEntityMetadataViewUrl(entity);
return {
icon: () => <OpenInNew fontSize="small" />,
tooltip: 'View',
disabled: !url,
onClick: () => {
if (!url) return;
window.open(url, '_blank');
},
};
},
({ entity }) => {
const url = getEntityMetadataEditUrl(entity);
return {
icon: () => <Edit fontSize="small" />,
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<EntityRow>['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}
/>
);
};
@@ -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: () => <OpenInNew fontSize="small" />,
tooltip: 'View',
disabled: !url,
onClick: () => {
if (!url) return;
window.open(url, '_blank');
},
};
}
export function createEditUrlAction(entity: Entity) {
const url = getEntityMetadataEditUrl(entity);
return {
icon: () => <Edit fontSize="small" />,
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),
};
}
+3
View File
@@ -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';