You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ta...@apache.org on 2020/10/14 18:15:54 UTC

[incubator-superset] branch master updated: refactor: Implement TableView component (#11217)

This is an automated email from the ASF dual-hosted git repository.

tai 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 820fa47  refactor: Implement TableView component (#11217)
820fa47 is described below

commit 820fa473a7fc7571036b86a99ad66396d45e4c2f
Author: Kamil Gabryjelski <ka...@gmail.com>
AuthorDate: Wed Oct 14 20:15:34 2020 +0200

    refactor: Implement TableView component (#11217)
---
 .../components/AlteredSliceTag_spec.jsx            |   2 +-
 .../components/ListView/ListView_spec.jsx          |   4 +-
 .../components/TableView/TableView_spec.tsx        | 143 +++++++++++++++++++++
 .../src/components/ListView/ListView.tsx           |   3 +-
 superset-frontend/src/components/ListView/index.ts |   1 -
 .../src/components/TableView/TableView.tsx         | 137 ++++++++++++++++++++
 .../components/{ListView => TableView}/index.ts    |   6 +-
 .../{ListView/index.ts => TableView/types.ts}      |   9 +-
 .../{ListView => dataViewCommon}/Pagination.tsx    |   0
 .../TableCollection.tsx                            |  11 +-
 .../{ListView => dataViewCommon}/index.ts          |   7 +-
 11 files changed, 299 insertions(+), 24 deletions(-)

diff --git a/superset-frontend/spec/javascripts/components/AlteredSliceTag_spec.jsx b/superset-frontend/spec/javascripts/components/AlteredSliceTag_spec.jsx
index 8cb313c..41b4fe9 100644
--- a/superset-frontend/spec/javascripts/components/AlteredSliceTag_spec.jsx
+++ b/superset-frontend/spec/javascripts/components/AlteredSliceTag_spec.jsx
@@ -24,7 +24,7 @@ import AlteredSliceTag from 'src/components/AlteredSliceTag';
 import ModalTrigger from 'src/components/ModalTrigger';
 import TooltipWrapper from 'src/components/TooltipWrapper';
 import ListView from 'src/components/ListView';
-import TableCollection from 'src/components/ListView/TableCollection';
+import TableCollection from 'src/components/dataViewCommon/TableCollection';
 
 import {
   defaultProps,
diff --git a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
index 118e01b..f8bed6b 100644
--- a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
+++ b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
@@ -29,9 +29,9 @@ import { CardSortSelect } from 'src/components/ListView/CardSortSelect';
 import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
 import ListView from 'src/components/ListView/ListView';
 import ListViewFilters from 'src/components/ListView/Filters';
-import ListViewPagination from 'src/components/ListView/Pagination';
+import ListViewPagination from 'src/components/dataViewCommon/Pagination';
+import TableCollection from 'src/components/dataViewCommon/TableCollection';
 import Pagination from 'src/components/Pagination';
-import TableCollection from 'src/components/ListView/TableCollection';
 
 import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
 
diff --git a/superset-frontend/spec/javascripts/components/TableView/TableView_spec.tsx b/superset-frontend/spec/javascripts/components/TableView/TableView_spec.tsx
new file mode 100644
index 0000000..ee9b1ae
--- /dev/null
+++ b/superset-frontend/spec/javascripts/components/TableView/TableView_spec.tsx
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { mount } from 'enzyme';
+import { supersetTheme, ThemeProvider } from '@superset-ui/core';
+import Pagination from 'src/components/Pagination';
+import TableView from '../../../../src/components/TableView';
+import { TableViewProps } from '../../../../src/components/TableView/TableView';
+
+const mockedProps: TableViewProps = {
+  columns: [
+    {
+      accessor: 'id',
+      Header: 'ID',
+      sortable: true,
+    },
+    {
+      accessor: 'age',
+      Header: 'Age',
+    },
+    {
+      accessor: 'name',
+      Header: 'Name',
+    },
+  ],
+  data: [
+    { id: 1, age: 20, name: 'Emily' },
+    { id: 2, age: 10, name: 'Kate' },
+    { id: 3, age: 40, name: 'Anna' },
+    { id: 4, age: 30, name: 'Jane' },
+  ],
+  pageSize: 1,
+};
+
+const factory = (props = mockedProps) =>
+  mount(<TableView {...props} />, {
+    wrappingComponent: ThemeProvider,
+    wrappingComponentProps: { theme: supersetTheme },
+  });
+
+describe('TableView', () => {
+  it('render a table, columns and rows', () => {
+    const pageSize = 10;
+    const wrapper = factory({ ...mockedProps, pageSize });
+    expect(wrapper.find('table')).toExist();
+    expect(wrapper.find('table th')).toHaveLength(mockedProps.columns.length);
+    expect(wrapper.find('table tbody tr')).toHaveLength(
+      Math.min(mockedProps.data.length, pageSize),
+    );
+  });
+
+  it('renders pagination controls', () => {
+    const wrapper = factory();
+    expect(wrapper.find(Pagination)).toExist();
+    expect(wrapper.find(Pagination.Prev)).toExist();
+    expect(wrapper.find(Pagination.Item)).toExist();
+    expect(wrapper.find(Pagination.Next)).toExist();
+  });
+
+  it("doesn't render pagination when pagination is disabled", () => {
+    const wrapper = factory({ ...mockedProps, withPagination: false });
+    expect(wrapper.find(Pagination)).not.toExist();
+  });
+
+  it("doesn't render pagination when fewer rows than page size", () => {
+    const pageSize = 999;
+    expect(pageSize).toBeGreaterThan(mockedProps.data.length);
+
+    const wrapper = factory({ ...mockedProps, pageSize });
+    expect(wrapper.find(Pagination)).not.toExist();
+  });
+
+  it('changes page when button is clicked', () => {
+    const pageSize = 3;
+    const dataLength = mockedProps.data.length;
+
+    expect(dataLength).toBeGreaterThan(pageSize);
+    const wrapper = factory({ ...mockedProps, pageSize });
+
+    expect(wrapper.find('table tbody tr')).toHaveLength(pageSize);
+
+    wrapper.find('NEXT_PAGE_LINK span').simulate('click');
+    expect(wrapper.find('table tbody tr')).toHaveLength(
+      Math.min(dataLength - pageSize, pageSize),
+    );
+  });
+
+  it('sorts by age when header cell is clicked', () => {
+    const wrapper = factory();
+    expect(wrapper.find('table tbody td Cell').at(1).props().value).toEqual(20);
+
+    // sort ascending
+    wrapper.find('table thead th').at(1).simulate('click');
+    expect(wrapper.find('table tbody td Cell').at(1).props().value).toEqual(10);
+
+    // sort descending
+    wrapper.find('table thead th').at(1).simulate('click');
+    expect(wrapper.find('table tbody td Cell').at(1).props().value).toEqual(40);
+
+    // no sort
+    wrapper.find('table thead th').at(1).simulate('click');
+    expect(wrapper.find('table tbody td Cell').at(1).props().value).toEqual(20);
+  });
+
+  it('sorts by data when initialSortBy is passed', () => {
+    let wrapper = factory();
+    expect(wrapper.find('table tbody td Cell').at(2).props().value).toEqual(
+      'Emily',
+    );
+
+    wrapper = factory({
+      ...mockedProps,
+      initialSortBy: [{ id: 'name', desc: true }],
+    });
+    expect(wrapper.find('table tbody td Cell').at(2).props().value).toEqual(
+      'Kate',
+    );
+
+    wrapper = factory({
+      ...mockedProps,
+      initialSortBy: [{ id: 'name', desc: false }],
+    });
+    expect(wrapper.find('table tbody td Cell').at(2).props().value).toEqual(
+      'Anna',
+    );
+  });
+});
diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx
index e79261c..e9e6581 100644
--- a/superset-frontend/src/components/ListView/ListView.tsx
+++ b/superset-frontend/src/components/ListView/ListView.tsx
@@ -24,9 +24,8 @@ import cx from 'classnames';
 import Button from 'src/components/Button';
 import Icon from 'src/components/Icon';
 import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
-import TableCollection from './TableCollection';
+import { TableCollection, Pagination } from 'src/components/dataViewCommon';
 import CardCollection from './CardCollection';
-import Pagination from './Pagination';
 import FilterControls from './Filters';
 import { CardSortSelect } from './CardSortSelect';
 import {
diff --git a/superset-frontend/src/components/ListView/index.ts b/superset-frontend/src/components/ListView/index.ts
index 30be8a1..0d6753f 100644
--- a/superset-frontend/src/components/ListView/index.ts
+++ b/superset-frontend/src/components/ListView/index.ts
@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 export * from './ListView';
 export * from './types';
 
diff --git a/superset-frontend/src/components/TableView/TableView.tsx b/superset-frontend/src/components/TableView/TableView.tsx
new file mode 100644
index 0000000..3eb11a8
--- /dev/null
+++ b/superset-frontend/src/components/TableView/TableView.tsx
@@ -0,0 +1,137 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { styled, t } from '@superset-ui/core';
+import { useFilters, usePagination, useSortBy, useTable } from 'react-table';
+import { Empty } from 'src/common/components';
+import { TableCollection, Pagination } from 'src/components/dataViewCommon';
+import { SortColumns } from './types';
+
+const DEFAULT_PAGE_SIZE = 10;
+
+export interface TableViewProps {
+  columns: any[];
+  data: any[];
+  pageSize?: number;
+  initialPageIndex?: number;
+  initialSortBy?: SortColumns;
+  loading?: boolean;
+  withPagination?: boolean;
+  className?: string;
+}
+
+const EmptyWrapper = styled.div`
+  margin: ${({ theme }) => theme.gridUnit * 40}px 0;
+`;
+
+const TableViewStyles = styled.div`
+  .table-cell.table-cell {
+    vertical-align: top;
+  }
+
+  .pagination-container {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  }
+
+  .row-count-container {
+    margin-top: ${({ theme }) => theme.gridUnit * 2}px;
+    color: ${({ theme }) => theme.colors.grayscale.base};
+  }
+`;
+
+const TableView = ({
+  columns,
+  data,
+  pageSize: initialPageSize,
+  initialPageIndex,
+  initialSortBy = [],
+  loading = false,
+  withPagination = true,
+  ...props
+}: TableViewProps) => {
+  const initialState = {
+    pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
+    pageIndex: initialPageIndex ?? 0,
+    sortBy: initialSortBy,
+  };
+
+  const {
+    getTableProps,
+    getTableBodyProps,
+    headerGroups,
+    page,
+    rows,
+    prepareRow,
+    pageCount,
+    gotoPage,
+    state: { pageIndex, pageSize },
+  } = useTable(
+    {
+      columns,
+      data,
+      initialState,
+    },
+    useFilters,
+    useSortBy,
+    usePagination,
+  );
+
+  const content = withPagination ? page : rows;
+  return (
+    <TableViewStyles {...props}>
+      <TableCollection
+        getTableProps={getTableProps}
+        getTableBodyProps={getTableBodyProps}
+        prepareRow={prepareRow}
+        headerGroups={headerGroups}
+        rows={content}
+        columns={columns}
+        loading={loading}
+      />
+      {!loading && content.length === 0 && (
+        <EmptyWrapper>
+          <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
+        </EmptyWrapper>
+      )}
+      {pageCount > 1 && withPagination && (
+        <div className="pagination-container">
+          <Pagination
+            totalPages={pageCount || 0}
+            currentPage={pageCount ? pageIndex + 1 : 0}
+            onChange={(p: number) => gotoPage(p - 1)}
+            hideFirstAndLastPageLinks
+          />
+          <div className="row-count-container">
+            {!loading &&
+              t(
+                '%s-%s of %s',
+                pageSize * pageIndex + (page.length && 1),
+                pageSize * pageIndex + page.length,
+                data.length,
+              )}
+          </div>
+        </div>
+      )}
+    </TableViewStyles>
+  );
+};
+
+export default TableView;
diff --git a/superset-frontend/src/components/ListView/index.ts b/superset-frontend/src/components/TableView/index.ts
similarity index 89%
copy from superset-frontend/src/components/ListView/index.ts
copy to superset-frontend/src/components/TableView/index.ts
index 30be8a1..88d57c4 100644
--- a/superset-frontend/src/components/ListView/index.ts
+++ b/superset-frontend/src/components/TableView/index.ts
@@ -16,8 +16,4 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-export * from './ListView';
-export * from './types';
-
-export { default } from './ListView';
+export { default } from './TableView';
diff --git a/superset-frontend/src/components/ListView/index.ts b/superset-frontend/src/components/TableView/types.ts
similarity index 88%
copy from superset-frontend/src/components/ListView/index.ts
copy to superset-frontend/src/components/TableView/types.ts
index 30be8a1..974cb24 100644
--- a/superset-frontend/src/components/ListView/index.ts
+++ b/superset-frontend/src/components/TableView/types.ts
@@ -16,8 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+export interface SortColumn {
+  id: string;
+  desc?: boolean;
+}
 
-export * from './ListView';
-export * from './types';
-
-export { default } from './ListView';
+export type SortColumns = SortColumn[];
diff --git a/superset-frontend/src/components/ListView/Pagination.tsx b/superset-frontend/src/components/dataViewCommon/Pagination.tsx
similarity index 100%
rename from superset-frontend/src/components/ListView/Pagination.tsx
rename to superset-frontend/src/components/dataViewCommon/Pagination.tsx
diff --git a/superset-frontend/src/components/ListView/TableCollection.tsx b/superset-frontend/src/components/dataViewCommon/TableCollection.tsx
similarity index 96%
rename from superset-frontend/src/components/ListView/TableCollection.tsx
rename to superset-frontend/src/components/dataViewCommon/TableCollection.tsx
index db79937..eb63825 100644
--- a/superset-frontend/src/components/ListView/TableCollection.tsx
+++ b/superset-frontend/src/components/dataViewCommon/TableCollection.tsx
@@ -33,8 +33,7 @@ interface TableCollectionProps {
   highlightRowId?: number;
 }
 
-const Table = styled.table`
-  background-color: white;
+export const Table = styled.table`
   border-collapse: separate;
   border-radius: ${({ theme }) => theme.borderRadius}px;
 
@@ -199,6 +198,8 @@ const Table = styled.table`
   }
 `;
 
+Table.displayName = 'table';
+
 export default function TableCollection({
   getTableProps,
   getTableBodyProps,
@@ -267,13 +268,15 @@ export default function TableCollection({
         {rows.length > 0 &&
           rows.map(row => {
             prepareRow(row);
+            // @ts-ignore
+            const rowId = row.original.id;
             return (
               <tr
                 {...row.getRowProps()}
                 className={cx('table-row', {
                   'table-row-selected':
-                    // @ts-ignore
-                    row.isSelected || row.original.id === highlightRowId,
+                    row.isSelected ||
+                    (typeof rowId !== 'undefined' && rowId === highlightRowId),
                 })}
               >
                 {row.cells.map(cell => {
diff --git a/superset-frontend/src/components/ListView/index.ts b/superset-frontend/src/components/dataViewCommon/index.ts
similarity index 87%
copy from superset-frontend/src/components/ListView/index.ts
copy to superset-frontend/src/components/dataViewCommon/index.ts
index 30be8a1..e552e5c 100644
--- a/superset-frontend/src/components/ListView/index.ts
+++ b/superset-frontend/src/components/dataViewCommon/index.ts
@@ -16,8 +16,5 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-export * from './ListView';
-export * from './types';
-
-export { default } from './ListView';
+export { default as Pagination } from './Pagination';
+export { default as TableCollection } from './TableCollection';