Skip to content

Commit

Permalink
[Lens] Enable Table Pagination (elastic#118557)
Browse files Browse the repository at this point in the history
  • Loading branch information
drewdaemon authored and TinLe committed Dec 22, 2021
1 parent dfaa08e commit e158c5d
Show file tree
Hide file tree
Showing 10 changed files with 479 additions and 31 deletions.
10 changes: 10 additions & 0 deletions x-pack/plugins/lens/common/expressions/datatable/datatable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ export interface SortingState {
direction: 'asc' | 'desc' | 'none';
}

export interface PagingState {
size: number;
enabled: boolean;
}

export interface DatatableArgs {
title: string;
description?: string;
columns: ColumnConfigArg[];
sortingColumnId: SortingState['columnId'];
sortingDirection: SortingState['direction'];
fitRowToContent?: boolean;
pageSize?: PagingState['size'];
}

export const getDatatable = (
Expand Down Expand Up @@ -62,6 +68,10 @@ export const getDatatable = (
types: ['boolean'],
help: '',
},
pageSize: {
types: ['number'],
help: '',
},
},
async fn(...args) {
/** Build optimization: prevent adding extra code into initial bundle **/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
export const LENS_EDIT_SORT_ACTION = 'sort';
export const LENS_EDIT_RESIZE_ACTION = 'resize';
export const LENS_TOGGLE_ACTION = 'toggle';
export const LENS_EDIT_PAGESIZE_ACTION = 'pagesize';
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
*/

import React from 'react';
import { ReactWrapper, shallow } from 'enzyme';
import { ReactWrapper, shallow, mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { mountWithIntl } from '@kbn/test/jest';
import { EuiDataGrid } from '@elastic/eui';
import { IAggType } from 'src/plugins/data/public';
import { IFieldFormat } from 'src/plugins/field_formats/common';
import {
FieldFormatParams,
IFieldFormat,
SerializedFieldFormat,
} from 'src/plugins/field_formats/common';
import { VisualizationContainer } from '../../visualization_container';
import { EmptyPlaceholder } from '../../shared_components';
import { LensIconChartDatatable } from '../../assets/chart_datatable';
Expand All @@ -20,6 +24,9 @@ import { LensMultiTable } from '../../../common';
import { DatatableProps } from '../../../common/expressions';
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
import { IUiSettingsClient } from 'kibana/public';
import { RenderMode } from 'src/plugins/expressions';

import { LENS_EDIT_PAGESIZE_ACTION } from './constants';

function sampleArgs() {
const indexPatternId = 'indexPatternId';
Expand Down Expand Up @@ -82,7 +89,7 @@ function sampleArgs() {
return { data, args };
}

function copyData(data: LensMultiTable): LensMultiTable {
function copyData<T>(data: T): T {
return JSON.parse(JSON.stringify(data));
}

Expand Down Expand Up @@ -658,4 +665,133 @@ describe('DatatableComponent', () => {

expect(wrapper.find('[data-test-subj="lnsDataTable-footer-c"]').exists()).toBe(false);
});

describe('pagination', () => {
it('enables pagination', async () => {
const { data, args } = sampleArgs();

args.pageSize = 10;

const wrapper = mount(
<DatatableComponent
data={data}
args={args}
formatFactory={(x) => x as IFieldFormat}
dispatchEvent={onDispatchEvent}
getType={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
renderMode="edit"
/>
);

const paginationConfig = wrapper.find(EuiDataGrid).prop('pagination');
expect(paginationConfig).toBeTruthy();
expect(paginationConfig?.pageIndex).toBe(0); // should start at 0
expect(paginationConfig?.pageSize).toBe(args.pageSize);

// trigger new page
const newIndex = 3;
act(() => paginationConfig?.onChangePage(newIndex));
wrapper.update();

const updatedConfig = wrapper.find(EuiDataGrid).prop('pagination');
expect(updatedConfig).toBeTruthy();
expect(updatedConfig?.pageIndex).toBe(newIndex);
expect(updatedConfig?.pageSize).toBe(args.pageSize);
});

it('disables pagination by default', async () => {
const { data, args } = sampleArgs();

delete args.pageSize;

const wrapper = mount(
<DatatableComponent
data={data}
args={args}
formatFactory={(x) => x as IFieldFormat}
dispatchEvent={onDispatchEvent}
getType={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
renderMode="edit"
/>
);

const paginationConfig = wrapper.find(EuiDataGrid).prop('pagination');
expect(paginationConfig).not.toBeTruthy();
});

it('dynamically toggles pagination', async () => {
const { data, args } = sampleArgs();

const argsWithPagination = copyData(args);
argsWithPagination.pageSize = 20;

const argsWithoutPagination = copyData(args);
delete argsWithoutPagination.pageSize;

const defaultProps = {
data,
formatFactory: (x?: SerializedFieldFormat<FieldFormatParams>) => x as IFieldFormat,
dispatchEvent: onDispatchEvent,
getType: jest.fn(),
paletteService: chartPluginMock.createPaletteRegistry(),
uiSettings: { get: jest.fn() } as unknown as IUiSettingsClient,
renderMode: 'edit' as RenderMode,
};

const wrapper = mount(
<DatatableComponent {...{ ...defaultProps, args: argsWithoutPagination }} />
);
wrapper.update();

expect(wrapper.find(EuiDataGrid).prop('pagination')).not.toBeTruthy();

wrapper.setProps({ args: argsWithPagination });
wrapper.update();

expect(wrapper.find(EuiDataGrid).prop('pagination')).toBeTruthy();

wrapper.setProps({ args: argsWithoutPagination });
wrapper.update();

expect(wrapper.find(EuiDataGrid).prop('pagination')).not.toBeTruthy();
});

it('dispatches event when page size changed', async () => {
const { data, args } = sampleArgs();

args.pageSize = 10;

const wrapper = mount(
<DatatableComponent
data={data}
args={args}
formatFactory={(x) => x as IFieldFormat}
dispatchEvent={onDispatchEvent}
getType={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient}
renderMode="edit"
/>
);

const paginationConfig = wrapper.find(EuiDataGrid).prop('pagination');
expect(paginationConfig).toBeTruthy();

const sizeToChangeTo = 100;
paginationConfig?.onChangeItemsPerPage(sizeToChangeTo);

expect(onDispatchEvent).toHaveBeenCalledTimes(1);
expect(onDispatchEvent).toHaveBeenCalledWith({
name: 'edit',
data: {
action: LENS_EDIT_PAGESIZE_ACTION,
size: sizeToChangeTo,
},
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import './table_basic.scss';

import React, { useCallback, useMemo, useRef, useState, useContext } from 'react';
import React, { useCallback, useMemo, useRef, useState, useContext, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect';
import {
Expand All @@ -30,6 +30,7 @@ import type {
LensSortAction,
LensResizeAction,
LensToggleAction,
LensPagesizeAction,
} from './types';
import { createGridColumns } from './columns';
import { createGridCell } from './cell_value';
Expand All @@ -50,6 +51,9 @@ const gridStyle: EuiDataGridStyle = {
header: 'underline',
};

export const DEFAULT_PAGE_SIZE = 10;
const PAGE_SIZE_OPTIONS = [DEFAULT_PAGE_SIZE, 20, 30, 50, 100];

export const DatatableComponent = (props: DatatableRenderProps) => {
const [firstTable] = Object.values(props.data.tables);

Expand All @@ -60,6 +64,22 @@ export const DatatableComponent = (props: DatatableRenderProps) => {
});
const [firstLocalTable, updateTable] = useState(firstTable);

// ** Pagination config
const [pagination, setPagination] = useState<{ pageIndex: number; pageSize: number } | undefined>(
undefined
);

useEffect(() => {
setPagination(
props.args.pageSize
? {
pageIndex: 0,
pageSize: props.args.pageSize ?? DEFAULT_PAGE_SIZE,
}
: undefined
);
}, [props.args.pageSize]);

useDeepCompareEffect(() => {
setColumnConfig({
columns: props.args.columns,
Expand Down Expand Up @@ -102,11 +122,35 @@ export const DatatableComponent = (props: DatatableRenderProps) => {
);

const onEditAction = useCallback(
(data: LensSortAction['data'] | LensResizeAction['data'] | LensToggleAction['data']) => {
(
data:
| LensSortAction['data']
| LensResizeAction['data']
| LensToggleAction['data']
| LensPagesizeAction['data']
) => {
dispatchEvent({ name: 'edit', data });
},
[dispatchEvent]
);

const onChangeItemsPerPage = useCallback(
(pageSize) => onEditAction({ action: 'pagesize', size: pageSize }),
[onEditAction]
);

// active page isn't persisted, so we manage this state locally
const onChangePage = useCallback(
(pageIndex) => {
setPagination((_pagination) => {
if (_pagination) {
return { pageSize: _pagination?.pageSize, pageIndex };
}
});
},
[setPagination]
);

const onRowContextMenuClick = useCallback(
(data: LensTableRowContextMenuEvent['data']) => {
dispatchEvent({ name: 'tableRowContextMenuClick', data });
Expand Down Expand Up @@ -346,6 +390,15 @@ export const DatatableComponent = (props: DatatableRenderProps) => {
}}
>
<EuiDataGrid
{
// we control the key when pagination is on to circumvent an EUI rendering bug
// see https://github.com/elastic/eui/issues/5391
...(pagination
? {
key: columns.map(({ id }) => id).join('-') + '-' + pagination.pageSize,
}
: {})
}
aria-label={dataGridAriaLabel}
data-test-subj="lnsDataTable"
rowHeightsOptions={
Expand All @@ -362,6 +415,14 @@ export const DatatableComponent = (props: DatatableRenderProps) => {
renderCellValue={renderCellValue}
gridStyle={gridStyle}
sorting={sorting}
pagination={
pagination && {
...pagination,
pageSizeOptions: PAGE_SIZE_OPTIONS,
onChangeItemsPerPage,
onChangePage,
}
}
onColumnResize={onColumnResize}
toolbarVisibility={false}
renderFooterCellValue={renderSummaryRow}
Expand Down
Loading

0 comments on commit e158c5d

Please sign in to comment.