This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to refs/heads/master by this push:
new 5cf0633 Render columns dynamically on wide tables (#7693)
5cf0633 is described below
commit 5cf06331fe182207bdc37ffcea0de00185c1def1
Author: Beto Dealmeida <[email protected]>
AuthorDate: Wed Jun 12 10:09:04 2019 -0700
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
---
.../FilterableTable/FilterableTable_spec.jsx | 16 ++-
.../components/FilterableTable/FilterableTable.jsx | 118 +++++++++++++++++++--
.../FilterableTable/FilterableTableStyles.css | 9 +-
3 files changed, 133 insertions(+), 10 deletions(-)
diff --git
a/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx
b/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx
index e84fd16..73837b6 100644
---
a/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx
+++
b/superset/assets/spec/javascripts/components/FilterableTable/FilterableTable_spec.jsx
@@ -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,
diff --git a/superset/assets/src/components/FilterableTable/FilterableTable.jsx
b/superset/assets/src/components/FilterableTable/FilterableTable.jsx
index 5b94d51..fa1fc44 100644
--- a/superset/assets/src/components/FilterableTable/FilterableTable.jsx
+++ b/superset/assets/src/components/FilterableTable/FilterableTable.jsx
@@ -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;
diff --git
a/superset/assets/src/components/FilterableTable/FilterableTableStyles.css
b/superset/assets/src/components/FilterableTable/FilterableTableStyles.css
index c890654..3db6e3a 100644
--- a/superset/assets/src/components/FilterableTable/FilterableTableStyles.css
+++ b/superset/assets/src/components/FilterableTable/FilterableTableStyles.css
@@ -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;