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 <robe...@dealmeida.net>
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;

Reply via email to