Add pagination to data explorer
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Fri, 22 Jun 2018 10:13:00 +0000 (12:13 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Fri, 22 Jun 2018 10:13:00 +0000 (12:13 +0200)
Feature #13633

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

src/components/data-explorer/data-explorer.test.tsx
src/components/data-explorer/data-explorer.tsx
src/views-components/project-explorer/project-explorer.tsx

index 8ced6cb32a6083beaf82dfc7ab838cc4b8d3f694..152d03697040687d9cee7afe3a715a542a6d3d61 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from "react";
-import { mount, configure } from "enzyme";
+import { configure, mount } from "enzyme";
 import * as Adapter from 'enzyme-adapter-react-16';
 
 import DataExplorer from "./data-explorer";
@@ -11,10 +11,12 @@ import ContextMenu from "../context-menu/context-menu";
 import ColumnSelector from "../column-selector/column-selector";
 import DataTable from "../data-table/data-table";
 import SearchInput from "../search-input/search-input";
+import { TablePagination } from "@material-ui/core";
 
 configure({ adapter: new Adapter() });
 
 describe("<DataExplorer />", () => {
+
     it("communicates with <ContextMenu/>", () => {
         const onContextAction = jest.fn();
         const dataExplorer = mount(<DataExplorer
@@ -30,13 +32,13 @@ describe("<DataExplorer />", () => {
         dataExplorer.find(ContextMenu).prop("onActionClick")({ name: "Action 1", icon: "" });
         expect(onContextAction).toHaveBeenCalledWith({ name: "Action 1", icon: "" }, "Item 1");
     });
-    
+
     it("communicates with <SearchInput/>", () => {
         const onSearch = jest.fn();
         const dataExplorer = mount(<DataExplorer
             {...mockDataExplorerProps()}
             searchValue="search value"
-            onSearch={onSearch}/>);
+            onSearch={onSearch} />);
         expect(dataExplorer.find(SearchInput).prop("value")).toEqual("search value");
         dataExplorer.find(SearchInput).prop("onSearch")("new value");
         expect(onSearch).toHaveBeenCalledWith("new value");
@@ -78,6 +80,35 @@ describe("<DataExplorer />", () => {
         expect(onSortToggle).toHaveBeenCalledWith("sortToggle");
         expect(onRowClick).toHaveBeenCalledWith("rowClick");
     });
+
+    it("renders <TablePagination/> if items list is not empty", () => {
+        const onChangePage = jest.fn();
+        const onChangeRowsPerPage = jest.fn();
+        const dataExplorer = mount(<DataExplorer
+            {...mockDataExplorerProps()}
+            items={["Item 1"]}
+        />);
+        expect(dataExplorer.find(TablePagination)).toHaveLength(1);
+    });
+
+    it("communicates with <TablePagination/>", () => {
+        const onChangePage = jest.fn();
+        const onChangeRowsPerPage = jest.fn();
+        const dataExplorer = mount(<DataExplorer
+            {...mockDataExplorerProps()}
+            items={["Item 1"]}
+            page={10}
+            rowsPerPage={50}
+            onChangePage={onChangePage}
+            onChangeRowsPerPage={onChangeRowsPerPage}
+        />);
+        expect(dataExplorer.find(TablePagination).prop("page")).toEqual(10);
+        expect(dataExplorer.find(TablePagination).prop("rowsPerPage")).toEqual(50);
+        dataExplorer.find(TablePagination).prop("onChangePage")(undefined, 6);
+        dataExplorer.find(TablePagination).prop("onChangeRowsPerPage")({ target: { value: 10 } });
+        expect(onChangePage).toHaveBeenCalledWith(6);
+        expect(onChangeRowsPerPage).toHaveBeenCalledWith(10);
+    });
 });
 
 const mockDataExplorerProps = () => ({
@@ -85,10 +116,14 @@ const mockDataExplorerProps = () => ({
     items: [],
     contextActions: [],
     searchValue: "",
+    page: 0,
+    rowsPerPage: 0,
     onSearch: jest.fn(),
     onFiltersChange: jest.fn(),
     onSortToggle: jest.fn(),
     onRowClick: jest.fn(),
     onColumnToggle: jest.fn(),
-    onContextAction: jest.fn()
+    onContextAction: jest.fn(),
+    onChangePage: jest.fn(),
+    onChangeRowsPerPage: jest.fn()
 });
\ No newline at end of file
index cf9886c3bcedd1d8a9aa39ea9b434d444d264702..d797b897645d5d6f708d88c540c03e7e3fe526b5 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, Theme, WithStyles } from '@material-ui/core';
+import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, Theme, WithStyles, TablePagination, Table } from '@material-ui/core';
 import ContextMenu, { ContextMenuActionGroup, ContextMenuAction } from "../../components/context-menu/context-menu";
 import ColumnSelector from "../../components/column-selector/column-selector";
 import DataTable from "../../components/data-table/data-table";
@@ -17,12 +17,16 @@ interface DataExplorerProps<T> {
     columns: Array<DataColumn<T>>;
     contextActions: ContextMenuActionGroup[];
     searchValue: string;
+    rowsPerPage: number;
+    page: number;
     onSearch: (value: string) => void;
     onRowClick: (item: T) => void;
     onColumnToggle: (column: DataColumn<T>) => void;
     onContextAction: (action: ContextMenuAction, item: T) => void;
     onSortToggle: (column: DataColumn<T>) => void;
     onFiltersChange: (filters: DataTableFilterItem[], column: DataColumn<T>) => void;
+    onChangePage: (page: number) => void;
+    onChangeRowsPerPage: (rowsPerPage: number) => void;
 }
 
 interface DataExplorerState<T> {
@@ -63,7 +67,19 @@ class DataExplorer<T> extends React.Component<DataExplorerProps<T> & WithStyles<
                 onRowContextMenu={this.openContextMenu}
                 onFiltersChange={this.props.onFiltersChange}
                 onSortToggle={this.props.onSortToggle} />
-            <Toolbar />
+            <Toolbar>
+                {this.props.items.length > 0 &&
+                    <Grid container justify="flex-end">
+                        <TablePagination
+                            count={this.props.items.length}
+                            rowsPerPage={this.props.rowsPerPage}
+                            page={this.props.page}
+                            onChangePage={this.changePage}
+                            onChangeRowsPerPage={this.changeRowsPerPage}
+                            component="div"
+                        />
+                    </Grid>}
+            </Toolbar>
         </Paper>;
     }
 
@@ -89,6 +105,14 @@ class DataExplorer<T> extends React.Component<DataExplorerProps<T> & WithStyles<
         }
     }
 
+    changePage = (event: React.MouseEvent<HTMLButtonElement> | null, page: number) => {
+        this.props.onChangePage(page);
+    }
+
+    changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = (event) => {
+        this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
+    }
+
 }
 
 type CssRules = "searchBox" | "toolbar";
index 4757440cc0d9757f8c362bb84ffaf86f74701ad1..4931c09a5149b67c1b3f5b300a6bf3b8798da093 100644 (file)
@@ -28,11 +28,15 @@ interface ProjectExplorerProps {
 interface ProjectExplorerState {
     columns: Array<DataColumn<ProjectExplorerItem>>;
     searchValue: string;
+    page: number;
+    rowsPerPage: number;
 }
 
 class ProjectExplorer extends React.Component<ProjectExplorerProps, ProjectExplorerState> {
     state: ProjectExplorerState = {
         searchValue: "",
+        page: 0,
+        rowsPerPage: 10,
         columns: [{
             name: "Name",
             selected: true,
@@ -106,12 +110,16 @@ class ProjectExplorer extends React.Component<ProjectExplorerProps, ProjectExplo
             columns={this.state.columns}
             contextActions={this.contextMenuActions}
             searchValue={this.state.searchValue}
+            page={this.state.page}
+            rowsPerPage={this.state.rowsPerPage}
             onColumnToggle={this.toggleColumn}
             onFiltersChange={this.changeFilters}
             onRowClick={console.log}
             onSortToggle={this.toggleSort}
             onSearch={this.search}
-            onContextAction={this.executeAction} />;
+            onContextAction={this.executeAction}
+            onChangePage={this.changePage}
+            onChangeRowsPerPage={this.changeRowsPerPage} />;
     }
 
     toggleColumn = (toggledColumn: DataColumn<ProjectExplorerItem>) => {
@@ -151,6 +159,14 @@ class ProjectExplorer extends React.Component<ProjectExplorerProps, ProjectExplo
     search = (searchValue: string) => {
         this.setState({ searchValue });
     }
+
+    changePage = (page: number) => {
+        this.setState({ page });
+    }
+
+    changeRowsPerPage = (rowsPerPage: number) => {
+        this.setState({ rowsPerPage });
+    }
 }
 
 const renderName = (item: ProjectExplorerItem) =>