[ML] Transforms: Data grid fixes. (#59538)

- Fixes data grid schemas to avoid crashing the page.
- Fixes date formatting.
- Uses data grid for preview table in transform list.
This commit is contained in:
Walter Rafelsberger 2020-03-10 15:52:17 +01:00 committed by GitHub
parent b56cd41412
commit ec1f46bdbd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 545 additions and 536 deletions

View file

@ -0,0 +1,70 @@
/*
* 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 { getNestedProperty } from './object_utils';
describe('object_utils', () => {
test('getNestedProperty()', () => {
const testObj = {
the: {
nested: {
value: 'the-nested-value',
},
},
};
const falseyObj = {
the: {
nested: {
value: false,
},
other_nested: {
value: 0,
},
},
};
const test1 = getNestedProperty(testObj, 'the');
expect(typeof test1).toBe('object');
expect(Object.keys(test1)).toStrictEqual(['nested']);
const test2 = getNestedProperty(testObj, 'the$');
expect(typeof test2).toBe('undefined');
const test3 = getNestedProperty(testObj, 'the$', 'the-default-value');
expect(typeof test3).toBe('string');
expect(test3).toBe('the-default-value');
const test4 = getNestedProperty(testObj, 'the.neSted');
expect(typeof test4).toBe('undefined');
const test5 = getNestedProperty(testObj, 'the.nested');
expect(typeof test5).toBe('object');
expect(Object.keys(test5)).toStrictEqual(['value']);
const test6 = getNestedProperty(testObj, 'the.nested.vaLue');
expect(typeof test6).toBe('undefined');
const test7 = getNestedProperty(testObj, 'the.nested.value');
expect(typeof test7).toBe('string');
expect(test7).toBe('the-nested-value');
const test8 = getNestedProperty(testObj, 'the.nested.value.doesntExist');
expect(typeof test8).toBe('undefined');
const test9 = getNestedProperty(testObj, 'the.nested.value.doesntExist', 'the-default-value');
expect(typeof test9).toBe('string');
expect(test9).toBe('the-default-value');
const test10 = getNestedProperty(falseyObj, 'the.nested.value');
expect(typeof test10).toBe('boolean');
expect(test10).toBe(false);
const test11 = getNestedProperty(falseyObj, 'the.other_nested.value');
expect(typeof test11).toBe('number');
expect(test11).toBe(0);
});
});

View file

@ -11,5 +11,9 @@ export const getNestedProperty = (
accessor: string,
defaultValue?: any
) => {
return accessor.split('.').reduce((o, i) => o?.[i], obj) || defaultValue;
const value = accessor.split('.').reduce((o, i) => o?.[i], obj);
if (value === undefined) return defaultValue;
return value;
};

View file

@ -0,0 +1,117 @@
/*
* 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 { EuiDataGridSorting } from '@elastic/eui';
import {
getPreviewRequestBody,
PivotAggsConfig,
PivotGroupByConfig,
PIVOT_SUPPORTED_AGGS,
PIVOT_SUPPORTED_GROUP_BY_AGGS,
SimpleQuery,
} from '../../common';
import { multiColumnSortFactory, getPivotPreviewDevConsoleStatement } from './common';
describe('Transform: Define Pivot Common', () => {
test('multiColumnSortFactory()', () => {
const data = [
{ s: 'a', n: 1 },
{ s: 'a', n: 2 },
{ s: 'b', n: 3 },
{ s: 'b', n: 4 },
];
const sortingColumns1: EuiDataGridSorting['columns'] = [{ id: 's', direction: 'desc' }];
const multiColumnSort1 = multiColumnSortFactory(sortingColumns1);
data.sort(multiColumnSort1);
expect(data).toStrictEqual([
{ s: 'b', n: 3 },
{ s: 'b', n: 4 },
{ s: 'a', n: 1 },
{ s: 'a', n: 2 },
]);
const sortingColumns2: EuiDataGridSorting['columns'] = [
{ id: 's', direction: 'asc' },
{ id: 'n', direction: 'desc' },
];
const multiColumnSort2 = multiColumnSortFactory(sortingColumns2);
data.sort(multiColumnSort2);
expect(data).toStrictEqual([
{ s: 'a', n: 2 },
{ s: 'a', n: 1 },
{ s: 'b', n: 4 },
{ s: 'b', n: 3 },
]);
const sortingColumns3: EuiDataGridSorting['columns'] = [
{ id: 'n', direction: 'desc' },
{ id: 's', direction: 'desc' },
];
const multiColumnSort3 = multiColumnSortFactory(sortingColumns3);
data.sort(multiColumnSort3);
expect(data).toStrictEqual([
{ s: 'b', n: 4 },
{ s: 'b', n: 3 },
{ s: 'a', n: 2 },
{ s: 'a', n: 1 },
]);
});
test('getPivotPreviewDevConsoleStatement()', () => {
const query: SimpleQuery = {
query_string: {
query: '*',
default_operator: 'AND',
},
};
const groupBy: PivotGroupByConfig = {
agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS,
field: 'the-group-by-field',
aggName: 'the-group-by-agg-name',
dropDownName: 'the-group-by-drop-down-name',
};
const agg: PivotAggsConfig = {
agg: PIVOT_SUPPORTED_AGGS.AVG,
field: 'the-agg-field',
aggName: 'the-agg-agg-name',
dropDownName: 'the-agg-drop-down-name',
};
const request = getPreviewRequestBody('the-index-pattern-title', query, [groupBy], [agg]);
const pivotPreviewDevConsoleStatement = getPivotPreviewDevConsoleStatement(request);
expect(pivotPreviewDevConsoleStatement).toBe(`POST _transform/_preview
{
"source": {
"index": [
"the-index-pattern-title"
]
},
"pivot": {
"group_by": {
"the-group-by-agg-name": {
"terms": {
"field": "the-group-by-field"
}
}
},
"aggregations": {
"the-agg-agg-name": {
"avg": {
"field": "the-agg-field"
}
}
}
}
}
`);
});
});

View file

@ -0,0 +1,60 @@
/*
* 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 { EuiDataGridSorting } from '@elastic/eui';
import { getNestedProperty } from '../../../../common/utils/object_utils';
import { PreviewRequestBody } from '../../common';
/**
* Helper to sort an array of objects based on an EuiDataGrid sorting configuration.
* `sortFn()` is recursive to support sorting on multiple columns.
*
* @param sortingColumns - The EUI data grid sorting configuration
* @returns The sorting function which can be used with an array's sort() function.
*/
export const multiColumnSortFactory = (sortingColumns: EuiDataGridSorting['columns']) => {
const isString = (arg: any): arg is string => {
return typeof arg === 'string';
};
const sortFn = (a: any, b: any, sortingColumnIndex = 0): number => {
const sort = sortingColumns[sortingColumnIndex];
const aValue = getNestedProperty(a, sort.id, null);
const bValue = getNestedProperty(b, sort.id, null);
if (typeof aValue === 'number' && typeof bValue === 'number') {
if (aValue < bValue) {
return sort.direction === 'asc' ? -1 : 1;
}
if (aValue > bValue) {
return sort.direction === 'asc' ? 1 : -1;
}
}
if (isString(aValue) && isString(bValue)) {
if (aValue.localeCompare(bValue) === -1) {
return sort.direction === 'asc' ? -1 : 1;
}
if (aValue.localeCompare(bValue) === 1) {
return sort.direction === 'asc' ? 1 : -1;
}
}
if (sortingColumnIndex + 1 < sortingColumns.length) {
return sortFn(a, b, sortingColumnIndex + 1);
}
return 0;
};
return sortFn;
};
export const getPivotPreviewDevConsoleStatement = (request: PreviewRequestBody) => {
return `POST _transform/_preview\n${JSON.stringify(request, null, 2)}\n`;
};

View file

@ -0,0 +1,7 @@
/*
* 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.
*/
export { PivotPreview } from './pivot_preview';

View file

@ -8,20 +8,19 @@ import React from 'react';
import { render, wait } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { Providers } from '../../../../app_dependencies.mock';
import { Providers } from '../../app_dependencies.mock';
import {
getPivotQuery,
PivotAggsConfig,
PivotGroupByConfig,
PIVOT_SUPPORTED_AGGS,
PIVOT_SUPPORTED_GROUP_BY_AGGS,
} from '../../../../common';
import { SearchItems } from '../../../../hooks/use_search_items';
} from '../../common';
import { PivotPreview } from './pivot_preview';
jest.mock('ui/new_platform');
jest.mock('../../../../../shared_imports');
jest.mock('../../../shared_imports');
describe('Transform: <PivotPreview />', () => {
// Using the async/await wait()/done() pattern to avoid act() errors.
@ -42,10 +41,7 @@ describe('Transform: <PivotPreview />', () => {
const props = {
aggs: { 'the-agg-name': agg },
groupBy: { 'the-group-by-name': groupBy },
indexPattern: {
title: 'the-index-pattern-title',
fields: [] as any[],
} as SearchItems['indexPattern'],
indexPatternTitle: 'the-index-pattern-title',
query: getPivotQuery('the-query'),
};

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import moment from 'moment-timezone';
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
@ -21,8 +22,11 @@ import {
EuiTitle,
} from '@elastic/eui';
import { dictionaryToArray } from '../../../../../../common/types/common';
import { getNestedProperty } from '../../../../../../common/utils/object_utils';
import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/common';
import { dictionaryToArray } from '../../../../common/types/common';
import { formatHumanReadableDateTimeSeconds } from '../../../../common/utils/date_utils';
import { getNestedProperty } from '../../../../common/utils/object_utils';
import {
euiDataGridStyle,
@ -33,8 +37,8 @@ import {
PivotGroupByConfig,
PivotGroupByConfigDict,
PivotQuery,
} from '../../../../common';
import { SearchItems } from '../../../../hooks/use_search_items';
} from '../../common';
import { SearchItems } from '../../hooks/use_search_items';
import { getPivotPreviewDevConsoleStatement, multiColumnSortFactory } from './common';
import { PIVOT_PREVIEW_STATUS, usePivotPreviewData } from './use_pivot_preview_data';
@ -102,21 +106,22 @@ const ErrorMessage: FC<ErrorMessageProps> = ({ message }) => (
interface PivotPreviewProps {
aggs: PivotAggsConfigDict;
groupBy: PivotGroupByConfigDict;
indexPattern: SearchItems['indexPattern'];
indexPatternTitle: SearchItems['indexPattern']['title'];
query: PivotQuery;
showHeader?: boolean;
}
const defaultPagination = { pageIndex: 0, pageSize: 5 };
export const PivotPreview: FC<PivotPreviewProps> = React.memo(
({ aggs, groupBy, indexPattern, query }) => {
({ aggs, groupBy, indexPatternTitle, query, showHeader = true }) => {
const {
previewData: data,
previewMappings,
errorMessage,
previewRequest,
status,
} = usePivotPreviewData(indexPattern, query, aggs, groupBy);
} = usePivotPreviewData(indexPatternTitle, query, aggs, groupBy);
const groupByArr = dictionaryToArray(groupBy);
// Filters mapping properties of type `object`, which get returned for nested field parents.
@ -142,7 +147,42 @@ export const PivotPreview: FC<PivotPreviewProps> = React.memo(
}, [data.length]);
// EuiDataGrid State
const dataGridColumns = columnKeys.map(id => ({ id }));
const dataGridColumns = columnKeys.map(id => {
const field = previewMappings.properties[id];
// Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json']
// To fall back to the default string schema it needs to be undefined.
let schema;
switch (field?.type) {
case ES_FIELD_TYPES.GEO_POINT:
case ES_FIELD_TYPES.GEO_SHAPE:
schema = 'json';
break;
case ES_FIELD_TYPES.BOOLEAN:
schema = 'boolean';
break;
case ES_FIELD_TYPES.DATE:
schema = 'datetime';
break;
case ES_FIELD_TYPES.BYTE:
case ES_FIELD_TYPES.DOUBLE:
case ES_FIELD_TYPES.FLOAT:
case ES_FIELD_TYPES.HALF_FLOAT:
case ES_FIELD_TYPES.INTEGER:
case ES_FIELD_TYPES.LONG:
case ES_FIELD_TYPES.SCALED_FLOAT:
case ES_FIELD_TYPES.SHORT:
schema = 'numeric';
break;
// keep schema undefined for text based columns
case ES_FIELD_TYPES.KEYWORD:
case ES_FIELD_TYPES.TEXT:
break;
}
return { id, schema };
});
const onChangeItemsPerPage = useCallback(
pageSize => {
@ -191,13 +231,17 @@ export const PivotPreview: FC<PivotPreviewProps> = React.memo(
return JSON.stringify(cellValue);
}
if (cellValue === undefined) {
if (cellValue === undefined || cellValue === null) {
return null;
}
if (previewMappings.properties[columnId].type === ES_FIELD_TYPES.DATE) {
return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000);
}
return cellValue;
};
}, [pageData, pagination.pageIndex, pagination.pageSize]);
}, [pageData, pagination.pageIndex, pagination.pageSize, previewMappings.properties]);
if (status === PIVOT_PREVIEW_STATUS.ERROR) {
return (
@ -256,13 +300,17 @@ export const PivotPreview: FC<PivotPreviewProps> = React.memo(
return (
<div data-test-subj="transformPivotPreview loaded">
<PreviewTitle previewRequest={previewRequest} />
<div className="transform__progress">
{status === PIVOT_PREVIEW_STATUS.LOADING && <EuiProgress size="xs" color="accent" />}
{status !== PIVOT_PREVIEW_STATUS.LOADING && (
<EuiProgress size="xs" color="accent" max={1} value={0} />
)}
</div>
{showHeader && (
<>
<PreviewTitle previewRequest={previewRequest} />
<div className="transform__progress">
{status === PIVOT_PREVIEW_STATUS.LOADING && <EuiProgress size="xs" color="accent" />}
{status !== PIVOT_PREVIEW_STATUS.LOADING && (
<EuiProgress size="xs" color="accent" max={1} value={0} />
)}
</div>
</>
)}
{dataGridColumns.length > 0 && data.length > 0 && (
<EuiDataGrid
aria-label="Source index preview"

View file

@ -7,16 +7,14 @@
import React, { FC } from 'react';
import ReactDOM from 'react-dom';
import { SimpleQuery } from '../../../../common';
import { SimpleQuery } from '../../common';
import {
PIVOT_PREVIEW_STATUS,
usePivotPreviewData,
UsePivotPreviewDataReturnType,
} from './use_pivot_preview_data';
import { IndexPattern } from '../../../../../../../../../src/plugins/data/public';
jest.mock('../../../../hooks/use_api');
jest.mock('../../hooks/use_api');
type Callback = () => void;
interface TestHookProps {
@ -46,12 +44,7 @@ let pivotPreviewObj: UsePivotPreviewDataReturnType;
describe('usePivotPreviewData', () => {
test('indexPattern not defined', () => {
testHook(() => {
pivotPreviewObj = usePivotPreviewData(
({ id: 'the-id', title: 'the-title', fields: [] } as unknown) as IndexPattern,
query,
{},
{}
);
pivotPreviewObj = usePivotPreviewData('the-title', query, {}, {});
});
expect(pivotPreviewObj.errorMessage).toBe('');
@ -61,12 +54,7 @@ describe('usePivotPreviewData', () => {
test('indexPattern set triggers loading', () => {
testHook(() => {
pivotPreviewObj = usePivotPreviewData(
({ id: 'the-id', title: 'the-title', fields: [] } as unknown) as IndexPattern,
query,
{},
{}
);
pivotPreviewObj = usePivotPreviewData('the-title', query, {}, {});
});
expect(pivotPreviewObj.errorMessage).toBe('');

View file

@ -6,11 +6,11 @@
import { useEffect, useState } from 'react';
import { dictionaryToArray } from '../../../../../../common/types/common';
import { useApi } from '../../../../hooks/use_api';
import { dictionaryToArray } from '../../../../common/types/common';
import { useApi } from '../../hooks/use_api';
import { Dictionary } from '../../../../../../common/types/common';
import { IndexPattern, ES_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/public';
import { Dictionary } from '../../../../common/types/common';
import { IndexPattern, ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/public';
import {
getPreviewRequestBody,
@ -18,7 +18,7 @@ import {
PivotAggsConfigDict,
PivotGroupByConfigDict,
PivotQuery,
} from '../../../../common';
} from '../../common';
export enum PIVOT_PREVIEW_STATUS {
UNUSED,
@ -51,7 +51,7 @@ export interface GetTransformsResponse {
}
export const usePivotPreviewData = (
indexPattern: IndexPattern,
indexPatternTitle: IndexPattern['title'],
query: PivotQuery,
aggs: PivotAggsConfigDict,
groupBy: PivotGroupByConfigDict
@ -65,7 +65,7 @@ export const usePivotPreviewData = (
const aggsArr = dictionaryToArray(aggs);
const groupByArr = dictionaryToArray(groupBy);
const previewRequest = getPreviewRequestBody(indexPattern.title, query, groupByArr, aggsArr);
const previewRequest = getPreviewRequestBody(indexPatternTitle, query, groupByArr, aggsArr);
const getPreviewData = async () => {
if (aggsArr.length === 0 || groupByArr.length === 0) {
@ -94,7 +94,7 @@ export const usePivotPreviewData = (
// custom comparison
/* eslint-disable react-hooks/exhaustive-deps */
}, [
indexPattern.title,
indexPatternTitle,
JSON.stringify(aggsArr),
JSON.stringify(groupByArr),
JSON.stringify(query),

View file

@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import moment from 'moment-timezone';
import React, { useCallback, useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
@ -17,9 +18,11 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiProgress,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { formatHumanReadableDateTimeSeconds } from '../../../../../../common/utils/date_utils';
import { getNestedProperty } from '../../../../../../common/utils/object_utils';
import {
@ -29,6 +32,7 @@ import {
PivotQuery,
} from '../../../../common';
import { SearchItems } from '../../../../hooks/use_search_items';
import { useToastNotifications } from '../../../../app_dependencies';
import { getSourceIndexDevConsoleStatement } from './common';
import { SOURCE_INDEX_STATUS, useSourceIndexData } from './use_source_index_data';
@ -52,9 +56,8 @@ interface Props {
query: PivotQuery;
}
const defaultPagination = { pageIndex: 0, pageSize: 5 };
export const SourceIndexPreview: React.FC<Props> = React.memo(({ indexPattern, query }) => {
const toastNotifications = useToastNotifications();
const allFields = indexPattern.fields.map(f => f.name);
const indexPatternFields: string[] = allFields.filter(f => {
if (indexPattern.metaFields.includes(f)) {
@ -73,38 +76,67 @@ export const SourceIndexPreview: React.FC<Props> = React.memo(({ indexPattern, q
// Column visibility
const [visibleColumns, setVisibleColumns] = useState<EsFieldName[]>(indexPatternFields);
const [pagination, setPagination] = useState(defaultPagination);
useEffect(() => {
setPagination(defaultPagination);
}, [query]);
const { errorMessage, status, rowCount, tableItems: data } = useSourceIndexData(
indexPattern,
query,
pagination
);
const {
errorMessage,
pagination,
setPagination,
setSortingColumns,
rowCount,
sortingColumns,
status,
tableItems: data,
} = useSourceIndexData(indexPattern, query);
// EuiDataGrid State
const dataGridColumns = indexPatternFields.map(id => {
const field = indexPattern.fields.getByName(id);
const dataGridColumns = [
...indexPatternFields.map(id => {
const field = indexPattern.fields.getByName(id);
let schema = 'string';
// Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json']
// To fall back to the default string schema it needs to be undefined.
let schema;
switch (field?.type) {
case 'date':
schema = 'datetime';
break;
case 'geo_point':
schema = 'json';
break;
case 'number':
schema = 'numeric';
break;
}
switch (field?.type) {
case 'date':
schema = 'datetime';
break;
case 'geo_point':
schema = 'json';
break;
case 'number':
schema = 'numeric';
break;
}
return { id, schema };
});
return { id, schema };
}),
];
const onSort = useCallback(
(sc: Array<{ id: string; direction: 'asc' | 'desc' }>) => {
// Check if an unsupported column type for sorting was selected.
const invalidSortingColumnns = sc.reduce<string[]>((arr, current) => {
const columnType = dataGridColumns.find(dgc => dgc.id === current.id);
if (columnType?.schema === 'json') {
arr.push(current.id);
}
return arr;
}, []);
if (invalidSortingColumnns.length === 0) {
setSortingColumns(sc);
} else {
invalidSortingColumnns.forEach(columnId => {
toastNotifications.addDanger(
i18n.translate('xpack.transform.sourceIndexPreview.invalidSortingColumnError', {
defaultMessage: `The column '{columnId}' cannot be used for sorting.`,
values: { columnId },
})
);
});
}
},
[dataGridColumns, setSortingColumns, toastNotifications]
);
const onChangeItemsPerPage = useCallback(
pageSize => {
@ -120,10 +152,6 @@ export const SourceIndexPreview: React.FC<Props> = React.memo(({ indexPattern, q
setPagination,
]);
// ** Sorting config
const [sortingColumns, setSortingColumns] = useState([]);
const onSort = useCallback(sc => setSortingColumns(sc), [setSortingColumns]);
const renderCellValue = useMemo(() => {
return ({
rowIndex,
@ -144,32 +172,18 @@ export const SourceIndexPreview: React.FC<Props> = React.memo(({ indexPattern, q
return JSON.stringify(cellValue);
}
if (cellValue === undefined) {
if (cellValue === undefined || cellValue === null) {
return null;
}
const field = indexPattern.fields.getByName(columnId);
if (field?.type === 'date') {
return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000);
}
return cellValue;
};
}, [data, pagination.pageIndex, pagination.pageSize]);
if (status === SOURCE_INDEX_STATUS.ERROR) {
return (
<div data-test-subj="transformSourceIndexPreview error">
<SourceIndexPreviewTitle indexPatternTitle={indexPattern.title} />
<EuiCallOut
title={i18n.translate('xpack.transform.sourceIndexPreview.sourceIndexPatternError', {
defaultMessage: 'An error occurred loading the source index data.',
})}
color="danger"
iconType="cross"
>
<EuiCodeBlock language="json" fontSize="s" paddingSize="s" isCopyable>
{errorMessage}
</EuiCodeBlock>
</EuiCallOut>
</div>
);
}
}, [data, indexPattern.fields, pagination.pageIndex, pagination.pageSize]);
if (status === SOURCE_INDEX_STATUS.LOADED && data.length === 0) {
return (
@ -200,7 +214,11 @@ export const SourceIndexPreview: React.FC<Props> = React.memo(({ indexPattern, q
});
return (
<div data-test-subj="transformSourceIndexPreview loaded">
<div
data-test-subj={`transformSourceIndexPreview ${
status === SOURCE_INDEX_STATUS.ERROR ? 'error' : 'loaded'
}`}
>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem>
<SourceIndexPreviewTitle indexPatternTitle={indexPattern.title} />
@ -222,24 +240,38 @@ export const SourceIndexPreview: React.FC<Props> = React.memo(({ indexPattern, q
<EuiProgress size="xs" color="accent" max={1} value={0} />
)}
</div>
{dataGridColumns.length > 0 && data.length > 0 && (
<EuiDataGrid
aria-label="Source index preview"
columns={dataGridColumns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
gridStyle={euiDataGridStyle}
rowCount={rowCount}
renderCellValue={renderCellValue}
sorting={{ columns: sortingColumns, onSort }}
toolbarVisibility={euiDataGridToolbarSettings}
pagination={{
...pagination,
pageSizeOptions: [5, 10, 25],
onChangeItemsPerPage,
onChangePage,
}}
/>
{status === SOURCE_INDEX_STATUS.ERROR && (
<div data-test-subj="transformSourceIndexPreview error">
<EuiCallOut
title={i18n.translate('xpack.transform.sourceIndexPreview.sourceIndexPatternError', {
defaultMessage: 'An error occurred loading the source index data.',
})}
color="danger"
iconType="cross"
>
<EuiCodeBlock language="json" fontSize="s" paddingSize="s" isCopyable>
{errorMessage}
</EuiCodeBlock>
</EuiCallOut>
<EuiSpacer size="m" />
</div>
)}
<EuiDataGrid
aria-label="Source index preview"
columns={dataGridColumns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
gridStyle={euiDataGridStyle}
rowCount={rowCount}
renderCellValue={renderCellValue}
sorting={{ columns: sortingColumns, onSort }}
toolbarVisibility={euiDataGridToolbarSettings}
pagination={{
...pagination,
pageSizeOptions: [5, 10, 25],
onChangeItemsPerPage,
onChangePage,
}}
/>
</div>
);
});

View file

@ -26,10 +26,7 @@ const query: SimpleQuery = {
describe('useSourceIndexData', () => {
test('indexPattern set triggers loading', async done => {
const { result, waitForNextUpdate } = renderHook(() =>
useSourceIndexData({ id: 'the-id', title: 'the-title', fields: [] }, query, {
pageIndex: 0,
pageSize: 10,
})
useSourceIndexData({ id: 'the-id', title: 'the-title', fields: [] }, query)
);
const sourceIndexObj: UseSourceIndexDataReturnType = result.current;

View file

@ -4,15 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { useEffect, useState } from 'react';
import { useEffect, useState, Dispatch, SetStateAction } from 'react';
import { SearchResponse } from 'elasticsearch';
import { EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui';
import { IIndexPattern } from 'src/plugins/data/public';
import { useApi } from '../../../../hooks/use_api';
import { Dictionary } from '../../../../../../common/types/common';
import { isDefaultQuery, matchAllQuery, EsDocSource, PivotQuery } from '../../../../common';
import { useApi } from '../../../../hooks/use_api';
export enum SOURCE_INDEX_STATUS {
UNUSED,
@ -21,19 +24,25 @@ export enum SOURCE_INDEX_STATUS {
ERROR,
}
type EsSorting = Dictionary<{
order: 'asc' | 'desc';
}>;
interface ErrorResponse {
error: {
body: string;
msg: string;
path: string;
query: any;
response: string;
request: Dictionary<any>;
response: Dictionary<any>;
body: {
statusCode: number;
error: string;
message: string;
};
name: string;
req: Dictionary<any>;
res: Dictionary<any>;
}
const isErrorResponse = (arg: any): arg is ErrorResponse => {
return arg.error !== undefined;
return arg?.body?.error !== undefined && arg?.body?.message !== undefined;
};
// The types specified in `@types/elasticsearch` are out of date and still have `total: number`.
@ -46,42 +55,60 @@ interface SearchResponse7 extends SearchResponse<any> {
};
}
type SourceIndexSearchResponse = ErrorResponse | SearchResponse7;
type SourceIndexSearchResponse = SearchResponse7;
type SourceIndexPagination = Pick<EuiDataGridPaginationProps, 'pageIndex' | 'pageSize'>;
const defaultPagination: SourceIndexPagination = { pageIndex: 0, pageSize: 5 };
export interface UseSourceIndexDataReturnType {
errorMessage: string;
status: SOURCE_INDEX_STATUS;
pagination: SourceIndexPagination;
setPagination: Dispatch<SetStateAction<SourceIndexPagination>>;
setSortingColumns: Dispatch<SetStateAction<EuiDataGridSorting['columns']>>;
rowCount: number;
sortingColumns: EuiDataGridSorting['columns'];
status: SOURCE_INDEX_STATUS;
tableItems: EsDocSource[];
}
export const useSourceIndexData = (
indexPattern: IIndexPattern,
query: PivotQuery,
pagination: { pageIndex: number; pageSize: number }
query: PivotQuery
): UseSourceIndexDataReturnType => {
const [errorMessage, setErrorMessage] = useState('');
const [status, setStatus] = useState(SOURCE_INDEX_STATUS.UNUSED);
const [pagination, setPagination] = useState(defaultPagination);
const [sortingColumns, setSortingColumns] = useState<EuiDataGridSorting['columns']>([]);
const [rowCount, setRowCount] = useState(0);
const [tableItems, setTableItems] = useState<EsDocSource[]>([]);
const api = useApi();
useEffect(() => {
setPagination(defaultPagination);
}, [query]);
const getSourceIndexData = async function() {
setErrorMessage('');
setStatus(SOURCE_INDEX_STATUS.LOADING);
try {
const resp: SourceIndexSearchResponse = await api.esSearch({
index: indexPattern.title,
const sort: EsSorting = sortingColumns.reduce((s, column) => {
s[column.id] = { order: column.direction };
return s;
}, {} as EsSorting);
const esSearchRequest = {
index: indexPattern.title,
body: {
// Instead of using the default query (`*`), fall back to a more efficient `match_all` query.
query: isDefaultQuery(query) ? matchAllQuery : query,
from: pagination.pageIndex * pagination.pageSize,
size: pagination.pageSize,
// Instead of using the default query (`*`), fall back to a more efficient `match_all` query.
body: { query: isDefaultQuery(query) ? matchAllQuery : query },
});
...(Object.keys(sort).length > 0 ? { sort } : {}),
},
};
if (isErrorResponse(resp)) {
throw resp.error;
}
try {
const resp: SourceIndexSearchResponse = await api.esSearch(esSearchRequest);
const docs = resp.hits.hits.map(d => d._source);
@ -89,12 +116,11 @@ export const useSourceIndexData = (
setTableItems(docs);
setStatus(SOURCE_INDEX_STATUS.LOADED);
} catch (e) {
if (e.message !== undefined) {
setErrorMessage(e.message);
if (isErrorResponse(e)) {
setErrorMessage(`${e.body.error}: ${e.body.message}`);
} else {
setErrorMessage(JSON.stringify(e, null, 2));
}
setTableItems([]);
setStatus(SOURCE_INDEX_STATUS.ERROR);
}
};
@ -103,6 +129,15 @@ export const useSourceIndexData = (
getSourceIndexData();
// custom comparison
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [indexPattern.title, JSON.stringify(query), JSON.stringify(pagination)]);
return { errorMessage, status, rowCount, tableItems };
}, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]);
return {
errorMessage,
pagination,
setPagination,
setSortingColumns,
rowCount,
sortingColumns,
status,
tableItems,
};
};

View file

@ -4,73 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiDataGridSorting } from '@elastic/eui';
import {
getPreviewRequestBody,
PivotAggsConfig,
PivotGroupByConfig,
PIVOT_SUPPORTED_AGGS,
PIVOT_SUPPORTED_GROUP_BY_AGGS,
SimpleQuery,
} from '../../../../common';
import {
multiColumnSortFactory,
getPivotPreviewDevConsoleStatement,
getPivotDropdownOptions,
} from './common';
import { getPivotDropdownOptions } from './common';
import { IndexPattern } from '../../../../../../../../../src/plugins/data/public';
describe('Transform: Define Pivot Common', () => {
test('customSortFactory()', () => {
const data = [
{ s: 'a', n: 1 },
{ s: 'a', n: 2 },
{ s: 'b', n: 3 },
{ s: 'b', n: 4 },
];
const sortingColumns1: EuiDataGridSorting['columns'] = [{ id: 's', direction: 'desc' }];
const multiColumnSort1 = multiColumnSortFactory(sortingColumns1);
data.sort(multiColumnSort1);
expect(data).toStrictEqual([
{ s: 'b', n: 3 },
{ s: 'b', n: 4 },
{ s: 'a', n: 1 },
{ s: 'a', n: 2 },
]);
const sortingColumns2: EuiDataGridSorting['columns'] = [
{ id: 's', direction: 'asc' },
{ id: 'n', direction: 'desc' },
];
const multiColumnSort2 = multiColumnSortFactory(sortingColumns2);
data.sort(multiColumnSort2);
expect(data).toStrictEqual([
{ s: 'a', n: 2 },
{ s: 'a', n: 1 },
{ s: 'b', n: 4 },
{ s: 'b', n: 3 },
]);
const sortingColumns3: EuiDataGridSorting['columns'] = [
{ id: 'n', direction: 'desc' },
{ id: 's', direction: 'desc' },
];
const multiColumnSort3 = multiColumnSortFactory(sortingColumns3);
data.sort(multiColumnSort3);
expect(data).toStrictEqual([
{ s: 'b', n: 4 },
{ s: 'b', n: 3 },
{ s: 'a', n: 2 },
{ s: 'a', n: 1 },
]);
});
test('getPivotDropdownOptions()', () => {
// The field name includes the characters []> as well as a leading and ending space charcter
// which cannot be used for aggregation names. The test results verifies that the characters
@ -155,53 +92,4 @@ describe('Transform: Define Pivot Common', () => {
},
});
});
test('getPivotPreviewDevConsoleStatement()', () => {
const query: SimpleQuery = {
query_string: {
query: '*',
default_operator: 'AND',
},
};
const groupBy: PivotGroupByConfig = {
agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS,
field: 'the-group-by-field',
aggName: 'the-group-by-agg-name',
dropDownName: 'the-group-by-drop-down-name',
};
const agg: PivotAggsConfig = {
agg: PIVOT_SUPPORTED_AGGS.AVG,
field: 'the-agg-field',
aggName: 'the-agg-agg-name',
dropDownName: 'the-agg-drop-down-name',
};
const request = getPreviewRequestBody('the-index-pattern-title', query, [groupBy], [agg]);
const pivotPreviewDevConsoleStatement = getPivotPreviewDevConsoleStatement(request);
expect(pivotPreviewDevConsoleStatement).toBe(`POST _transform/_preview
{
"source": {
"index": [
"the-index-pattern-title"
]
},
"pivot": {
"group_by": {
"the-group-by-agg-name": {
"terms": {
"field": "the-group-by-field"
}
}
},
"aggregations": {
"the-agg-agg-name": {
"avg": {
"field": "the-agg-field"
}
}
}
}
}
`);
});
});

View file

@ -4,13 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { get } from 'lodash';
import { EuiComboBoxOptionOption, EuiDataGridSorting } from '@elastic/eui';
import { EuiComboBoxOptionOption } from '@elastic/eui';
import { IndexPattern, KBN_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/public';
import { getNestedProperty } from '../../../../../../common/utils/object_utils';
import {
PreviewRequestBody,
DropDownLabel,
DropDownOption,
EsFieldName,
@ -27,51 +24,6 @@ export interface Field {
type: KBN_FIELD_TYPES;
}
/**
* Helper to sort an array of objects based on an EuiDataGrid sorting configuration.
* `sortFn()` is recursive to support sorting on multiple columns.
*
* @param sortingColumns - The EUI data grid sorting configuration
* @returns The sorting function which can be used with an array's sort() function.
*/
export const multiColumnSortFactory = (sortingColumns: EuiDataGridSorting['columns']) => {
const isString = (arg: any): arg is string => {
return typeof arg === 'string';
};
const sortFn = (a: any, b: any, sortingColumnIndex = 0): number => {
const sort = sortingColumns[sortingColumnIndex];
const aValue = getNestedProperty(a, sort.id, null);
const bValue = getNestedProperty(b, sort.id, null);
if (typeof aValue === 'number' && typeof bValue === 'number') {
if (aValue < bValue) {
return sort.direction === 'asc' ? -1 : 1;
}
if (aValue > bValue) {
return sort.direction === 'asc' ? 1 : -1;
}
}
if (isString(aValue) && isString(bValue)) {
if (aValue.localeCompare(bValue) === -1) {
return sort.direction === 'asc' ? -1 : 1;
}
if (aValue.localeCompare(bValue) === 1) {
return sort.direction === 'asc' ? 1 : -1;
}
}
if (sortingColumnIndex + 1 < sortingColumns.length) {
return sortFn(a, b, sortingColumnIndex + 1);
}
return 0;
};
return sortFn;
};
function getDefaultGroupByConfig(
aggName: string,
dropDownName: string,
@ -166,7 +118,3 @@ export function getPivotDropdownOptions(indexPattern: IndexPattern) {
aggOptionsData,
};
}
export const getPivotPreviewDevConsoleStatement = (request: PreviewRequestBody) => {
return `POST _transform/_preview\n${JSON.stringify(request, null, 2)}\n`;
};

View file

@ -26,6 +26,7 @@ import {
EuiSwitch,
} from '@elastic/eui';
import { PivotPreview } from '../../../../components/pivot_preview';
import { useDocumentationLinks } from '../../../../hooks/use_documentation_links';
import { SavedSearchQuery, SearchItems } from '../../../../hooks/use_search_items';
import { useXJsonMode, xJsonMode } from '../../../../hooks/use_x_json_mode';
@ -36,7 +37,6 @@ import { DropDown } from '../aggregation_dropdown';
import { AggListForm } from '../aggregation_list';
import { GroupByListForm } from '../group_by_list';
import { SourceIndexPreview } from '../source_index_preview';
import { PivotPreview } from './pivot_preview';
import { KqlFilterBar } from '../../../../../shared_imports';
import { SwitchModal } from './switch_modal';
@ -899,7 +899,7 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange,
<PivotPreview
aggs={aggList}
groupBy={groupByList}
indexPattern={searchItems.indexPattern}
indexPatternTitle={searchItems.indexPattern.title}
query={pivotQuery}
/>
</EuiFlexItem>

View file

@ -18,12 +18,12 @@ import {
} from '@elastic/eui';
import { getPivotQuery } from '../../../../common';
import { PivotPreview } from '../../../../components/pivot_preview';
import { SearchItems } from '../../../../hooks/use_search_items';
import { AggListSummary } from '../aggregation_list';
import { GroupByListSummary } from '../group_by_list';
import { PivotPreview } from './pivot_preview';
import { StepDefineExposedState } from './step_define_form';
const defaultSearch = '*';
@ -134,7 +134,7 @@ export const StepDefineSummary: FC<Props> = ({
<PivotPreview
aggs={aggList}
groupBy={groupByList}
indexPattern={searchItems.indexPattern}
indexPatternTitle={searchItems.indexPattern.title}
query={pivotQuery}
/>
</EuiText>

View file

@ -4,218 +4,39 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FC, useState } from 'react';
import { i18n } from '@kbn/i18n';
import moment from 'moment-timezone';
import { Direction } from '@elastic/eui';
import { SortDirection, SORT_DIRECTION, FieldDataColumnType } from '../../../../../shared_imports';
import React, { FC } from 'react';
import { useApi } from '../../../../hooks/use_api';
import { SearchItems } from '../../../../hooks/use_search_items';
import { getPivotQuery, TransformPivotConfig } from '../../../../common';
import {
getFlattenedFields,
useRefreshTransformList,
EsDoc,
PreviewRequestBody,
TransformPivotConfig,
} from '../../../../common';
import { ES_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/public';
import { formatHumanReadableDateTimeSeconds } from '../../../../../../common/utils/date_utils';
import { transformTableFactory } from './transform_table';
const TransformTable = transformTableFactory<EsDoc>();
applyTransformConfigToDefineState,
getDefaultStepDefineState,
} from '../../../create_transform/components/step_define/';
import { PivotPreview } from '../../../../components/pivot_preview';
interface Props {
transformConfig: TransformPivotConfig;
}
export function sortColumns(groupByArr: string[]) {
return (a: string, b: string) => {
// make sure groupBy fields are always most left columns
if (groupByArr.some(aggName => aggName === a) && groupByArr.some(aggName => aggName === b)) {
return a.localeCompare(b);
}
if (groupByArr.some(aggName => aggName === a)) {
return -1;
}
if (groupByArr.some(aggName => aggName === b)) {
return 1;
}
return a.localeCompare(b);
};
}
function getDataFromTransform(
transformConfig: TransformPivotConfig
): { previewRequest: PreviewRequestBody; groupByArr: string[] | [] } {
const index = transformConfig.source.index;
const query = transformConfig.source.query;
const pivot = transformConfig.pivot;
const groupByArr = [];
const previewRequest: PreviewRequestBody = {
source: {
index,
query,
},
pivot,
};
// hasOwnProperty check to ensure only properties on object itself, and not its prototypes
if (pivot.group_by !== undefined) {
for (const key in pivot.group_by) {
if (pivot.group_by.hasOwnProperty(key)) {
groupByArr.push(key);
}
}
}
return { groupByArr, previewRequest };
}
export const ExpandedRowPreviewPane: FC<Props> = ({ transformConfig }) => {
const [previewData, setPreviewData] = useState<EsDoc[]>([]);
const [columns, setColumns] = useState<Array<FieldDataColumnType<EsDoc>> | []>([]);
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
const [sortDirection, setSortDirection] = useState<SortDirection | Direction>(SORT_DIRECTION.ASC);
const [sortField, setSortField] = useState<keyof typeof previewData[number] | ''>('');
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const api = useApi();
const previewConfig = applyTransformConfigToDefineState(
getDefaultStepDefineState({} as SearchItems),
transformConfig
);
const getPreviewFactory = () => {
let concurrentLoads = 0;
return async function getPreview() {
try {
concurrentLoads++;
if (concurrentLoads > 1) {
return;
}
const { previewRequest, groupByArr } = getDataFromTransform(transformConfig);
setIsLoading(true);
const resp: any = await api.getTransformsPreview(previewRequest);
setIsLoading(false);
if (resp.preview.length > 0) {
const columnKeys = getFlattenedFields(resp.preview[0]);
columnKeys.sort(sortColumns(groupByArr));
const tableColumns: Array<FieldDataColumnType<EsDoc>> = columnKeys.map(k => {
const column: FieldDataColumnType<EsDoc> = {
field: k,
name: k,
sortable: true,
truncateText: true,
};
if (typeof resp.mappings.properties[k] !== 'undefined') {
const esFieldType = resp.mappings.properties[k].type;
switch (esFieldType) {
case ES_FIELD_TYPES.BOOLEAN:
column.dataType = 'boolean';
break;
case ES_FIELD_TYPES.DATE:
column.align = 'right';
column.render = (d: any) =>
formatHumanReadableDateTimeSeconds(moment(d).unix() * 1000);
break;
case ES_FIELD_TYPES.BYTE:
case ES_FIELD_TYPES.DOUBLE:
case ES_FIELD_TYPES.FLOAT:
case ES_FIELD_TYPES.HALF_FLOAT:
case ES_FIELD_TYPES.INTEGER:
case ES_FIELD_TYPES.LONG:
case ES_FIELD_TYPES.SCALED_FLOAT:
case ES_FIELD_TYPES.SHORT:
column.dataType = 'number';
break;
case ES_FIELD_TYPES.KEYWORD:
case ES_FIELD_TYPES.TEXT:
column.dataType = 'string';
break;
}
}
return column;
});
setPreviewData(resp.preview);
setColumns(tableColumns);
setSortField(sortField);
setSortDirection(sortDirection);
}
concurrentLoads--;
if (concurrentLoads > 0) {
concurrentLoads = 0;
getPreview();
}
} catch (error) {
setIsLoading(false);
setErrorMessage(
i18n.translate('xpack.transform.transformList.stepDetails.previewPane.errorMessage', {
defaultMessage: 'Preview could not be loaded',
})
);
}
};
};
useRefreshTransformList({ onRefresh: getPreviewFactory() });
const pagination = {
initialPageIndex: pageIndex,
initialPageSize: pageSize,
totalItemCount: previewData.length,
pageSizeOptions: [10, 20],
hidePerPageOptions: false,
};
const sorting = {
sort: {
field: sortField as string,
direction: sortDirection,
},
};
const onTableChange = ({
page = { index: 0, size: 10 },
sort = { field: columns[0].field, direction: SORT_DIRECTION.ASC },
}: {
page?: { index: number; size: number };
sort?: { field: keyof typeof previewData[number]; direction: SortDirection | Direction };
}) => {
const { index, size } = page;
setPageIndex(index);
setPageSize(size);
const { field, direction } = sort;
setSortField(field);
setSortDirection(direction);
};
const transformTableLoading = previewData.length === 0 && isLoading === true;
const dataTestSubj = `transformPreviewTabContent${!transformTableLoading ? ' loaded' : ''}`;
const indexPatternTitle = Array.isArray(transformConfig.source.index)
? transformConfig.source.index.join(',')
: transformConfig.source.index;
return (
<div data-test-subj={dataTestSubj}>
<TransformTable
allowNeutralSort={false}
loading={transformTableLoading}
compressed
items={previewData}
columns={columns}
onTableChange={onTableChange}
pagination={pagination}
rowProps={() => ({
'data-test-subj': 'transformPreviewTabContentRow',
})}
sorting={sorting}
error={errorMessage}
/>
</div>
<PivotPreview
aggs={previewConfig.aggList}
groupBy={previewConfig.groupByList}
indexPatternTitle={indexPatternTitle}
query={getPivotQuery(previewConfig.searchQuery)}
showHeader={false}
/>
);
};

View file

@ -12500,7 +12500,6 @@
"xpack.transform.transformList.startModalTitle": "{transformId} を開始",
"xpack.transform.transformList.startTransformErrorMessage": "変換 {transformId} の開始中にエラーが発生しました",
"xpack.transform.transformList.startTransformSuccessMessage": "変換 {transformId} の開始リクエストが受け付けられました。",
"xpack.transform.transformList.stepDetails.previewPane.errorMessage": "プレビューを読み込めませんでした",
"xpack.transform.transformList.stopActionName": "停止",
"xpack.transform.transformList.stoppedTransformBulkToolTip": "1 つまたは複数の変換が既に開始済みです。",
"xpack.transform.transformList.stoppedTransformToolTip": "{transformId} は既に停止済みです。",

View file

@ -12500,7 +12500,6 @@
"xpack.transform.transformList.startModalTitle": "启动 {transformId}",
"xpack.transform.transformList.startTransformErrorMessage": "启动转换 {transformId} 时发生错误",
"xpack.transform.transformList.startTransformSuccessMessage": "启动转换 {transformId} 的请求已确认。",
"xpack.transform.transformList.stepDetails.previewPane.errorMessage": "无法加载预览",
"xpack.transform.transformList.stopActionName": "停止",
"xpack.transform.transformList.stoppedTransformBulkToolTip": "一个或多个选定数据帧转换已停止。",
"xpack.transform.transformList.stoppedTransformToolTip": "{transformId} 已停止。",

View file

@ -61,17 +61,17 @@ export function TransformTableProvider({ getService }: FtrProviderContext) {
return rows;
}
async parseEuiInMemoryTable(tableSubj: string) {
async parseEuiDataGrid(tableSubj: string) {
const table = await testSubjects.find(`~${tableSubj}`);
const $ = await table.parseDomContent();
const rows = [];
// For each row, get the content of each cell and
// add its values as an array to each row.
for (const tr of $.findTestSubjects(`~${tableSubj}Row`).toArray()) {
for (const tr of $.findTestSubjects(`~dataGridRow`).toArray()) {
rows.push(
$(tr)
.find('.euiTableCellContent')
.find('.euiDataGridRowCell__truncate')
.toArray()
.map(cell =>
$(cell)
@ -84,14 +84,14 @@ export function TransformTableProvider({ getService }: FtrProviderContext) {
return rows;
}
async assertEuiInMemoryTableColumnValues(
async assertEuiDataGridColumnValues(
tableSubj: string,
column: number,
expectedColumnValues: string[]
) {
await retry.tryForTime(2000, async () => {
// get a 2D array of rows and cell values
const rows = await this.parseEuiInMemoryTable(tableSubj);
const rows = await this.parseEuiDataGrid(tableSubj);
// reduce the rows data to an array of unique values in the specified column
const uniqueColumnValues = rows
@ -148,17 +148,17 @@ export function TransformTableProvider({ getService }: FtrProviderContext) {
await testSubjects.existOrFail('transformPreviewTab');
await testSubjects.click('transformPreviewTab');
await testSubjects.existOrFail('~transformPreviewTabContent');
await testSubjects.existOrFail('~transformPivotPreview');
}
public async waitForTransformsExpandedRowPreviewTabToLoad() {
await testSubjects.existOrFail('~transformPreviewTabContent', { timeout: 60 * 1000 });
await testSubjects.existOrFail('transformPreviewTabContent loaded', { timeout: 30 * 1000 });
await testSubjects.existOrFail('~transformPivotPreview', { timeout: 60 * 1000 });
await testSubjects.existOrFail('transformPivotPreview loaded', { timeout: 30 * 1000 });
}
async assertTransformsExpandedRowPreviewColumnValues(column: number, values: string[]) {
await this.waitForTransformsExpandedRowPreviewTabToLoad();
await this.assertEuiInMemoryTableColumnValues('transformPreviewTabContent', column, values);
await this.assertEuiDataGridColumnValues('transformPivotPreview', column, values);
}
})();
}