mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
Render columns dynamically on wide tables (#7693)
* Use grid for wide tables * WIP * Fix CSS issues * Add unit test * Add constant * Improve ref * Remove wrong refs; no longer need extra height * Revert number of columns
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import FilterableTable from '../../../../src/components/FilterableTable/FilterableTable';
|
||||
import FilterableTable, { MAX_COLUMNS_FOR_TABLE } from '../../../../src/components/FilterableTable/FilterableTable';
|
||||
|
||||
describe('FilterableTable', () => {
|
||||
const mockedProps = {
|
||||
@@ -36,10 +36,22 @@ describe('FilterableTable', () => {
|
||||
it('is valid element', () => {
|
||||
expect(React.isValidElement(<FilterableTable {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
it('renders a grid with 2 rows', () => {
|
||||
it('renders a grid with 2 Table rows', () => {
|
||||
expect(wrapper.find('.ReactVirtualized__Grid')).toHaveLength(1);
|
||||
expect(wrapper.find('.ReactVirtualized__Table__row')).toHaveLength(2);
|
||||
});
|
||||
it('renders a grid with 2 Grid rows for wide tables', () => {
|
||||
const wideTableColumns = MAX_COLUMNS_FOR_TABLE + 1;
|
||||
const wideTableMockedProps = {
|
||||
orderedColumnKeys: Array.from(Array(wideTableColumns), (_, x) => `col_${x}`),
|
||||
data: [
|
||||
Object.assign(...Array.from(Array(wideTableColumns)).map((val, x) => ({ [`col_${x}`]: x }))),
|
||||
],
|
||||
height: 500,
|
||||
};
|
||||
const wideTableWrapper = mount(<FilterableTable {...wideTableMockedProps} />);
|
||||
expect(wideTableWrapper.find('.ReactVirtualized__Grid')).toHaveLength(2);
|
||||
});
|
||||
it('filters on a string', () => {
|
||||
const props = {
|
||||
...mockedProps,
|
||||
|
||||
@@ -22,9 +22,11 @@ import JSONbig from 'json-bigint';
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
Column,
|
||||
Table,
|
||||
Grid,
|
||||
ScrollSync,
|
||||
SortDirection,
|
||||
SortIndicator,
|
||||
Table,
|
||||
} from 'react-virtualized';
|
||||
import { getTextDimension } from '@superset-ui/dimension';
|
||||
import TooltipWrapper from '../TooltipWrapper';
|
||||
@@ -34,6 +36,10 @@ function getTextWidth(text, font = '12px Roboto') {
|
||||
}
|
||||
|
||||
const SCROLL_BAR_HEIGHT = 15;
|
||||
const GRID_POSITION_ADJUSTMENT = 4;
|
||||
|
||||
// when more than MAX_COLUMNS_FOR_TABLE are returned, switch from table to grid view
|
||||
export const MAX_COLUMNS_FOR_TABLE = 50;
|
||||
|
||||
const propTypes = {
|
||||
orderedColumnKeys: PropTypes.array.isRequired,
|
||||
@@ -41,6 +47,7 @@ const propTypes = {
|
||||
height: PropTypes.number.isRequired,
|
||||
filterText: PropTypes.string,
|
||||
headerHeight: PropTypes.number,
|
||||
overscanColumnCount: PropTypes.number,
|
||||
overscanRowCount: PropTypes.number,
|
||||
rowHeight: PropTypes.number,
|
||||
striped: PropTypes.bool,
|
||||
@@ -50,6 +57,7 @@ const propTypes = {
|
||||
const defaultProps = {
|
||||
filterText: '',
|
||||
headerHeight: 32,
|
||||
overscanColumnCount: 10,
|
||||
overscanRowCount: 10,
|
||||
rowHeight: 32,
|
||||
striped: true,
|
||||
@@ -60,7 +68,11 @@ export default class FilterableTable extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.list = List(this.formatTableData(props.data));
|
||||
this.renderHeader = this.renderHeader.bind(this);
|
||||
this.renderGridCell = this.renderGridCell.bind(this);
|
||||
this.renderGridCellHeader = this.renderGridCellHeader.bind(this);
|
||||
this.renderGrid = this.renderGrid.bind(this);
|
||||
this.renderTableHeader = this.renderTableHeader.bind(this);
|
||||
this.renderTable = this.renderTable.bind(this);
|
||||
this.rowClassName = this.rowClassName.bind(this);
|
||||
this.sort = this.sort.bind(this);
|
||||
|
||||
@@ -75,6 +87,8 @@ export default class FilterableTable extends PureComponent {
|
||||
sortDirection: SortDirection.ASC,
|
||||
fitted: false,
|
||||
};
|
||||
|
||||
this.container = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -151,7 +165,7 @@ export default class FilterableTable extends PureComponent {
|
||||
this.setState({ sortBy, sortDirection });
|
||||
}
|
||||
|
||||
renderHeader({ dataKey, label, sortBy, sortDirection }) {
|
||||
renderTableHeader({ dataKey, label, sortBy, sortDirection }) {
|
||||
const className = this.props.expandedColumns.indexOf(label) > -1
|
||||
? 'header-style-disabled'
|
||||
: 'header-style';
|
||||
@@ -167,7 +181,92 @@ export default class FilterableTable extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
renderGridCellHeader({ columnIndex, key, style }) {
|
||||
const label = this.props.orderedColumnKeys[columnIndex];
|
||||
const className = this.props.expandedColumns.indexOf(label) > -1
|
||||
? 'header-style-disabled'
|
||||
: 'header-style';
|
||||
return (
|
||||
<TooltipWrapper key={key} label="header" tooltip={label}>
|
||||
<div
|
||||
style={{ ...style, top: style.top - GRID_POSITION_ADJUSTMENT }}
|
||||
className={`${className} grid-cell grid-header-cell`}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
renderGridCell({ columnIndex, key, rowIndex, style }) {
|
||||
const columnKey = this.props.orderedColumnKeys[columnIndex];
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
style={{ ...style, top: style.top - GRID_POSITION_ADJUSTMENT }}
|
||||
className={`grid-cell ${this.rowClassName({ index: rowIndex })}`}
|
||||
>
|
||||
{this.list.get(rowIndex)[columnKey]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderGrid() {
|
||||
const { orderedColumnKeys, overscanColumnCount, overscanRowCount, rowHeight } = this.props;
|
||||
|
||||
let { height } = this.props;
|
||||
let totalTableHeight = height;
|
||||
if (this.container && this.totalTableWidth > this.container.clientWidth) {
|
||||
// exclude the height of the horizontal scroll bar from the height of the table
|
||||
// and the height of the table container if the content overflows
|
||||
height -= SCROLL_BAR_HEIGHT;
|
||||
totalTableHeight -= SCROLL_BAR_HEIGHT;
|
||||
}
|
||||
|
||||
const getColumnWidth = ({ index }) => this.widthsForColumnsByKey[orderedColumnKeys[index]];
|
||||
|
||||
// fix height of filterable table
|
||||
return (
|
||||
<ScrollSync>
|
||||
{({ onScroll, scrollTop }) => (
|
||||
<div
|
||||
style={{ height }}
|
||||
className="filterable-table-container Table"
|
||||
ref={this.container}
|
||||
>
|
||||
<div className="LeftColumn">
|
||||
<Grid
|
||||
cellRenderer={this.renderGridCellHeader}
|
||||
columnCount={orderedColumnKeys.length}
|
||||
columnWidth={getColumnWidth}
|
||||
height={rowHeight}
|
||||
rowCount={1}
|
||||
rowHeight={rowHeight}
|
||||
scrollTop={scrollTop}
|
||||
width={this.totalTableWidth}
|
||||
/>
|
||||
</div>
|
||||
<div className="RightColumn">
|
||||
<Grid
|
||||
cellRenderer={this.renderGridCell}
|
||||
columnCount={orderedColumnKeys.length}
|
||||
columnWidth={getColumnWidth}
|
||||
height={totalTableHeight - rowHeight}
|
||||
onScroll={onScroll}
|
||||
overscanColumnCount={overscanColumnCount}
|
||||
overscanRowCount={overscanRowCount}
|
||||
rowCount={this.list.size}
|
||||
rowHeight={rowHeight}
|
||||
width={this.totalTableWidth}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ScrollSync>
|
||||
);
|
||||
}
|
||||
|
||||
renderTable() {
|
||||
const { sortBy, sortDirection } = this.state;
|
||||
const {
|
||||
filterText,
|
||||
@@ -203,7 +302,7 @@ export default class FilterableTable extends PureComponent {
|
||||
<div
|
||||
style={{ height }}
|
||||
className="filterable-table-container"
|
||||
ref={(ref) => { this.container = ref; }}
|
||||
ref={this.container}
|
||||
>
|
||||
{this.state.fitted &&
|
||||
<Table
|
||||
@@ -224,7 +323,7 @@ export default class FilterableTable extends PureComponent {
|
||||
<Column
|
||||
dataKey={columnKey}
|
||||
disableSort={false}
|
||||
headerRenderer={this.renderHeader}
|
||||
headerRenderer={this.renderTableHeader}
|
||||
width={this.widthsForColumnsByKey[columnKey]}
|
||||
label={columnKey}
|
||||
key={columnKey}
|
||||
@@ -235,6 +334,13 @@ export default class FilterableTable extends PureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.orderedColumnKeys.length > MAX_COLUMNS_FOR_TABLE) {
|
||||
return this.renderGrid();
|
||||
}
|
||||
return this.renderTable();
|
||||
}
|
||||
}
|
||||
|
||||
FilterableTable.propTypes = propTypes;
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.ReactVirtualized__Table__headerTruncatedText {
|
||||
.ReactVirtualized__Table__headerTruncatedText,
|
||||
.grid-header-cell {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
@@ -38,13 +39,17 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
.ReactVirtualized__Table__headerColumn,
|
||||
.ReactVirtualized__Table__rowColumn {
|
||||
.ReactVirtualized__Table__rowColumn,
|
||||
.grid-cell {
|
||||
min-width: 0px;
|
||||
border-right: 1px solid #ccc;
|
||||
align-self: center;
|
||||
padding: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.grid-header-cell {
|
||||
font-weight: 700;
|
||||
}
|
||||
.ReactVirtualized__Table__headerColumn:last-of-type,
|
||||
.ReactVirtualized__Table__rowColumn:last-of-type {
|
||||
border-right: 0px;
|
||||
|
||||
Reference in New Issue
Block a user