core-components: remove BaseTable refactoring Table logic

Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Vincenzo Scamporlino
2023-11-15 20:06:22 +01:00
parent b9736f55fc
commit 35bd8a89b6
4 changed files with 148 additions and 198 deletions
+1 -14
View File
@@ -13,7 +13,6 @@ import { BackstageUserIdentity } from '@backstage/core-plugin-api';
import { BottomNavigationActionProps } from '@material-ui/core/BottomNavigationAction';
import { ButtonProps as ButtonProps_2 } from '@material-ui/core/Button';
import { CardHeaderProps } from '@material-ui/core/CardHeader';
import { ClassNameMap } from '@material-ui/styles';
import { Column } from '@material-table/core';
import { ComponentClass } from 'react';
import { ComponentProps } from 'react';
@@ -105,15 +104,6 @@ export type BackstageOverrides = Overrides & {
>;
};
// @public (undocumented)
export function BaseTable<T extends object = {}>(
props: TableProps<T> & {
loading?: boolean;
emptyContent?: ReactNode;
subtitle?: string;
},
): React_2.JSX.Element;
// @public (undocumented)
export type BoldHeaderClassKey = 'root' | 'title' | 'subheader';
@@ -1448,9 +1438,6 @@ export type TableState = {
filters?: SelectedFilters;
};
// @public
export const tableStyles: (props?: any) => ClassNameMap<string>;
// Warning: (ae-missing-release-tag) "TableToolbarClassKey" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -1551,6 +1538,6 @@ export type WarningPanelClassKey =
// src/components/DependencyGraph/types.d.ts:22:9 - (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/core-components" does not have an export "DependencyNode"
// src/components/DependencyGraph/types.d.ts:26:9 - (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/core-components" does not have an export "DependencyNode"
// src/components/TabbedLayout/RoutedTabs.d.ts:9:5 - (ae-forgotten-export) The symbol "SubRoute_2" needs to be exported by the entry point index.d.ts
// src/components/Table/Table.d.ts:26:5 - (ae-forgotten-export) The symbol "SelectedFilters" needs to be exported by the entry point index.d.ts
// src/components/Table/Table.d.ts:20:5 - (ae-forgotten-export) The symbol "SelectedFilters" needs to be exported by the entry point index.d.ts
// src/layout/ErrorBoundary/ErrorBoundary.d.ts:8:5 - (ae-forgotten-export) The symbol "SlackChannel" needs to be exported by the entry point index.d.ts
```
@@ -48,6 +48,7 @@ import React, {
ReactNode,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
@@ -154,12 +155,7 @@ const useFilterStyles = makeStyles<BackstageTheme>(
export type TableClassKey = 'root';
/**
* Style classes for the `Table` component.
*
* @public
*/
export const tableStyles = makeStyles<BackstageTheme>(
const tableStyles = makeStyles<BackstageTheme>(
() => ({
root: {
display: 'flex',
@@ -308,9 +304,11 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
const {
data,
columns,
emptyContent,
options,
title,
subtitle,
localization,
filters,
initialState,
onStateChange,
@@ -320,6 +318,8 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
} = props;
const tableClasses = tableStyles();
const theme = useTheme();
const calculatedInitialState = { ...defaultInitialState, ...initialState };
const [filtersOpen, setFiltersOpen] = useState(
@@ -329,8 +329,7 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
() => setFiltersOpen(v => !v),
[setFiltersOpen],
);
const [selectedFiltersLength, setSelectedFiltersLength] = useState(0);
const [tableData, setTableData] = useState(data as any[]);
const [selectedFilters, setSelectedFilters] = useState(
calculatedInitialState.filters,
);
@@ -358,13 +357,9 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
[columns],
);
useEffect(() => {
if (typeof data === 'function') {
return;
}
if (!selectedFilters) {
setTableData(data as any[]);
return;
const tableData = useMemo(() => {
if (typeof data === 'function' || !selectedFilters) {
return data;
}
const selectedFiltersArray = Object.values(selectedFilters);
@@ -390,62 +385,12 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
return fieldValue === filterValue;
}),
);
setTableData(newData);
} else {
setTableData(data as any[]);
return newData;
}
setSelectedFiltersLength(selectedFiltersArray.flat().length);
return data;
}, [data, selectedFilters, getFieldByTitle]);
const constructFilters = (
filterConfig: TableFilter[],
dataValue: any[] | undefined,
): Filter[] => {
const extractDistinctValues = (field: string | keyof T): Set<any> => {
const distinctValues = new Set<any>();
const addValue = (value: any) => {
if (value !== undefined && value !== null) {
distinctValues.add(value);
}
};
if (dataValue) {
dataValue.forEach(el => {
const value = extractValueByField(
el,
getFieldByTitle(field) as string,
);
if (Array.isArray(value)) {
(value as []).forEach(addValue);
} else {
addValue(value);
}
});
}
return distinctValues;
};
const constructSelect = (
filter: TableFilter,
): Without<SelectProps, 'onChange'> => {
return {
placeholder: 'All results',
label: filter.column,
multiple: filter.type === 'multiple-select',
items: [...extractDistinctValues(filter.column)].sort().map(value => ({
label: value,
value,
})),
};
};
return filterConfig.map(filter => ({
type: filter.type,
element: constructSelect(filter),
}));
};
const selectedFiltersLength = Object.values(selectedFilters).flat().length;
const hasFilters = !!filters?.length;
const Toolbar = useCallback(
@@ -463,26 +408,50 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
[toggleFilters, hasFilters, selectedFiltersLength, setSearch],
);
const hasNoRows = typeof data !== 'function' && data.length === 0;
const columnCount = columns.length;
const Body = useMemo(
() => makeBody({ hasNoRows, emptyContent, columnCount, loading }),
[hasNoRows, emptyContent, columnCount, loading],
);
return (
<Box className={tableClasses.root}>
{filtersOpen && data && typeof data !== 'function' && filters?.length && (
<Filters
filters={constructFilters(filters, data as any[])}
filters={constructFilters(filters, data as any[], columns)}
selectedFilters={selectedFilters}
onChangeFilters={setSelectedFilters}
/>
)}
<BaseTable<T>
<MTable<T>
components={{
Header: StyledMTableHeader,
Body,
Toolbar,
...components,
}}
options={options}
columns={columns}
title={title}
subtitle={subtitle}
loading={loading}
data={typeof data === 'function' ? data : tableData}
options={{ headerStyle: { textTransform: 'uppercase' }, ...options }}
columns={convertColumns(columns, theme)}
icons={tableIcons}
title={
<>
<Typography variant="h5" component="h2">
{title}
</Typography>
{subtitle && (
<Typography color="textSecondary" variant="body1">
{subtitle}
</Typography>
)}
</>
}
data={tableData}
style={{ width: '100%' }}
localization={{
toolbar: { searchPlaceholder: 'Filter', searchTooltip: 'Filter' },
...localization,
}}
{...restProps}
/>
</Box>
@@ -491,83 +460,83 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
Table.icons = Object.freeze(tableIcons);
/**
* @public
*/
export function BaseTable<T extends object = {}>(
props: TableProps<T> & {
loading?: boolean;
emptyContent?: ReactNode;
subtitle?: string;
},
) {
const {
columns,
components,
data,
emptyContent,
loading,
options,
title,
subtitle,
localization,
...restProps
} = props;
function makeBody({
columnCount,
emptyContent,
hasNoRows,
loading,
}: {
hasNoRows: boolean;
emptyContent: ReactNode;
columnCount: number;
loading?: boolean;
}) {
return (bodyProps: any /* no type for this in material-table */) => {
if (loading) {
return <TableLoadingBody colSpan={columnCount} />;
}
const hasNoRows = typeof data !== 'function' && data.length === 0;
if (emptyContent && hasNoRows) {
return (
<tbody>
<tr>
<td colSpan={columnCount}>{emptyContent}</td>
</tr>
</tbody>
);
}
const columnCount = columns.length;
const Body = useCallback(
(bodyProps: any /* no type for this in material-table */) => {
if (loading) {
return <TableLoadingBody colSpan={columnCount} />;
}
if (emptyContent && hasNoRows) {
return (
<tbody>
<tr>
<td colSpan={columnCount}>{emptyContent}</td>
</tr>
</tbody>
);
}
return <MTableBody {...bodyProps} />;
},
[hasNoRows, emptyContent, columnCount, loading],
);
const theme = useTheme();
return (
<MTable<T>
components={{
Header: StyledMTableHeader,
Body,
...components,
}}
options={{ headerStyle: { textTransform: 'uppercase' }, ...options }}
columns={convertColumns(columns, theme)}
icons={tableIcons}
title={
<>
<Typography variant="h5" component="h2">
{title}
</Typography>
{subtitle && (
<Typography color="textSecondary" variant="body1">
{subtitle}
</Typography>
)}
</>
}
data={data}
style={{ width: '100%' }}
localization={{
...localization,
toolbar: { searchPlaceholder: 'Filter', searchTooltip: 'Filter' },
}}
{...restProps}
/>
);
return <MTableBody {...bodyProps} />;
};
}
function constructFilters<T extends object>(
filterConfig: TableFilter[],
dataValue: any[] | undefined,
columns: TableColumn<T>[],
): Filter[] {
const extractDistinctValues = (field: string | keyof T): Set<any> => {
const distinctValues = new Set<any>();
const addValue = (value: any) => {
if (value !== undefined && value !== null) {
distinctValues.add(value);
}
};
if (dataValue) {
dataValue.forEach(el => {
const value = extractValueByField(
el,
columns.find(c => c.title === field)?.field as string,
);
if (Array.isArray(value)) {
(value as []).forEach(addValue);
} else {
addValue(value);
}
});
}
return distinctValues;
};
const constructSelect = (
filter: TableFilter,
): Without<SelectProps, 'onChange'> => {
return {
placeholder: 'All results',
label: filter.column,
multiple: filter.type === 'multiple-select',
items: [...extractDistinctValues(filter.column)].sort().map(value => ({
label: value,
value,
})),
};
};
return filterConfig.map(filter => ({
type: filter.type,
element: constructSelect(filter),
}));
}
@@ -17,7 +17,7 @@
export type { TableFiltersClassKey } from './Filters';
export { SubvalueCell } from './SubvalueCell';
export type { SubvalueCellClassKey } from './SubvalueCell';
export { Table, BaseTable, tableStyles } from './Table';
export { Table } from './Table';
export type {
TableColumn,
TableFilter,
@@ -15,9 +15,8 @@
*/
import React from 'react';
import Box from '@material-ui/core/Box';
import { BaseTable, TableProps, tableStyles } from '@backstage/core-components';
import { Table, TableProps } from '@backstage/core-components';
import { CatalogTableRow } from './types';
type PaginatedCatalogTableProps = {
@@ -30,35 +29,30 @@ type PaginatedCatalogTableProps = {
*/
export function PaginatedCatalogTable(props: PaginatedCatalogTableProps) {
const { columns, data, next, prev } = props;
const tableClasses = tableStyles();
return (
<>
<Box className={tableClasses.root}>
<BaseTable
columns={columns}
data={data}
options={{
paginationPosition: 'both',
pageSizeOptions: [],
showFirstLastPageButtons: false,
pageSize: Number.MAX_SAFE_INTEGER,
emptyRowsWhenPaging: false,
}}
onPageChange={page => {
if (page > 0) {
next?.();
} else {
prev?.();
}
}}
/* this will enable the prev button accordingly */
page={prev ? 1 : 0}
/* this will enable the next button accordingly */
totalCount={next ? Number.MAX_VALUE : Number.MAX_SAFE_INTEGER}
localization={{ pagination: { labelDisplayedRows: '' } }}
/>
</Box>
</>
<Table
columns={columns}
data={data}
options={{
paginationPosition: 'both',
pageSizeOptions: [],
showFirstLastPageButtons: false,
pageSize: Number.MAX_SAFE_INTEGER,
emptyRowsWhenPaging: false,
}}
onPageChange={page => {
if (page > 0) {
next?.();
} else {
prev?.();
}
}}
/* this will enable the prev button accordingly */
page={prev ? 1 : 0}
/* this will enable the next button accordingly */
totalCount={next ? Number.MAX_VALUE : Number.MAX_SAFE_INTEGER}
localization={{ pagination: { labelDisplayedRows: '' } }}
/>
);
}