Skip to content

Commit

Permalink
[ML] Data frames: Fixes table sorting. (#43859) (#44070)
Browse files Browse the repository at this point in the history
- EuiInMemoryTable will not correctly reflect prop updates like sorting. So for example, when the component gets mounted with sorting={false} it will never consider a later update to make sorting available after all data is loaded. This PR fixes it by mounting the component only once sorting was set properly. This affected all data frame analytics/transform tables.
- This consolidates code where we had multiple custom type definitions for EuiInMemoryTable because it's not based on TypeScript itself yet. The PR adds TypeScript Prop definitions for
the component in ml/common/types/eui/in_memory_table.ts based on React propTypes and exposes a MlInMemoryTable component that wraps EuiInMemoryTable. I'll be in contact with the EUI team so they can make use of this for EUI itself.
  • Loading branch information
walterra committed Aug 27, 2019
1 parent bb6fdf5 commit b8c6f00
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 84 deletions.
184 changes: 184 additions & 0 deletions x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* 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 { Component, HTMLAttributes, ReactElement, ReactNode } from 'react';

import { CommonProps, EuiInMemoryTable } from '@elastic/eui';

// At some point this could maybe solved with a generic <T>.
type Item = any;

// Not using an enum here because the original HorizontalAlignment is also a union type of string.
type HorizontalAlignment = 'left' | 'center' | 'right';

type SortableFunc = (item: Item) => any;
type Sortable = boolean | SortableFunc;
type DATA_TYPES = any;
type FooterFunc = (payload: { items: Item[]; pagination: any }) => ReactNode;
type RenderFunc = (value: any, record?: any) => ReactNode;
export interface FieldDataColumnType {
field: string;
name: ReactNode;
description?: string;
dataType?: DATA_TYPES;
width?: string;
sortable?: Sortable;
align?: HorizontalAlignment;
truncateText?: boolean;
render?: RenderFunc;
footer?: string | ReactElement | FooterFunc;
}

export interface ComputedColumnType {
render: RenderFunc;
name?: ReactNode;
description?: string;
sortable?: (item: Item) => any;
width?: string;
truncateText?: boolean;
}

type ICON_TYPES = any;
type IconTypesFunc = (item: Item) => ICON_TYPES; // (item) => oneOf(ICON_TYPES)
type BUTTON_ICON_COLORS = any;
type ButtonIconColorsFunc = (item: Item) => BUTTON_ICON_COLORS; // (item) => oneOf(ICON_BUTTON_COLORS)
interface DefaultItemActionType {
type?: 'icon' | 'button';
name: string;
description: string;
onClick?(item: Item): void;
href?: string;
target?: string;
available?(item: Item): boolean;
enabled?(item: Item): boolean;
isPrimary?: boolean;
icon?: ICON_TYPES | IconTypesFunc; // required when type is 'icon'
color?: BUTTON_ICON_COLORS | ButtonIconColorsFunc;
}

interface CustomItemActionType {
render(item: Item, enabled: boolean): ReactNode;
available?(item: Item): boolean;
enabled?(item: Item): boolean;
isPrimary?: boolean;
}

export interface ExpanderColumnType {
align?: HorizontalAlignment;
width?: string;
isExpander: boolean;
render: RenderFunc;
}

type SupportedItemActionType = DefaultItemActionType | CustomItemActionType;

export interface ActionsColumnType {
actions: SupportedItemActionType[];
name?: ReactNode;
description?: string;
width?: string;
}

export type ColumnType =
| ActionsColumnType
| ComputedColumnType
| ExpanderColumnType
| FieldDataColumnType;

type QueryType = any;

interface Schema {
strict?: boolean;
fields?: Record<string, any>;
flags?: string[];
}

interface SearchBoxConfigPropTypes {
placeholder?: string;
incremental?: boolean;
schema?: Schema;
}

interface Box {
placeholder?: string;
incremental?: boolean;
// here we enable the user to just assign 'true' to the schema, in which case
// we will auto-generate it out of the columns configuration
schema?: boolean | SearchBoxConfigPropTypes['schema'];
}

type SearchFiltersFiltersType = any;

interface ExecuteQueryOptions {
defaultFields: string[];
isClauseMatcher: () => void;
explain: boolean;
}

type SearchType =
| boolean
| {
toolsLeft?: ReactNode;
toolsRight?: ReactNode;
defaultQuery?: QueryType;
box?: Box;
filters?: SearchFiltersFiltersType;
onChange?: (arg: any) => void;
executeQueryOptions?: ExecuteQueryOptions;
};

interface PageSizeOptions {
pageSizeOptions: number[];
}
interface InitialPageOptions extends PageSizeOptions {
initialPageIndex: number;
initialPageSize: number;
}
type Pagination = boolean | PageSizeOptions | InitialPageOptions;

type PropertySortType = any;
type Sorting = boolean | { sort: PropertySortType };

type SelectionType = any;

type ItemIdTypeFunc = (item: Item) => string;
type ItemIdType =
| string // the name of the item id property
| ItemIdTypeFunc;

export type EuiInMemoryTableProps = CommonProps & {
columns: ColumnType[];
hasActions?: boolean;
isExpandable?: boolean;
isSelectable?: boolean;
items?: Item[];
loading?: boolean;
message?: HTMLAttributes<HTMLDivElement>;
error?: string;
compressed?: boolean;
search?: SearchType;
pagination?: Pagination;
sorting?: Sorting;
// Set `allowNeutralSort` to false to force column sorting. Defaults to true.
allowNeutralSort?: boolean;
selection?: SelectionType;
itemId?: ItemIdType;
itemIdToExpandedRowMap?: Record<string, Item>;
rowProps?: () => void | Record<string, any>;
cellProps?: () => void | Record<string, any>;
onTableChange?: (arg: {
page: { index: number; size: number };
sort: { field: string; direction: string };
}) => void;
};

interface ComponentWithConstructor<T> extends Component {
new (): Component<T>;
}

export const MlInMemoryTable = (EuiInMemoryTable as any) as ComponentWithConstructor<
EuiInMemoryTableProps
>;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { FunctionComponent, useState } from 'react';
import React, { useState } from 'react';
import moment from 'moment-timezone';

import { i18n } from '@kbn/i18n';
Expand All @@ -18,8 +18,6 @@ import {
EuiCopy,
EuiFlexGroup,
EuiFlexItem,
EuiInMemoryTable,
EuiInMemoryTableProps,
EuiPanel,
EuiPopover,
EuiPopoverTitle,
Expand All @@ -30,14 +28,7 @@ import {
RIGHT_ALIGNMENT,
} from '@elastic/eui';

// TODO EUI's types for EuiInMemoryTable is missing these props
interface ExpandableTableProps extends EuiInMemoryTableProps {
compressed: boolean;
itemIdToExpandedRowMap: ItemIdToExpandedRowMap;
isExpandable: boolean;
}

const ExpandableTable = (EuiInMemoryTable as any) as FunctionComponent<ExpandableTableProps>;
import { ColumnType, MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table';

import { KBN_FIELD_TYPES } from '../../../../../../common/constants/field_types';
import { Dictionary } from '../../../../../../common/types/common';
Expand Down Expand Up @@ -204,13 +195,13 @@ export const SourceIndexPreview: React.SFC<Props> = React.memo(({ cellClick, que
docFieldsCount = docFields.length;
}

const columns = selectedFields.map(k => {
const column = {
const columns: ColumnType[] = selectedFields.map(k => {
const column: ColumnType = {
field: `_source["${k}"]`,
name: k,
sortable: true,
truncateText: true,
} as Dictionary<any>;
};

const field = indexPattern.fields.find(f => f.name === k);

Expand Down Expand Up @@ -315,7 +306,7 @@ export const SourceIndexPreview: React.SFC<Props> = React.memo(({ cellClick, que
if (columns.length > 0) {
sorting = {
sort: {
field: columns[0].field,
field: `_source["${selectedFields[0]}"]`,
direction: SORT_DIRECTON.ASC,
},
};
Expand Down Expand Up @@ -425,8 +416,9 @@ export const SourceIndexPreview: React.SFC<Props> = React.memo(({ cellClick, que
{status !== SOURCE_INDEX_STATUS.LOADING && (
<EuiProgress size="xs" color="accent" max={1} value={0} />
)}
{clearTable === false && (
<ExpandableTable
{clearTable === false && columns.length > 0 && sorting !== false && (
<MlInMemoryTable
allowNeutralSort={false}
compressed
items={tableItems}
columns={columns}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,15 @@ import {
EuiCopy,
EuiFlexGroup,
EuiFlexItem,
EuiInMemoryTable,
EuiInMemoryTableProps,
EuiPanel,
EuiProgress,
EuiText,
EuiTitle,
SortDirection,
} from '@elastic/eui';

import { Dictionary, dictionaryToArray } from '../../../../../../common/types/common';
import { ColumnType, MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table';
import { dictionaryToArray } from '../../../../../../common/types/common';
import { ES_FIELD_TYPES } from '../../../../../../common/constants/field_types';
import { formatHumanReadableDateTimeSeconds } from '../../../../../util/date_utils';

Expand All @@ -42,13 +41,6 @@ import {
import { getPivotPreviewDevConsoleStatement } from './common';
import { PIVOT_PREVIEW_STATUS, usePivotPreviewData } from './use_pivot_preview_data';

// TODO EUI's types for EuiInMemoryTable is missing these props
interface CompressedTableProps extends EuiInMemoryTableProps {
compressed: boolean;
}

const CompressedTable = (EuiInMemoryTable as any) as SFC<CompressedTableProps>;

function sortColumns(groupByArr: PivotGroupByConfig[]) {
return (a: string, b: string) => {
// make sure groupBy fields are always most left columns
Expand Down Expand Up @@ -237,7 +229,7 @@ export const PivotPreview: SFC<PivotPreviewProps> = React.memo(({ aggs, groupBy,
const columns = columnKeys
.filter(k => typeof dataFramePreviewMappings.properties[k] !== 'undefined')
.map(k => {
const column: Dictionary<any> = {
const column: ColumnType = {
field: k,
name: k,
sortable: true,
Expand Down Expand Up @@ -290,8 +282,9 @@ export const PivotPreview: SFC<PivotPreviewProps> = React.memo(({ aggs, groupBy,
{status !== PIVOT_PREVIEW_STATUS.LOADING && (
<EuiProgress size="xs" color="accent" max={1} value={0} />
)}
{dataFramePreviewData.length > 0 && clearTable === false && (
<CompressedTable
{dataFramePreviewData.length > 0 && clearTable === false && columns.length > 0 && (
<MlInMemoryTable
allowNeutralSort={false}
compressed
items={dataFramePreviewData}
columns={columns}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ import {
RIGHT_ALIGNMENT,
} from '@elastic/eui';

import {
ActionsColumnType,
ComputedColumnType,
ExpanderColumnType,
FieldDataColumnType,
} from '../../../../../../common/types/eui/in_memory_table';

import { DataFrameTransformId } from '../../../../common';
import {
getTransformProgress,
Expand Down Expand Up @@ -79,7 +86,17 @@ export const getColumns = (
setExpandedRowItemIds([...expandedRowItemIds]);
}

return [
const columns: [
ExpanderColumnType,
FieldDataColumnType,
FieldDataColumnType,
FieldDataColumnType,
FieldDataColumnType,
ComputedColumnType,
ComputedColumnType,
ComputedColumnType,
ActionsColumnType
] = [
{
align: RIGHT_ALIGNMENT,
width: '40px',
Expand Down Expand Up @@ -205,4 +222,6 @@ export const getColumns = (
width: '200px',
},
];

return columns;
};
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,12 @@ export const ExpandedRowPreviewPane: FC<Props> = ({ transformConfig }) => {

return (
<TransformTable
allowNeutralSort={false}
loading={dataFramePreviewData.length === 0 && isLoading === true}
compressed
items={dataFramePreviewData}
columns={columns}
onChange={onTableChange}
onTableChange={onTableChange}
pagination={pagination}
sorting={sorting}
error={errorMessage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ export const DataFrameTransformList: SFC = () => {
<Fragment>
<ProgressBar isLoading={isLoading} />
<TransformTable
allowNeutralSort={false}
className="mlTransformTable"
columns={columns}
error={searchError}
Expand All @@ -379,7 +380,7 @@ export const DataFrameTransformList: SFC = () => {
items={filterActive ? filteredTransforms : transforms}
itemId={DataFrameTransformListColumn.id}
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
onChange={onTableChange}
onTableChange={onTableChange}
pagination={pagination}
selection={selection}
sorting={sorting}
Expand Down
Loading

0 comments on commit b8c6f00

Please sign in to comment.