Accept columns prop as an array or a function
Signed-off-by: Paul Stoker <pstoker@spotify.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user