Skip to content

Commit

Permalink
[ML] Transforms list: persist pagination through refresh interval (el…
Browse files Browse the repository at this point in the history
…astic#76786)

* use basic table in transforms to persist pagination

* simplify state and update types
  • Loading branch information
alvarezmelissa87 committed Sep 9, 2020
1 parent 256d7d4 commit 5a40d4d
Show file tree
Hide file tree
Showing 3 changed files with 376 additions and 183 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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[],
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -114,90 +97,41 @@ export const TransformList: FC<Props> = ({
!capabilities.canPreviewTransform ||
!capabilities.canStartStopTransform;

const onQueryChange = ({
query,
error,
}: Parameters<NonNullable<EuiSearchBarProps['onChange']>>[0]) => {
if (error) {
setSearchError(error.message);
const { columns, modals: singleActionModals } = useColumns(
expandedRowItemIds,
setExpandedRowItemIds,
transformSelection
);

const updateFilteredItems = (queryClauses: any) => {
if (queryClauses.length) {
const filtered = filterTransforms(transforms, queryClauses);
setFilteredTransforms(filtered);
} 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);
setFilteredTransforms(transforms);
}
};

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
);
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 {
// 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);
}
updateFilteredItems([]);
}

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
};
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.
Expand Down Expand Up @@ -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)}>
Expand Down Expand Up @@ -331,7 +250,7 @@ export const TransformList: FC<Props> = ({
];
};

const renderToolsRight = () => (
const toolsRight = (
<EuiFlexGroup gutterSize="m" justifyContent="spaceAround">
<EuiFlexItem>
<RefreshTransformListButton onClick={refresh} isLoading={isLoading} />
Expand All @@ -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),
};
Expand All @@ -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>
);
Expand Down
Loading

0 comments on commit 5a40d4d

Please sign in to comment.