From 820fa473a7fc7571036b86a99ad66396d45e4c2f Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Wed, 14 Oct 2020 20:15:34 +0200 Subject: [PATCH] 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 +- .../src/components/ListView/index.ts | 1 - .../src/components/TableView/TableView.tsx | 137 +++++++++++++++++ .../src/components/TableView/index.ts | 19 +++ .../src/components/TableView/types.ts | 24 +++ .../Pagination.tsx | 0 .../TableCollection.tsx | 11 +- .../src/components/dataViewCommon/index.ts | 20 +++ 11 files changed, 354 insertions(+), 10 deletions(-) create mode 100644 superset-frontend/spec/javascripts/components/TableView/TableView_spec.tsx create mode 100644 superset-frontend/src/components/TableView/TableView.tsx create mode 100644 superset-frontend/src/components/TableView/index.ts create mode 100644 superset-frontend/src/components/TableView/types.ts rename superset-frontend/src/components/{ListView => dataViewCommon}/Pagination.tsx (100%) rename superset-frontend/src/components/{ListView => dataViewCommon}/TableCollection.tsx (96%) create mode 100644 superset-frontend/src/components/dataViewCommon/index.ts diff --git a/superset-frontend/spec/javascripts/components/AlteredSliceTag_spec.jsx b/superset-frontend/spec/javascripts/components/AlteredSliceTag_spec.jsx index 8cb313c70..41b4fe97a 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 118e01bbd..f8bed6b13 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 000000000..ee9b1ae69 --- /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(, { + 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 e79261c5b..e9e65812d 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 30be8a1c8..0d6753fc6 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 000000000..3eb11a8b3 --- /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 ( + + + {!loading && content.length === 0 && ( + + + + )} + {pageCount > 1 && withPagination && ( +
+ gotoPage(p - 1)} + hideFirstAndLastPageLinks + /> +
+ {!loading && + t( + '%s-%s of %s', + pageSize * pageIndex + (page.length && 1), + pageSize * pageIndex + page.length, + data.length, + )} +
+
+ )} +
+ ); +}; + +export default TableView; diff --git a/superset-frontend/src/components/TableView/index.ts b/superset-frontend/src/components/TableView/index.ts new file mode 100644 index 000000000..88d57c481 --- /dev/null +++ b/superset-frontend/src/components/TableView/index.ts @@ -0,0 +1,19 @@ +/** + * 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. + */ +export { default } from './TableView'; diff --git a/superset-frontend/src/components/TableView/types.ts b/superset-frontend/src/components/TableView/types.ts new file mode 100644 index 000000000..974cb2496 --- /dev/null +++ b/superset-frontend/src/components/TableView/types.ts @@ -0,0 +1,24 @@ +/** + * 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. + */ +export interface SortColumn { + id: string; + desc?: boolean; +} + +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 db799379b..eb638253e 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 ( {row.cells.map(cell => { diff --git a/superset-frontend/src/components/dataViewCommon/index.ts b/superset-frontend/src/components/dataViewCommon/index.ts new file mode 100644 index 000000000..e552e5c9d --- /dev/null +++ b/superset-frontend/src/components/dataViewCommon/index.ts @@ -0,0 +1,20 @@ +/** + * 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. + */ +export { default as Pagination } from './Pagination'; +export { default as TableCollection } from './TableCollection';