mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[ML] Transforms list: persist pagination through refresh interval (#76786)
* use basic table in transforms to persist pagination * simplify state and update types
This commit is contained in:
parent
a0495090c0
commit
e985f1a272
3 changed files with 379 additions and 186 deletions
|
@ -4,31 +4,30 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { MouseEventHandler, FC, useContext, useState } from 'react';
|
||||
import React, { MouseEventHandler, FC, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
Direction,
|
||||
EuiBadge,
|
||||
EuiBasicTable,
|
||||
EuiBasicTableProps,
|
||||
EuiButtonEmpty,
|
||||
EuiButtonIcon,
|
||||
EuiCallOut,
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiInMemoryTable,
|
||||
EuiSearchBarProps,
|
||||
EuiSearchBar,
|
||||
EuiSpacer,
|
||||
EuiPopover,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { TransformId, TRANSFORM_STATE } from '../../../../../../common';
|
||||
import { TransformId } from '../../../../../../common';
|
||||
|
||||
import {
|
||||
useRefreshTransformList,
|
||||
TransformListRow,
|
||||
TRANSFORM_MODE,
|
||||
TRANSFORM_LIST_COLUMN,
|
||||
} from '../../../../common';
|
||||
import { useStopTransforms } from '../../../../hooks';
|
||||
|
@ -45,9 +44,11 @@ import {
|
|||
import { useStartAction, StartActionName, StartActionModal } from '../action_start';
|
||||
import { StopActionName } from '../action_stop';
|
||||
|
||||
import { ItemIdToExpandedRowMap, Clause, TermClause, FieldClause, Value } from './common';
|
||||
import { getTaskStateBadge, useColumns } from './use_columns';
|
||||
import { ItemIdToExpandedRowMap } from './common';
|
||||
import { useColumns } from './use_columns';
|
||||
import { ExpandedRow } from './expanded_row';
|
||||
import { TransformSearchBar, filterTransforms } from './transform_search_bar';
|
||||
import { useTableSettings } from './use_table_settings';
|
||||
|
||||
function getItemIdToExpandedRowMap(
|
||||
itemIds: TransformId[],
|
||||
|
@ -62,14 +63,6 @@ function getItemIdToExpandedRowMap(
|
|||
}, {} as ItemIdToExpandedRowMap);
|
||||
}
|
||||
|
||||
function stringMatch(str: string | undefined, substr: any) {
|
||||
return (
|
||||
typeof str === 'string' &&
|
||||
typeof substr === 'string' &&
|
||||
(str.toLowerCase().match(substr.toLowerCase()) === null) === false
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
errorMessage: any;
|
||||
isInitialized: boolean;
|
||||
|
@ -88,24 +81,14 @@ export const TransformList: FC<Props> = ({
|
|||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { refresh } = useRefreshTransformList({ isLoading: setIsLoading });
|
||||
|
||||
const [filterActive, setFilterActive] = useState(false);
|
||||
|
||||
const [searchQueryText, setSearchQueryText] = useState<string>('');
|
||||
const [filteredTransforms, setFilteredTransforms] = useState<TransformListRow[]>([]);
|
||||
const [expandedRowItemIds, setExpandedRowItemIds] = useState<TransformId[]>([]);
|
||||
|
||||
const [transformSelection, setTransformSelection] = useState<TransformListRow[]>([]);
|
||||
const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(false);
|
||||
const bulkStartAction = useStartAction(false);
|
||||
const bulkDeleteAction = useDeleteAction(false);
|
||||
|
||||
const [searchError, setSearchError] = useState<any>(undefined);
|
||||
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const [sortField, setSortField] = useState<string>(TRANSFORM_LIST_COLUMN.ID);
|
||||
const [sortDirection, setSortDirection] = useState<Direction>('asc');
|
||||
|
||||
const stopTransforms = useStopTransforms();
|
||||
|
||||
const { capabilities } = useContext(AuthorizationContext);
|
||||
|
@ -114,92 +97,43 @@ export const TransformList: FC<Props> = ({
|
|||
!capabilities.canPreviewTransform ||
|
||||
!capabilities.canStartStopTransform;
|
||||
|
||||
const onQueryChange = ({
|
||||
query,
|
||||
error,
|
||||
}: Parameters<NonNullable<EuiSearchBarProps['onChange']>>[0]) => {
|
||||
if (error) {
|
||||
setSearchError(error.message);
|
||||
} else {
|
||||
let clauses: Clause[] = [];
|
||||
if (query && query.ast !== undefined && query.ast.clauses !== undefined) {
|
||||
clauses = query.ast.clauses;
|
||||
}
|
||||
if (clauses.length > 0) {
|
||||
setFilterActive(true);
|
||||
filterTransforms(clauses as Array<TermClause | FieldClause>);
|
||||
} else {
|
||||
setFilterActive(false);
|
||||
}
|
||||
setSearchError(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const filterTransforms = (clauses: Array<TermClause | FieldClause>) => {
|
||||
setIsLoading(true);
|
||||
// keep count of the number of matches we make as we're looping over the clauses
|
||||
// we only want to return transforms which match all clauses, i.e. each search term is ANDed
|
||||
// { transform-one: { transform: { id: transform-one, config: {}, state: {}, ... }, count: 0 }, transform-two: {...} }
|
||||
const matches: Record<string, any> = transforms.reduce((p: Record<string, any>, c) => {
|
||||
p[c.id] = {
|
||||
transform: c,
|
||||
count: 0,
|
||||
};
|
||||
return p;
|
||||
}, {});
|
||||
|
||||
clauses.forEach((c) => {
|
||||
// the search term could be negated with a minus, e.g. -bananas
|
||||
const bool = c.match === 'must';
|
||||
let ts = [];
|
||||
|
||||
if (c.type === 'term') {
|
||||
// filter term based clauses, e.g. bananas
|
||||
// match on ID and description
|
||||
// if the term has been negated, AND the matches
|
||||
if (bool === true) {
|
||||
ts = transforms.filter(
|
||||
(transform) =>
|
||||
stringMatch(transform.id, c.value) === bool ||
|
||||
stringMatch(transform.config.description, c.value) === bool
|
||||
);
|
||||
} else {
|
||||
ts = transforms.filter(
|
||||
(transform) =>
|
||||
stringMatch(transform.id, c.value) === bool &&
|
||||
stringMatch(transform.config.description, c.value) === bool
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// filter other clauses, i.e. the mode and status filters
|
||||
if (Array.isArray(c.value)) {
|
||||
// the status value is an array of string(s) e.g. ['failed', 'stopped']
|
||||
ts = transforms.filter((transform) =>
|
||||
(c.value as Value[]).includes(transform.stats.state)
|
||||
);
|
||||
} else {
|
||||
ts = transforms.filter((transform) => transform.mode === c.value);
|
||||
}
|
||||
}
|
||||
|
||||
ts.forEach((t) => matches[t.id].count++);
|
||||
});
|
||||
|
||||
// loop through the matches and return only transforms which have match all the clauses
|
||||
const filtered = Object.values(matches)
|
||||
.filter((m) => (m && m.count) >= clauses.length)
|
||||
.map((m) => m.transform);
|
||||
|
||||
setFilteredTransforms(filtered);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const { columns, modals: singleActionModals } = useColumns(
|
||||
expandedRowItemIds,
|
||||
setExpandedRowItemIds,
|
||||
transformSelection
|
||||
);
|
||||
|
||||
const updateFilteredItems = (queryClauses: any) => {
|
||||
if (queryClauses.length) {
|
||||
const filtered = filterTransforms(transforms, queryClauses);
|
||||
setFilteredTransforms(filtered);
|
||||
} else {
|
||||
setFilteredTransforms(transforms);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const filterList = () => {
|
||||
if (searchQueryText !== '') {
|
||||
const query = EuiSearchBar.Query.parse(searchQueryText);
|
||||
let clauses: any = [];
|
||||
if (query && query.ast !== undefined && query.ast.clauses !== undefined) {
|
||||
clauses = query.ast.clauses;
|
||||
}
|
||||
updateFilteredItems(clauses);
|
||||
} else {
|
||||
updateFilteredItems([]);
|
||||
}
|
||||
};
|
||||
filterList();
|
||||
// eslint-disable-next-line
|
||||
}, [searchQueryText, transforms]); // missing dependency updateFilteredItems
|
||||
|
||||
const { onTableChange, pageOfItems, pagination, sorting } = useTableSettings<TransformListRow>(
|
||||
TRANSFORM_LIST_COLUMN.ID,
|
||||
filteredTransforms
|
||||
);
|
||||
|
||||
// Before the transforms have been loaded for the first time, display the loading indicator only.
|
||||
// Otherwise a user would see 'No transforms found' during the initial loading.
|
||||
if (!isInitialized) {
|
||||
|
@ -246,23 +180,8 @@ export const TransformList: FC<Props> = ({
|
|||
);
|
||||
}
|
||||
|
||||
const sorting = {
|
||||
sort: {
|
||||
field: sortField,
|
||||
direction: sortDirection,
|
||||
},
|
||||
};
|
||||
|
||||
const itemIdToExpandedRowMap = getItemIdToExpandedRowMap(expandedRowItemIds, transforms);
|
||||
|
||||
const pagination = {
|
||||
initialPageIndex: pageIndex,
|
||||
initialPageSize: pageSize,
|
||||
totalItemCount: transforms.length,
|
||||
pageSizeOptions: [10, 20, 50],
|
||||
hidePerPageOptions: false,
|
||||
};
|
||||
|
||||
const bulkActionMenuItems = [
|
||||
<div key="startAction" className="transform__BulkActionItem">
|
||||
<EuiButtonEmpty onClick={() => bulkStartAction.openModal(transformSelection)}>
|
||||
|
@ -331,7 +250,7 @@ export const TransformList: FC<Props> = ({
|
|||
];
|
||||
};
|
||||
|
||||
const renderToolsRight = () => (
|
||||
const toolsRight = (
|
||||
<EuiFlexGroup gutterSize="m" justifyContent="spaceAround">
|
||||
<EuiFlexItem>
|
||||
<RefreshTransformListButton onClick={refresh} isLoading={isLoading} />
|
||||
|
@ -342,56 +261,6 @@ export const TransformList: FC<Props> = ({
|
|||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
const search = {
|
||||
toolsLeft: transformSelection.length > 0 ? renderToolsLeft() : undefined,
|
||||
toolsRight: renderToolsRight(),
|
||||
onChange: onQueryChange,
|
||||
box: {
|
||||
incremental: true,
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
type: 'field_value_selection' as const,
|
||||
field: 'state.state',
|
||||
name: i18n.translate('xpack.transform.statusFilter', { defaultMessage: 'Status' }),
|
||||
multiSelect: 'or' as const,
|
||||
options: Object.values(TRANSFORM_STATE).map((val) => ({
|
||||
value: val,
|
||||
name: val,
|
||||
view: getTaskStateBadge(val),
|
||||
})),
|
||||
},
|
||||
{
|
||||
type: 'field_value_selection' as const,
|
||||
field: 'mode',
|
||||
name: i18n.translate('xpack.transform.modeFilter', { defaultMessage: 'Mode' }),
|
||||
multiSelect: false,
|
||||
options: Object.values(TRANSFORM_MODE).map((val) => ({
|
||||
value: val,
|
||||
name: val,
|
||||
view: (
|
||||
<EuiBadge className="transform__TaskModeBadge" color="hollow">
|
||||
{val}
|
||||
</EuiBadge>
|
||||
),
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const onTableChange = ({
|
||||
page = { index: 0, size: 10 },
|
||||
sort = { field: TRANSFORM_LIST_COLUMN.ID as string, direction: 'asc' },
|
||||
}) => {
|
||||
const { index, size } = page;
|
||||
setPageIndex(index);
|
||||
setPageSize(size);
|
||||
|
||||
const { field, direction } = sort;
|
||||
setSortField(field as string);
|
||||
setSortDirection(direction as Direction);
|
||||
};
|
||||
|
||||
const selection = {
|
||||
onSelectionChange: (selected: TransformListRow[]) => setTransformSelection(selected),
|
||||
};
|
||||
|
@ -404,30 +273,38 @@ export const TransformList: FC<Props> = ({
|
|||
|
||||
{/* Single Action Modals */}
|
||||
{singleActionModals}
|
||||
|
||||
<EuiInMemoryTable
|
||||
allowNeutralSort={false}
|
||||
className="transform__TransformTable"
|
||||
<EuiFlexGroup alignItems="center">
|
||||
{transformSelection.length > 0 ? (
|
||||
<EuiFlexItem grow={false}>{renderToolsLeft()}</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem>
|
||||
<TransformSearchBar
|
||||
searchQueryText={searchQueryText}
|
||||
setSearchQueryText={setSearchQueryText}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{toolsRight}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiBasicTable<TransformListRow>
|
||||
columns={columns}
|
||||
error={searchError}
|
||||
hasActions={false}
|
||||
isExpandable={true}
|
||||
isSelectable={false}
|
||||
items={filterActive ? filteredTransforms : transforms}
|
||||
items={pageOfItems as TransformListRow[]}
|
||||
itemId={TRANSFORM_LIST_COLUMN.ID}
|
||||
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
|
||||
loading={isLoading || transformsLoading}
|
||||
onTableChange={onTableChange}
|
||||
pagination={pagination}
|
||||
rowProps={(item) => ({
|
||||
'data-test-subj': `transformListRow row-${item.id}`,
|
||||
})}
|
||||
onChange={onTableChange as EuiBasicTableProps<TransformListRow>['onChange']}
|
||||
selection={selection}
|
||||
pagination={pagination!}
|
||||
sorting={sorting}
|
||||
search={search}
|
||||
data-test-subj={`transformListTable ${
|
||||
isLoading || transformsLoading ? 'loading' : 'loaded'
|
||||
}`}
|
||||
rowProps={(item) => ({
|
||||
'data-test-subj': `transformListRow row-${item.id}`,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Dispatch, SetStateAction, FC, Fragment, useState } from 'react';
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiSearchBar,
|
||||
EuiSearchBarProps,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
SearchFilterConfig,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TermClause, FieldClause, Value } from './common';
|
||||
import { TRANSFORM_STATE } from '../../../../../../common';
|
||||
import { TRANSFORM_MODE, TransformListRow } from '../../../../common';
|
||||
import { getTaskStateBadge } from './use_columns';
|
||||
|
||||
const filters: SearchFilterConfig[] = [
|
||||
{
|
||||
type: 'field_value_selection',
|
||||
field: 'state.state',
|
||||
name: i18n.translate('xpack.transform.statusFilter', { defaultMessage: 'Status' }),
|
||||
multiSelect: 'or',
|
||||
options: Object.values(TRANSFORM_STATE).map((val) => ({
|
||||
value: val,
|
||||
name: val,
|
||||
view: getTaskStateBadge(val),
|
||||
})),
|
||||
},
|
||||
{
|
||||
type: 'field_value_selection',
|
||||
field: 'mode',
|
||||
name: i18n.translate('xpack.transform.modeFilter', { defaultMessage: 'Mode' }),
|
||||
multiSelect: false,
|
||||
options: Object.values(TRANSFORM_MODE).map((val) => ({
|
||||
value: val,
|
||||
name: val,
|
||||
view: (
|
||||
<EuiBadge className="transform__TaskModeBadge" color="hollow">
|
||||
{val}
|
||||
</EuiBadge>
|
||||
),
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
function stringMatch(str: string | undefined, substr: any) {
|
||||
return (
|
||||
typeof str === 'string' &&
|
||||
typeof substr === 'string' &&
|
||||
(str.toLowerCase().match(substr.toLowerCase()) === null) === false
|
||||
);
|
||||
}
|
||||
|
||||
export const filterTransforms = (
|
||||
transforms: TransformListRow[],
|
||||
clauses: Array<TermClause | FieldClause>
|
||||
) => {
|
||||
// keep count of the number of matches we make as we're looping over the clauses
|
||||
// we only want to return transforms which match all clauses, i.e. each search term is ANDed
|
||||
// { transform-one: { transform: { id: transform-one, config: {}, state: {}, ... }, count: 0 }, transform-two: {...} }
|
||||
const matches: Record<string, any> = transforms.reduce((p: Record<string, any>, c) => {
|
||||
p[c.id] = {
|
||||
transform: c,
|
||||
count: 0,
|
||||
};
|
||||
return p;
|
||||
}, {});
|
||||
|
||||
clauses.forEach((c) => {
|
||||
// the search term could be negated with a minus, e.g. -bananas
|
||||
const bool = c.match === 'must';
|
||||
let ts = [];
|
||||
|
||||
if (c.type === 'term') {
|
||||
// filter term based clauses, e.g. bananas
|
||||
// match on ID and description
|
||||
// if the term has been negated, AND the matches
|
||||
if (bool === true) {
|
||||
ts = transforms.filter(
|
||||
(transform) =>
|
||||
stringMatch(transform.id, c.value) === bool ||
|
||||
stringMatch(transform.config.description, c.value) === bool
|
||||
);
|
||||
} else {
|
||||
ts = transforms.filter(
|
||||
(transform) =>
|
||||
stringMatch(transform.id, c.value) === bool &&
|
||||
stringMatch(transform.config.description, c.value) === bool
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// filter other clauses, i.e. the mode and status filters
|
||||
if (Array.isArray(c.value)) {
|
||||
// the status value is an array of string(s) e.g. ['failed', 'stopped']
|
||||
ts = transforms.filter((transform) => (c.value as Value[]).includes(transform.stats.state));
|
||||
} else {
|
||||
ts = transforms.filter((transform) => transform.mode === c.value);
|
||||
}
|
||||
}
|
||||
|
||||
ts.forEach((t) => matches[t.id].count++);
|
||||
});
|
||||
|
||||
// loop through the matches and return only transforms which have match all the clauses
|
||||
const filtered = Object.values(matches)
|
||||
.filter((m) => (m && m.count) >= clauses.length)
|
||||
.map((m) => m.transform);
|
||||
|
||||
return filtered;
|
||||
};
|
||||
|
||||
function getError(errorMessage: string | null) {
|
||||
if (errorMessage) {
|
||||
return i18n.translate('xpack.transform.transformList.searchBar.invalidSearchErrorMessage', {
|
||||
defaultMessage: 'Invalid search: {errorMessage}',
|
||||
values: { errorMessage },
|
||||
});
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
interface Props {
|
||||
searchQueryText: string;
|
||||
setSearchQueryText: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export const TransformSearchBar: FC<Props> = ({ searchQueryText, setSearchQueryText }) => {
|
||||
const [errorMessage, setErrorMessage] = useState<null | string>(null);
|
||||
|
||||
const onChange: EuiSearchBarProps['onChange'] = ({ query, error }) => {
|
||||
if (error) {
|
||||
setErrorMessage(error.message);
|
||||
} else if (query !== null && query.text !== undefined) {
|
||||
setSearchQueryText(query.text);
|
||||
setErrorMessage(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem data-test-subj="transformSearchBar" grow={false}>
|
||||
{searchQueryText === undefined && (
|
||||
<EuiSearchBar
|
||||
box={{
|
||||
incremental: true,
|
||||
}}
|
||||
filters={filters}
|
||||
onChange={onChange}
|
||||
className="transformSearchBar"
|
||||
/>
|
||||
)}
|
||||
{searchQueryText !== undefined && (
|
||||
<EuiSearchBar
|
||||
box={{
|
||||
incremental: true,
|
||||
}}
|
||||
defaultQuery={searchQueryText}
|
||||
filters={filters}
|
||||
onChange={onChange}
|
||||
className="transformSearchBar"
|
||||
/>
|
||||
)}
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
isInvalid={errorMessage !== null}
|
||||
error={getError(errorMessage)}
|
||||
style={{ maxHeight: '0px' }}
|
||||
>
|
||||
<Fragment />
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Direction, EuiBasicTableProps, EuiTableSortingType } from '@elastic/eui';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import get from 'lodash/get';
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
const PAGE_SIZE_OPTIONS = [10, 25, 50];
|
||||
|
||||
const propertyMap = {
|
||||
Mode: 'mode',
|
||||
};
|
||||
|
||||
// Copying from EUI EuiBasicTable types as type is not correctly picked up for table's onChange
|
||||
// Can be removed when https://github.com/elastic/eui/issues/4011 is addressed in EUI
|
||||
export interface Criteria<T> {
|
||||
page?: {
|
||||
index: number;
|
||||
size: number;
|
||||
};
|
||||
sort?: {
|
||||
field: keyof T;
|
||||
direction: Direction;
|
||||
};
|
||||
}
|
||||
export interface CriteriaWithPagination<T> extends Criteria<T> {
|
||||
page: {
|
||||
index: number;
|
||||
size: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface AnalyticsBasicTableSettings<T> {
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
totalItemCount: number;
|
||||
hidePerPageOptions: boolean;
|
||||
sortField: keyof T;
|
||||
sortDirection: Direction;
|
||||
}
|
||||
|
||||
interface UseTableSettingsReturnValue<T> {
|
||||
onTableChange: EuiBasicTableProps<T>['onChange'];
|
||||
pageOfItems: T[];
|
||||
pagination: EuiBasicTableProps<T>['pagination'];
|
||||
sorting: EuiTableSortingType<any>;
|
||||
}
|
||||
|
||||
export function useTableSettings<TypeOfItem>(
|
||||
sortByField: keyof TypeOfItem,
|
||||
items: TypeOfItem[]
|
||||
): UseTableSettingsReturnValue<TypeOfItem> {
|
||||
const [tableSettings, setTableSettings] = useState<AnalyticsBasicTableSettings<TypeOfItem>>({
|
||||
pageIndex: 0,
|
||||
pageSize: PAGE_SIZE,
|
||||
totalItemCount: 0,
|
||||
hidePerPageOptions: false,
|
||||
sortField: sortByField,
|
||||
sortDirection: 'asc',
|
||||
});
|
||||
|
||||
const getPageOfItems = (
|
||||
list: TypeOfItem[],
|
||||
index: number,
|
||||
size: number,
|
||||
sortField: keyof TypeOfItem,
|
||||
sortDirection: Direction
|
||||
) => {
|
||||
list = sortBy(list, (item) =>
|
||||
get(item, propertyMap[sortField as keyof typeof propertyMap] || sortField)
|
||||
);
|
||||
list = sortDirection === 'asc' ? list : list.reverse();
|
||||
const listLength = list.length;
|
||||
|
||||
let pageStart = index * size;
|
||||
if (pageStart >= listLength && listLength !== 0) {
|
||||
// if the page start is larger than the number of items due to
|
||||
// filters being applied or items being deleted, calculate a new page start
|
||||
pageStart = Math.floor((listLength - 1) / size) * size;
|
||||
|
||||
setTableSettings({ ...tableSettings, pageIndex: pageStart / size });
|
||||
}
|
||||
return {
|
||||
pageOfItems: list.slice(pageStart, pageStart + size),
|
||||
totalItemCount: listLength,
|
||||
};
|
||||
};
|
||||
|
||||
const onTableChange: EuiBasicTableProps<TypeOfItem>['onChange'] = ({
|
||||
page = { index: 0, size: PAGE_SIZE },
|
||||
sort = { field: sortByField, direction: 'asc' },
|
||||
}: CriteriaWithPagination<TypeOfItem>) => {
|
||||
const { index, size } = page;
|
||||
const { field, direction } = sort;
|
||||
|
||||
setTableSettings({
|
||||
...tableSettings,
|
||||
pageIndex: index,
|
||||
pageSize: size,
|
||||
sortField: field,
|
||||
sortDirection: direction,
|
||||
});
|
||||
};
|
||||
|
||||
const { pageIndex, pageSize, sortField, sortDirection } = tableSettings;
|
||||
|
||||
const { pageOfItems, totalItemCount } = getPageOfItems(
|
||||
items,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
sortField,
|
||||
sortDirection
|
||||
);
|
||||
|
||||
const pagination = {
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalItemCount,
|
||||
pageSizeOptions: PAGE_SIZE_OPTIONS,
|
||||
};
|
||||
|
||||
const sorting = {
|
||||
sort: {
|
||||
field: sortField,
|
||||
direction: sortDirection,
|
||||
},
|
||||
};
|
||||
|
||||
return { onTableChange, pageOfItems, pagination, sorting };
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue