Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Transforms list: persist pagination through refresh interval #76786

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = [];
Copy link
Member

@qn895 qn895 Sep 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion to use Array<TermClause | FieldClause> here if that's what filterAnalytics accepts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call - I think this also might be part of the EUI type issue I ran into but I will timebox some further digging time 👌

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