Accept columns prop as an array or a function

Signed-off-by: Paul Stoker <pstoker@spotify.com>
This commit is contained in:
Paul Stoker
2023-11-08 13:57:14 +01:00
parent 02022a0ba8
commit b8e1eb2c0f
5 changed files with 110 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': minor
---
The `columns` prop can be an array or a function that returns an array in order to override the default columns of the `CatalogIndexPage`.
@@ -44,6 +44,8 @@ import React from 'react';
import { createComponentRouteRef } from '../../routes';
import { CatalogTableRow } from '../CatalogTable';
import { DefaultCatalogPage } from './DefaultCatalogPage';
import { ColumnsFunc } from '../CatalogTable/CatalogTable';
import { Entity } from '@backstage/catalog-model/';
describe('DefaultCatalogPage', () => {
const origReplaceState = window.history.replaceState;
@@ -217,6 +219,28 @@ describe('DefaultCatalogPage', () => {
expect(columnHeaderLabels).toEqual(['Foo', 'Bar', 'Baz', 'Actions']);
}, 20_000);
it('should render the custom column function passed as prop', async () => {
const columns: ColumnsFunc = (
kind: string | undefined,
entities: Entity[],
) => {
return kind === 'component' && entities.length
? [
{ title: 'Foo', field: 'entity.foo' },
{ title: 'Bar', field: 'entity.bar' },
{ title: 'Baz', field: 'entity.spec.lifecycle' },
]
: [];
};
await renderWrapped(<DefaultCatalogPage columns={columns} />);
const columnHeader = screen
.getAllByRole('button')
.filter(c => c.tagName === 'SPAN');
const columnHeaderLabels = columnHeader.map(c => c.textContent);
expect(columnHeaderLabels).toEqual(['Foo', 'Bar', 'Baz', 'Actions']);
}, 20_000);
it('should render the default actions of an item in the grid', async () => {
await renderWrapped(<DefaultCatalogPage />);
await waitFor(() => expect(catalogApi.queryEntities).toHaveBeenCalled());
@@ -43,6 +43,7 @@ import { createComponentRouteRef } from '../../routes';
import { CatalogTable, CatalogTableRow } from '../CatalogTable';
import { catalogTranslationRef } from '../../translation';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { ColumnsFunc } from '../CatalogTable/CatalogTable';
/** @internal */
export interface BaseCatalogPageProps {
@@ -86,7 +87,7 @@ export function BaseCatalogPage(props: BaseCatalogPageProps) {
*/
export interface DefaultCatalogPageProps {
initiallySelectedFilter?: UserListFilterKind;
columns?: TableColumn<CatalogTableRow>[];
columns?: TableColumn<CatalogTableRow>[] | ColumnsFunc;
actions?: TableProps<CatalogTableRow>['actions'];
initialKind?: string;
tableOptions?: TableProps<CatalogTableRow>['options'];
@@ -31,7 +31,7 @@ import {
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { act, fireEvent, screen } from '@testing-library/react';
import * as React from 'react';
import { CatalogTable } from './CatalogTable';
import { CatalogTable, ColumnsFunc } from './CatalogTable';
const entities: Entity[] = [
{
@@ -379,4 +379,60 @@ describe('CatalogTable component', () => {
const labelCellValue = screen.getByText('generic');
expect(labelCellValue).toBeInTheDocument();
});
it('should render the label column with customised title and value as specified using function', async () => {
const columns: ColumnsFunc = (kind, entities1) => {
return kind === 'api' && entities1.length
? [
CatalogTable.columns.createNameColumn({ defaultKind: 'API' }),
CatalogTable.columns.createLabelColumn('category', {
title: 'Category',
}),
]
: [];
};
const entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'API',
metadata: {
name: 'APIWithLabel',
labels: { category: 'generic' },
},
};
const expectedColumns = ['Name', 'Category', 'Actions'];
await renderInTestApp(
<ApiProvider apis={mockApis}>
<MockEntityListContextProvider
value={{
entities: [entity],
filters: {
kind: {
value: 'api',
getCatalogFilters: () => ({ kind: 'api' }),
toQueryValue: () => 'api',
},
},
}}
>
<CatalogTable columns={columns} />
</MockEntityListContextProvider>
</ApiProvider>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
},
},
);
const columnHeader = screen
.getAllByRole('button')
.filter(c => c.tagName === 'SPAN');
const columnHeaderLabels = columnHeader.map(c => c.textContent);
expect(columnHeaderLabels).toEqual(expectedColumns);
const labelCellValue = screen.getByText('generic');
expect(labelCellValue).toBeInTheDocument();
});
});
@@ -40,19 +40,29 @@ import Edit from '@material-ui/icons/Edit';
import OpenInNew from '@material-ui/icons/OpenInNew';
import Star from '@material-ui/icons/Star';
import StarBorder from '@material-ui/icons/StarBorder';
import { capitalize } from 'lodash';
import { capitalize, isFunction } from 'lodash';
import React, { ReactNode, useMemo } from 'react';
import { columnFactories } from './columns';
import { CatalogTableRow } from './types';
import pluralize from 'pluralize';
/**
* Typed columns function to dynamically render columns based on entities and chosen kind.
*
* @public
*/
export type ColumnsFunc = (
kind: string | undefined,
entities: Entity[],
) => TableColumn<CatalogTableRow>[];
/**
* Props for {@link CatalogTable}.
*
* @public
*/
export interface CatalogTableProps {
columns?: TableColumn<CatalogTableRow>[];
columns?: TableColumn<CatalogTableRow>[] | ColumnsFunc;
actions?: TableProps<CatalogTableRow>['actions'];
tableOptions?: TableProps<CatalogTableRow>['options'];
emptyContent?: ReactNode;
@@ -121,6 +131,12 @@ export const CatalogTable = (props: CatalogTableProps) => {
}
}, [filters.kind?.value, entities]);
const overrideColumns = useMemo(() => {
return isFunction(columns)
? columns(filters.kind?.value, entities)
: columns;
}, [columns, filters.kind?.value, entities]);
const showTypeColumn = filters.type === undefined;
// TODO(timbonicus): remove the title from the CatalogTable once using EntitySearchBar
const titlePreamble = capitalize(filters.user?.value ?? 'all');
@@ -227,7 +243,9 @@ export const CatalogTable = (props: CatalogTableProps) => {
};
});
const typeColumn = (columns || defaultColumns).find(c => c.title === 'Type');
const typeColumn = (overrideColumns || defaultColumns).find(
c => c.title === 'Type',
);
if (typeColumn) {
typeColumn.hidden = !showTypeColumn;
}
@@ -241,7 +259,7 @@ export const CatalogTable = (props: CatalogTableProps) => {
return (
<Table<CatalogTableRow>
isLoading={loading}
columns={columns || defaultColumns}
columns={overrideColumns || defaultColumns}
options={{
paging: showPagination,
pageSize: 20,