Skip to content

Commit

Permalink
[ML] Migrate transform list preview table to data grid.
Browse files Browse the repository at this point in the history
  • Loading branch information
walterra committed Mar 6, 2020
1 parent 305e47d commit b760225
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 244 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
PIVOT_SUPPORTED_AGGS,
PIVOT_SUPPORTED_GROUP_BY_AGGS,
} from '../../../../common';
import { SearchItems } from '../../../../hooks/use_search_items';

import { PivotPreview } from './pivot_preview';

Expand All @@ -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'),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,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.
Expand Down Expand Up @@ -299,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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export const StepDefineSummary: FC<Props> = ({
<PivotPreview
aggs={aggList}
groupBy={groupByList}
indexPattern={searchItems.indexPattern}
indexPatternTitle={searchItems.indexPattern.title}
query={pivotQuery}
/>
</EuiText>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import {
UsePivotPreviewDataReturnType,
} from './use_pivot_preview_data';

import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';

jest.mock('../../../../hooks/use_api');

type Callback = () => void;
Expand Down Expand Up @@ -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('');
Expand All @@ -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('');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface GetTransformsResponse {
}

export const usePivotPreviewData = (
indexPattern: IndexPattern,
indexPatternTitle: IndexPattern['title'],
query: PivotQuery,
aggs: PivotAggsConfigDict,
groupBy: PivotGroupByConfigDict
Expand All @@ -68,7 +68,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) {
Expand Down Expand Up @@ -97,7 +97,7 @@ export const usePivotPreviewData = (
// custom comparison
/* eslint-disable react-hooks/exhaustive-deps */
}, [
indexPattern.title,
indexPatternTitle,
JSON.stringify(aggsArr),
JSON.stringify(groupByArr),
JSON.stringify(query),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
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';
import { getPivotQuery, TransformPivotConfig } from '../../../../common';

const TransformTable = transformTableFactory<EsDoc>();
import {
applyTransformConfigToDefineState,
getDefaultStepDefineState,
} from '../../../create_transform/components/step_define/';
import { PivotPreview } from '../../../create_transform/components/step_define/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 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 previewConfig = applyTransformConfigToDefineState(
getDefaultStepDefineState({} as SearchItems),
transformConfig
);

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}
/>
);
};
Loading

0 comments on commit b760225

Please sign in to comment.