From 202035a24a69a02607fcea6ab938f39568dc5709 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 5 Jun 2020 11:42:24 +0200 Subject: [PATCH 01/42] [ML] Initial experimental version. --- .../components/data_grid/column_chart.tsx | 301 ++++++++++++++++++ .../components/data_grid/data_grid.tsx | 60 +++- .../data_grid/use_column_charts.tsx | 76 +++++ .../step_define/step_define_form.tsx | 7 +- 4 files changed, 427 insertions(+), 17 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx new file mode 100644 index 0000000000000..aba5633f6f96e --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -0,0 +1,301 @@ +/* + * 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 moment from 'moment'; +import React, { useEffect, useState, FC } from 'react'; +import { BehaviorSubject } from 'rxjs'; + +import { BarSeries, Chart, Settings } from '@elastic/charts'; +import { EuiText } from '@elastic/eui'; + +// import { StaticIndexPattern } from 'ui/index_patterns'; + +// TODO: Below import is temporary, use `react-use` lib instead. +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { useObservable } from 'react-use'; +import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; + +export const hoveredRow$ = new BehaviorSubject(null); + +interface Props { + indexPatternTitle: string; + columnType: any; + query: any; + api: any; +} + +interface DataItem { + key: string; + x: string | number; + y: number; +} + +const MAX_CHART_COLUMNS = 20; + +export const ColumnChart: FC = ({ indexPatternTitle, columnType, query, api }) => { + const [cardinality, setCardinality] = useState(0); + const [data, setData] = useState([]); + const [interval, setInterval] = useState(0); + const [stats, setStats] = useState([0, 0]); + + const hoveredRow = useObservable(hoveredRow$); + + let fieldType: KBN_FIELD_TYPES; + + switch (columnType.schema) { + case 'datetime': + fieldType = KBN_FIELD_TYPES.DATE; + break; + case 'numeric': + fieldType = KBN_FIELD_TYPES.NUMBER; + break; + case 'boolean': + fieldType = KBN_FIELD_TYPES.BOOLEAN; + break; + case 'json': + fieldType = KBN_FIELD_TYPES.OBJECT; + break; + default: + fieldType = KBN_FIELD_TYPES.STRING; + } + + const fetchCardinality = async () => { + try { + const resp: any = await api.esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: { + categories: { + cardinality: { + field: columnType.id, + }, + }, + }, + size: 0, + }, + }); + setCardinality(resp.aggregations.categories.value); + } catch (e) { + throw new Error(e); + } + }; + + const fetchChartHistogramData = async () => { + try { + const respStats: any = await api.esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: { + min_max: { + stats: { + field: columnType.id, + }, + }, + }, + size: 0, + }, + }); + + setStats([respStats.aggregations.min_max.min, respStats.aggregations.min_max.max]); + + const delta = respStats.aggregations.min_max.max - respStats.aggregations.min_max.min; + + let aggInterval = 1; + + if (delta > MAX_CHART_COLUMNS) { + aggInterval = Math.round(delta / MAX_CHART_COLUMNS); + } + + if (delta <= 1) { + aggInterval = delta / MAX_CHART_COLUMNS; + } + setInterval(aggInterval); + + const resp: any = await api.esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: { + chart: { + histogram: { + field: columnType.id, + interval: aggInterval, + }, + }, + }, + size: 0, + }, + }); + + setData(resp.aggregations.chart.buckets); + } catch (e) { + throw new Error(e); + } + }; + const fetchChartTermsData = async () => { + try { + const resp: any = await api.esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: { + chart: { + terms: { + field: columnType.id, + size: MAX_CHART_COLUMNS, + }, + }, + }, + size: 0, + }, + }); + setData(resp.aggregations.chart.buckets); + } catch (e) { + throw new Error(e); + } + }; + + useEffect(() => { + if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { + fetchChartHistogramData(); + } + if ( + fieldType === KBN_FIELD_TYPES.STRING || + fieldType === KBN_FIELD_TYPES.IP || + fieldType === KBN_FIELD_TYPES.BOOLEAN + ) { + fetchCardinality(); + fetchChartTermsData(); + } + }, [JSON.stringify(query)]); + + if (data.length === 0 || fieldType === KBN_FIELD_TYPES.OBJECT) { + return null; + } + + function getXScaleType(kbnFieldType: KBN_FIELD_TYPES) { + switch (kbnFieldType) { + case KBN_FIELD_TYPES.BOOLEAN: + case KBN_FIELD_TYPES.IP: + case KBN_FIELD_TYPES.STRING: + return 'ordinal'; + case KBN_FIELD_TYPES.DATE: + return 'time'; + case KBN_FIELD_TYPES.NUMBER: + return 'linear'; + } + } + + const xScaleType = getXScaleType(fieldType); + + const getColor = (d: DataItem) => { + const barColor = '#55b399'; + + if (hoveredRow === undefined || hoveredRow === null) { + return barColor; + } + + if (xScaleType === 'ordinal' && hoveredRow._source[columnType.id] === d.key) { + return barColor; + } + + if ( + xScaleType === 'linear' && + hoveredRow._source[columnType.id] >= +d.key && + hoveredRow._source[columnType.id] < +d.key + interval + ) { + return barColor; + } + + if ( + xScaleType === 'time' && + moment(hoveredRow._source[columnType.id]).unix() * 1000 >= +d.key && + moment(hoveredRow._source[columnType.id]).unix() * 1000 < +d.key + interval + ) { + return barColor; + } + + if (xScaleType === 'time') { + // console.log('time', moment(hoveredRow._source[columnType.id]).unix(), +d.key, d.key); + } + return '#d4dae5'; + }; + + const coloredData = data.map((d) => ({ ...d, color: getColor(d) })); + + return ( + <> +
+ + + d.datum.color} + data={coloredData} + /> + +
+
+ + + {(fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) && + cardinality === 1 && + `${cardinality} category`} + {(fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) && + cardinality > MAX_CHART_COLUMNS && + `top ${MAX_CHART_COLUMNS} of ${cardinality} categories`} + {(fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) && + cardinality <= MAX_CHART_COLUMNS && + cardinality > 1 && + `${cardinality} categories`} + {(fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) && + `${Math.round(stats[0] * 100) / 100} - ${Math.round(stats[1] * 100) / 100}`} + + +
+ + ); +}; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index 618075a77d906..f1dd02e8e6652 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -27,6 +27,9 @@ import { INDEX_STATUS } from '../../data_frame_analytics/common'; import { euiDataGridStyle, euiDataGridToolbarSettings } from './common'; import { UseIndexDataReturnType } from './types'; +// TODO Fix row hovering + bar highlighting +// import { hoveredRow$ } from './column_chart'; +import { useColumnCharts } from './use_column_charts'; export const DataGridTitle: FC<{ title: string }> = ({ title }) => ( @@ -71,8 +74,27 @@ export const DataGrid: FC = memo( tableItems: data, toastNotifications, visibleColumns, + // TODO move mini charts data fetching to outer hook + // @ts-ignore + api, + // @ts-ignore + indexPatternTitle, } = props; + const { refFn, columnResizeHandler } = useColumnCharts( + columns.filter((c) => visibleColumns.includes(c.id)), + api, + indexPatternTitle + ); + + // TODO Fix row hovering + bar highlighting + // const getRowProps = (item: any) => { + // return { + // onMouseOver: () => hoveredRow$.next(item), + // onMouseLeave: () => hoveredRow$.next(null), + // }; + // }; + useEffect(() => { if (invalidSortingColumnns.length > 0) { invalidSortingColumnns.forEach((columnId) => { @@ -162,22 +184,28 @@ export const DataGrid: FC = memo( )} - +
+ { + c.initialWidth = 160; + return c; + })} + columnVisibility={{ visibleColumns, setVisibleColumns }} + gridStyle={euiDataGridStyle} + rowCount={rowCount} + renderCellValue={renderCellValue} + sorting={{ columns: sortingColumns, onSort }} + toolbarVisibility={euiDataGridToolbarSettings} + pagination={{ + ...pagination, + pageSizeOptions: [5, 10, 25], + onChangeItemsPerPage, + onChangePage, + }} + onColumnResize={columnResizeHandler} + /> +
); }, diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx new file mode 100644 index 0000000000000..16b2220e67ebe --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx @@ -0,0 +1,76 @@ +/* + * 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 React, { useRef } from 'react'; +import ReactDOM from 'react-dom'; + +import { EuiDataGridColumn, EuiDataGridOnColumnResizeHandler } from '@elastic/eui'; + +import { ColumnChart } from './column_chart'; + +const mlDataGridChartClassName = 'mlDataGridChart'; +const mlDataGridChartRowClassName = `${mlDataGridChartClassName}Row`; + +type RefValue = HTMLElement | null; + +export function useColumnCharts(columns: EuiDataGridColumn[], api: any, indexPatternTitle: string) { + const ref = useRef(null); + + const refFn = (node: RefValue) => { + ref.current = node; + + if (node !== null) { + const tBody = node.getElementsByClassName('euiDataGrid__content')[0]; + const chartRows = tBody.getElementsByClassName(mlDataGridChartRowClassName); + + let chartRow; + if (chartRows.length > 0) { + chartRow = chartRows[0]; + } else { + chartRow = document.createElement('div'); + chartRow.classList.add(mlDataGridChartRowClassName); + chartRow.classList.add('euiDataGridRow'); + tBody.insertBefore(chartRow, tBody.childNodes[0]); + } + + const query = { match_all: {} }; + + ReactDOM.render( + <> + {columns.map((d, i) => ( +
+ {!d.isExpandable && ( + + )} +
+ ))} + , + chartRow + ); + } + }; + + const columnResizeHandler: EuiDataGridOnColumnResizeHandler = ({ columnId, width }) => { + if (ref !== null && ref.current !== null) { + const columnIndex = columns.findIndex((c) => c.id === columnId); + const chartElement = ref.current.querySelector(`.${mlDataGridChartClassName}-${columnIndex}`); + if (chartElement !== null) { + (chartElement as HTMLElement).style.width = `${width}px`; + } + } + }; + + return { refFn, columnResizeHandler }; +} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index e0b350542a8f8..3c90a251587ea 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -43,6 +43,7 @@ import { useIndexData } from '../../../../hooks/use_index_data'; import { usePivotData } from '../../../../hooks/use_pivot_data'; import { useToastNotifications } from '../../../../app_dependencies'; import { SearchItems } from '../../../../hooks/use_search_items'; +import { useApi } from '../../../../hooks/use_api'; import { AdvancedPivotEditor } from '../advanced_pivot_editor'; import { AdvancedPivotEditorSwitch } from '../advanced_pivot_editor_switch'; @@ -65,6 +66,8 @@ export const StepDefineForm: FC = React.memo((props) => { const { searchItems } = props; const { indexPattern } = searchItems; + const api = useApi(); + const toastNotifications = useToastNotifications(); const stepDefineForm = useStepDefineForm(props); @@ -293,7 +296,9 @@ export const StepDefineForm: FC = React.memo((props) => { - + {/* TODO move mini charts data fetching to outer hook + // @ts-ignore */} + From b2f1f3162ef4393173a9415954de9ba3916e51ba Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 5 Jun 2020 15:07:31 +0200 Subject: [PATCH 02/42] [ML] Adds button to toggle chart visibility. --- .../components/data_grid/common.ts | 1 + .../components/data_grid/data_grid.tsx | 27 +++++++++++++++++-- .../data_grid/use_column_charts.tsx | 18 ++++++++++--- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index 7d0559c215114..f3353f1e25eba 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -8,6 +8,7 @@ import moment from 'moment-timezone'; import { useEffect, useMemo } from 'react'; import { + EuiButtonEmpty, EuiDataGridCellValueElementProps, EuiDataGridSorting, EuiDataGridStyle, diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index f1dd02e8e6652..db43282feecfa 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -10,6 +10,7 @@ import React, { memo, useEffect, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { + EuiButtonEmpty, EuiButtonIcon, EuiCallOut, EuiCodeBlock, @@ -81,7 +82,12 @@ export const DataGrid: FC = memo( indexPatternTitle, } = props; - const { refFn, columnResizeHandler } = useColumnCharts( + const { + columnResizeHandler, + histogramVisible, + refFn, + toggleHistogramVisibility, + } = useColumnCharts( columns.filter((c) => visibleColumns.includes(c.id)), api, indexPatternTitle @@ -196,7 +202,24 @@ export const DataGrid: FC = memo( rowCount={rowCount} renderCellValue={renderCellValue} sorting={{ columns: sortingColumns, onSort }} - toolbarVisibility={euiDataGridToolbarSettings} + toolbarVisibility={{ + ...euiDataGridToolbarSettings, + additionalControls: ( + + {i18n.translate('xpack.ml.dataGrid.histogramButtonText', { + defaultMessage: 'Histogram Charts', + })} + + ), + }} pagination={{ ...pagination, pageSizeOptions: [5, 10, 25], diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx index 16b2220e67ebe..ef7f135f665f0 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import { EuiDataGridColumn, EuiDataGridOnColumnResizeHandler } from '@elastic/eui'; @@ -17,6 +17,12 @@ const mlDataGridChartRowClassName = `${mlDataGridChartClassName}Row`; type RefValue = HTMLElement | null; export function useColumnCharts(columns: EuiDataGridColumn[], api: any, indexPatternTitle: string) { + const [histogramVisible, setHistogramVisible] = useState(false); + + const toggleHistogramVisibility = () => { + setHistogramVisible(!histogramVisible); + }; + const ref = useRef(null); const refFn = (node: RefValue) => { @@ -29,11 +35,17 @@ export function useColumnCharts(columns: EuiDataGridColumn[], api: any, indexPat let chartRow; if (chartRows.length > 0) { chartRow = chartRows[0]; - } else { + if (!histogramVisible) { + chartRow.remove(); + return; + } + } else if (histogramVisible) { chartRow = document.createElement('div'); chartRow.classList.add(mlDataGridChartRowClassName); chartRow.classList.add('euiDataGridRow'); tBody.insertBefore(chartRow, tBody.childNodes[0]); + } else { + return; } const query = { match_all: {} }; @@ -72,5 +84,5 @@ export function useColumnCharts(columns: EuiDataGridColumn[], api: any, indexPat } }; - return { refFn, columnResizeHandler }; + return { columnResizeHandler, histogramVisible, refFn, toggleHistogramVisibility }; } From f1eb76600ce273a8fa480406fe4c190ba2b4f67e Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 5 Jun 2020 15:23:46 +0200 Subject: [PATCH 03/42] [ML] Fix hard coded colors. --- .../components/data_grid/column_chart.tsx | 19 ++++++++----------- .../components/data_grid/common.ts | 1 - 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index aba5633f6f96e..a85e8a83d48e2 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -9,7 +9,7 @@ import React, { useEffect, useState, FC } from 'react'; import { BehaviorSubject } from 'rxjs'; import { BarSeries, Chart, Settings } from '@elastic/charts'; -import { EuiText } from '@elastic/eui'; +import { euiPaletteColorBlind, EuiText } from '@elastic/eui'; // import { StaticIndexPattern } from 'ui/index_patterns'; @@ -34,6 +34,8 @@ interface DataItem { } const MAX_CHART_COLUMNS = 20; +const BAR_COLOR = euiPaletteColorBlind()[0]; +const BAR_COLOR_BLUR = euiPaletteColorBlind(2)[10]; export const ColumnChart: FC = ({ indexPatternTitle, columnType, query, api }) => { const [cardinality, setCardinality] = useState(0); @@ -198,14 +200,12 @@ export const ColumnChart: FC = ({ indexPatternTitle, columnType, query, a const xScaleType = getXScaleType(fieldType); const getColor = (d: DataItem) => { - const barColor = '#55b399'; - if (hoveredRow === undefined || hoveredRow === null) { - return barColor; + return BAR_COLOR; } if (xScaleType === 'ordinal' && hoveredRow._source[columnType.id] === d.key) { - return barColor; + return BAR_COLOR; } if ( @@ -213,7 +213,7 @@ export const ColumnChart: FC = ({ indexPatternTitle, columnType, query, a hoveredRow._source[columnType.id] >= +d.key && hoveredRow._source[columnType.id] < +d.key + interval ) { - return barColor; + return BAR_COLOR; } if ( @@ -221,13 +221,10 @@ export const ColumnChart: FC = ({ indexPatternTitle, columnType, query, a moment(hoveredRow._source[columnType.id]).unix() * 1000 >= +d.key && moment(hoveredRow._source[columnType.id]).unix() * 1000 < +d.key + interval ) { - return barColor; + return BAR_COLOR; } - if (xScaleType === 'time') { - // console.log('time', moment(hoveredRow._source[columnType.id]).unix(), +d.key, d.key); - } - return '#d4dae5'; + return BAR_COLOR_BLUR; }; const coloredData = data.map((d) => ({ ...d, color: getColor(d) })); diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index f3353f1e25eba..7d0559c215114 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -8,7 +8,6 @@ import moment from 'moment-timezone'; import { useEffect, useMemo } from 'react'; import { - EuiButtonEmpty, EuiDataGridCellValueElementProps, EuiDataGridSorting, EuiDataGridStyle, From 294eac6fb8bcae2c356da4e6e976956896630242 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 8 Jun 2020 09:24:44 +0200 Subject: [PATCH 04/42] [ML] Move data fetching to hook. Fix toggling column visibility. Move styles to SCSS file. --- .../components/data_grid/column_chart.scss | 19 ++ .../components/data_grid/column_chart.tsx | 244 ++---------------- .../components/data_grid/use_column_chart.ts | 225 ++++++++++++++++ .../data_grid/use_column_charts.tsx | 7 +- 4 files changed, 271 insertions(+), 224 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss new file mode 100644 index 0000000000000..f0ac56a7969d1 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss @@ -0,0 +1,19 @@ +.mlDataGridChart { + border-left: solid 1px #D3DAE6; + border-right: solid 1px #D3DAE6; + background-color: #f5f7fa; +} + +.mlDataGridChart + .mlDataGridChart { + border-left: solid 0px #D3DAE6; +} + +.mlDataGridChart__histogram { + width: 100%; + height: $euiSizeXL + $euiSizeXXL; +} + +.mlDataGridChart__legend { + display: block; + overflow: hidden; +} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index a85e8a83d48e2..abc3fbcd566c4 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -4,21 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import moment from 'moment'; -import React, { useEffect, useState, FC } from 'react'; -import { BehaviorSubject } from 'rxjs'; +import React, { FC } from 'react'; import { BarSeries, Chart, Settings } from '@elastic/charts'; -import { euiPaletteColorBlind, EuiText } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; -// import { StaticIndexPattern } from 'ui/index_patterns'; - -// TODO: Below import is temporary, use `react-use` lib instead. -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { useObservable } from 'react-use'; import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; -export const hoveredRow$ = new BehaviorSubject(null); +import './column_chart.scss'; + +import { useColumnChart } from './use_column_chart'; + +export const mlDataGridChartClassName = 'mlDataGridChart'; interface Props { indexPatternTitle: string; @@ -27,217 +24,29 @@ interface Props { api: any; } -interface DataItem { - key: string; - x: string | number; - y: number; -} - -const MAX_CHART_COLUMNS = 20; -const BAR_COLOR = euiPaletteColorBlind()[0]; -const BAR_COLOR_BLUR = euiPaletteColorBlind(2)[10]; - export const ColumnChart: FC = ({ indexPatternTitle, columnType, query, api }) => { - const [cardinality, setCardinality] = useState(0); - const [data, setData] = useState([]); - const [interval, setInterval] = useState(0); - const [stats, setStats] = useState([0, 0]); - - const hoveredRow = useObservable(hoveredRow$); - - let fieldType: KBN_FIELD_TYPES; - - switch (columnType.schema) { - case 'datetime': - fieldType = KBN_FIELD_TYPES.DATE; - break; - case 'numeric': - fieldType = KBN_FIELD_TYPES.NUMBER; - break; - case 'boolean': - fieldType = KBN_FIELD_TYPES.BOOLEAN; - break; - case 'json': - fieldType = KBN_FIELD_TYPES.OBJECT; - break; - default: - fieldType = KBN_FIELD_TYPES.STRING; - } - - const fetchCardinality = async () => { - try { - const resp: any = await api.esSearch({ - index: indexPatternTitle, - size: 0, - body: { - query, - aggs: { - categories: { - cardinality: { - field: columnType.id, - }, - }, - }, - size: 0, - }, - }); - setCardinality(resp.aggregations.categories.value); - } catch (e) { - throw new Error(e); - } - }; - - const fetchChartHistogramData = async () => { - try { - const respStats: any = await api.esSearch({ - index: indexPatternTitle, - size: 0, - body: { - query, - aggs: { - min_max: { - stats: { - field: columnType.id, - }, - }, - }, - size: 0, - }, - }); - - setStats([respStats.aggregations.min_max.min, respStats.aggregations.min_max.max]); - - const delta = respStats.aggregations.min_max.max - respStats.aggregations.min_max.min; - - let aggInterval = 1; - - if (delta > MAX_CHART_COLUMNS) { - aggInterval = Math.round(delta / MAX_CHART_COLUMNS); - } - - if (delta <= 1) { - aggInterval = delta / MAX_CHART_COLUMNS; - } - setInterval(aggInterval); - - const resp: any = await api.esSearch({ - index: indexPatternTitle, - size: 0, - body: { - query, - aggs: { - chart: { - histogram: { - field: columnType.id, - interval: aggInterval, - }, - }, - }, - size: 0, - }, - }); - - setData(resp.aggregations.chart.buckets); - } catch (e) { - throw new Error(e); - } - }; - const fetchChartTermsData = async () => { - try { - const resp: any = await api.esSearch({ - index: indexPatternTitle, - size: 0, - body: { - query, - aggs: { - chart: { - terms: { - field: columnType.id, - size: MAX_CHART_COLUMNS, - }, - }, - }, - size: 0, - }, - }); - setData(resp.aggregations.chart.buckets); - } catch (e) { - throw new Error(e); - } - }; - - useEffect(() => { - if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { - fetchChartHistogramData(); - } - if ( - fieldType === KBN_FIELD_TYPES.STRING || - fieldType === KBN_FIELD_TYPES.IP || - fieldType === KBN_FIELD_TYPES.BOOLEAN - ) { - fetchCardinality(); - fetchChartTermsData(); - } - }, [JSON.stringify(query)]); - - if (data.length === 0 || fieldType === KBN_FIELD_TYPES.OBJECT) { + const { + cardinality, + coloredData, + fieldType, + stats, + xScaleType, + MAX_CHART_COLUMNS, + } = useColumnChart(indexPatternTitle, columnType, query, api); + + if (coloredData.length === 0) { return null; } - function getXScaleType(kbnFieldType: KBN_FIELD_TYPES) { - switch (kbnFieldType) { - case KBN_FIELD_TYPES.BOOLEAN: - case KBN_FIELD_TYPES.IP: - case KBN_FIELD_TYPES.STRING: - return 'ordinal'; - case KBN_FIELD_TYPES.DATE: - return 'time'; - case KBN_FIELD_TYPES.NUMBER: - return 'linear'; - } - } - - const xScaleType = getXScaleType(fieldType); - - const getColor = (d: DataItem) => { - if (hoveredRow === undefined || hoveredRow === null) { - return BAR_COLOR; - } - - if (xScaleType === 'ordinal' && hoveredRow._source[columnType.id] === d.key) { - return BAR_COLOR; - } - - if ( - xScaleType === 'linear' && - hoveredRow._source[columnType.id] >= +d.key && - hoveredRow._source[columnType.id] < +d.key + interval - ) { - return BAR_COLOR; - } - - if ( - xScaleType === 'time' && - moment(hoveredRow._source[columnType.id]).unix() * 1000 >= +d.key && - moment(hoveredRow._source[columnType.id]).unix() * 1000 < +d.key + interval - ) { - return BAR_COLOR; - } - - return BAR_COLOR_BLUR; - }; - - const coloredData = data.map((d) => ({ ...d, color: getColor(d) })); - return ( <> -
- +
+ = ({ indexPatternTitle, columnType, query, a }} /> = ({ indexPatternTitle, columnType, query, a />
-
+
(null); + +interface DataItem { + key: string; + x: string | number; + y: number; +} + +const BAR_COLOR = euiPaletteColorBlind()[0]; +const BAR_COLOR_BLUR = euiPaletteColorBlind(2)[10]; +const MAX_CHART_COLUMNS = 20; + +const getXScaleType = ( + kbnFieldType: KBN_FIELD_TYPES +): 'ordinal' | 'time' | 'linear' | undefined => { + switch (kbnFieldType) { + case KBN_FIELD_TYPES.BOOLEAN: + case KBN_FIELD_TYPES.IP: + case KBN_FIELD_TYPES.STRING: + return 'ordinal'; + case KBN_FIELD_TYPES.DATE: + return 'time'; + case KBN_FIELD_TYPES.NUMBER: + return 'linear'; + } +}; + +export const useColumnChart = ( + indexPatternTitle: string, + columnType: any, + query: any, + api: any +) => { + const [cardinality, setCardinality] = useState(0); + const [data, setData] = useState([]); + const [interval, setInterval] = useState(0); + const [stats, setStats] = useState([0, 0]); + + const hoveredRow = useObservable(hoveredRow$); + + let fieldType: KBN_FIELD_TYPES; + + switch (columnType.schema) { + case 'datetime': + fieldType = KBN_FIELD_TYPES.DATE; + break; + case 'numeric': + fieldType = KBN_FIELD_TYPES.NUMBER; + break; + case 'boolean': + fieldType = KBN_FIELD_TYPES.BOOLEAN; + break; + case 'json': + fieldType = KBN_FIELD_TYPES.OBJECT; + break; + default: + fieldType = KBN_FIELD_TYPES.STRING; + } + + const fetchCardinality = async () => { + try { + const resp: any = await api.esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: { + categories: { + cardinality: { + field: columnType.id, + }, + }, + }, + size: 0, + }, + }); + setCardinality(resp.aggregations.categories.value); + } catch (e) { + throw new Error(e); + } + }; + + const fetchChartHistogramData = async () => { + try { + const respStats: any = await api.esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: { + min_max: { + stats: { + field: columnType.id, + }, + }, + }, + size: 0, + }, + }); + + setStats([respStats.aggregations.min_max.min, respStats.aggregations.min_max.max]); + + const delta = respStats.aggregations.min_max.max - respStats.aggregations.min_max.min; + + let aggInterval = 1; + + if (delta > MAX_CHART_COLUMNS) { + aggInterval = Math.round(delta / MAX_CHART_COLUMNS); + } + + if (delta <= 1) { + aggInterval = delta / MAX_CHART_COLUMNS; + } + setInterval(aggInterval); + + const resp: any = await api.esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: { + chart: { + histogram: { + field: columnType.id, + interval: aggInterval, + }, + }, + }, + size: 0, + }, + }); + + setData(resp.aggregations.chart.buckets); + } catch (e) { + throw new Error(e); + } + }; + const fetchChartTermsData = async () => { + try { + const resp: any = await api.esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: { + chart: { + terms: { + field: columnType.id, + size: MAX_CHART_COLUMNS, + }, + }, + }, + size: 0, + }, + }); + setData(resp.aggregations.chart.buckets); + } catch (e) { + throw new Error(e); + } + }; + + useEffect(() => { + if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { + fetchChartHistogramData(); + } + if ( + fieldType === KBN_FIELD_TYPES.STRING || + fieldType === KBN_FIELD_TYPES.IP || + fieldType === KBN_FIELD_TYPES.BOOLEAN + ) { + fetchCardinality(); + fetchChartTermsData(); + } + }, [JSON.stringify(query)]); + + const xScaleType = getXScaleType(fieldType); + + const getColor = (d: DataItem) => { + if (hoveredRow === undefined || hoveredRow === null) { + return BAR_COLOR; + } + + if (xScaleType === 'ordinal' && hoveredRow._source[columnType.id] === d.key) { + return BAR_COLOR; + } + + if ( + xScaleType === 'linear' && + hoveredRow._source[columnType.id] >= +d.key && + hoveredRow._source[columnType.id] < +d.key + interval + ) { + return BAR_COLOR; + } + + if ( + xScaleType === 'time' && + moment(hoveredRow._source[columnType.id]).unix() * 1000 >= +d.key && + moment(hoveredRow._source[columnType.id]).unix() * 1000 < +d.key + interval + ) { + return BAR_COLOR; + } + + return BAR_COLOR_BLUR; + }; + + const coloredData = data.map((d) => ({ ...d, color: getColor(d) })); + + return { cardinality, coloredData, fieldType, stats, xScaleType, MAX_CHART_COLUMNS }; +}; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx index ef7f135f665f0..dd80acda82cf9 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx @@ -9,9 +9,8 @@ import ReactDOM from 'react-dom'; import { EuiDataGridColumn, EuiDataGridOnColumnResizeHandler } from '@elastic/eui'; -import { ColumnChart } from './column_chart'; +import { mlDataGridChartClassName, ColumnChart } from './column_chart'; -const mlDataGridChartClassName = 'mlDataGridChart'; const mlDataGridChartRowClassName = `${mlDataGridChartClassName}Row`; type RefValue = HTMLElement | null; @@ -54,8 +53,8 @@ export function useColumnCharts(columns: EuiDataGridColumn[], api: any, indexPat <> {columns.map((d, i) => (
{!d.isExpandable && ( From 4e2ef596234bf3c52d449775cac1ff76f6c61b04 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 8 Jun 2020 16:29:54 +0200 Subject: [PATCH 05/42] [ML] Tweak SCSS. Fetch data in outer hook. TS improvements. --- .../components/data_grid/column_chart.scss | 24 ++- .../components/data_grid/column_chart.tsx | 72 ++++---- .../components/data_grid/data_grid.scss | 3 + .../components/data_grid/data_grid.tsx | 11 +- .../application/components/data_grid/index.ts | 1 + .../application/components/data_grid/types.ts | 5 + .../components/data_grid/use_column_chart.ts | 155 ++++++++++++------ .../data_grid/use_column_charts.tsx | 22 +-- .../components/data_grid/use_data_grid.ts | 4 + .../public/app/hooks/use_index_data.ts | 28 +++- .../transform/public/shared_imports.ts | 1 + 11 files changed, 211 insertions(+), 115 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss index f0ac56a7969d1..e616369deb0aa 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss @@ -1,11 +1,11 @@ .mlDataGridChart { - border-left: solid 1px #D3DAE6; - border-right: solid 1px #D3DAE6; - background-color: #f5f7fa; + border-left: solid 1px $euiColorLightShade; + border-right: solid 1px $euiColorLightShade; + background-color: $euiColorLightestShade; } .mlDataGridChart + .mlDataGridChart { - border-left: solid 0px #D3DAE6; + border-left: solid 0px $euiColorLightShade; } .mlDataGridChart__histogram { @@ -14,6 +14,18 @@ } .mlDataGridChart__legend { - display: block; - overflow: hidden; + color: $euiColorMediumShade; + display: block; + overflow: hidden; + margin: $euiSizeXS $euiSizeXS 0px $euiSizeXS; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 0.75rem; + font-style: italic; + line-height: 1; + text-align: left; +} + +.mlDataGridChart__legend--numeric { + text-align: right; } diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index abc3fbcd566c4..251fe74e5756d 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -7,32 +7,26 @@ import React, { FC } from 'react'; import { BarSeries, Chart, Settings } from '@elastic/charts'; -import { EuiText } from '@elastic/eui'; - -import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; +import { EuiDataGridColumn } from '@elastic/eui'; import './column_chart.scss'; -import { useColumnChart } from './use_column_chart'; +import { + isNumericChartData, + isOrdinalChartData, + useColumnChart, + ChartData, +} from './use_column_chart'; export const mlDataGridChartClassName = 'mlDataGridChart'; interface Props { - indexPatternTitle: string; - columnType: any; - query: any; - api: any; + chartData: ChartData; + columnType: EuiDataGridColumn; } -export const ColumnChart: FC = ({ indexPatternTitle, columnType, query, api }) => { - const { - cardinality, - coloredData, - fieldType, - stats, - xScaleType, - MAX_CHART_COLUMNS, - } = useColumnChart(indexPatternTitle, columnType, query, api); +export const ColumnChart: FC = ({ chartData, columnType }) => { + const { coloredData, xScaleType, MAX_CHART_COLUMNS } = useColumnChart(chartData, columnType); if (coloredData.length === 0) { return null; @@ -71,31 +65,25 @@ export const ColumnChart: FC = ({ indexPatternTitle, columnType, query, a />
-
- - - {(fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) && - cardinality === 1 && - `${cardinality} category`} - {(fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) && - cardinality > MAX_CHART_COLUMNS && - `top ${MAX_CHART_COLUMNS} of ${cardinality} categories`} - {(fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) && - cardinality <= MAX_CHART_COLUMNS && - cardinality > 1 && - `${cardinality} categories`} - {(fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) && - `${Math.round(stats[0] * 100) / 100} - ${Math.round(stats[1] * 100) / 100}`} - - +
+ {isOrdinalChartData(chartData) && + chartData.cardinality === 1 && + `${chartData.cardinality} category`} + {isOrdinalChartData(chartData) && + chartData.cardinality > MAX_CHART_COLUMNS && + `top ${MAX_CHART_COLUMNS} of ${chartData.cardinality} categories`} + {isOrdinalChartData(chartData) && + chartData.cardinality <= MAX_CHART_COLUMNS && + chartData.cardinality > 1 && + `${chartData.cardinality} categories`} + {isNumericChartData(chartData) && + `${Math.round(chartData.stats[0] * 100) / 100} - ${ + Math.round(chartData.stats[1] * 100) / 100 + }`}
); diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss new file mode 100644 index 0000000000000..bb46708dd5399 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss @@ -0,0 +1,3 @@ +.mlDataGridHeaderCell--paddingChart { + padding: 0 $euiSizeXS 0px $euiSizeXS !important; +} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index db43282feecfa..80661a7bed83d 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -32,6 +32,8 @@ import { UseIndexDataReturnType } from './types'; // import { hoveredRow$ } from './column_chart'; import { useColumnCharts } from './use_column_charts'; +import './data_grid.scss'; + export const DataGridTitle: FC<{ title: string }> = ({ title }) => ( {title} @@ -58,6 +60,7 @@ type Props = PropsWithHeader | PropsWithoutHeader; export const DataGrid: FC = memo( (props) => { const { + columnCharts, columns, dataTestSubj, errorMessage, @@ -75,11 +78,6 @@ export const DataGrid: FC = memo( tableItems: data, toastNotifications, visibleColumns, - // TODO move mini charts data fetching to outer hook - // @ts-ignore - api, - // @ts-ignore - indexPatternTitle, } = props; const { @@ -89,8 +87,7 @@ export const DataGrid: FC = memo( toggleHistogramVisibility, } = useColumnCharts( columns.filter((c) => visibleColumns.includes(c.id)), - api, - indexPatternTitle + columnCharts ); // TODO Fix row hovering + bar highlighting diff --git a/x-pack/plugins/ml/public/application/components/data_grid/index.ts b/x-pack/plugins/ml/public/application/components/data_grid/index.ts index 2472878d1b0c1..97f6c91d42480 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/index.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/index.ts @@ -11,6 +11,7 @@ export { multiColumnSortFactory, useRenderCellValue, } from './common'; +export { fetchChartData } from './use_column_chart'; export { useDataGrid } from './use_data_grid'; export { DataGrid } from './data_grid'; export { diff --git a/x-pack/plugins/ml/public/application/components/data_grid/types.ts b/x-pack/plugins/ml/public/application/components/data_grid/types.ts index 5fa038edf7815..409ade874daac 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/types.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/types.ts @@ -13,6 +13,8 @@ import { Dictionary } from '../../../../common/types/common'; import { INDEX_STATUS } from '../../data_frame_analytics/common/analytics'; +import { ChartData } from './use_column_chart'; + export type ColumnId = string; export type DataGridItem = Record; @@ -54,6 +56,7 @@ export interface SearchResponse7 extends SearchResponse { export interface UseIndexDataReturnType extends Pick< UseDataGridReturnType, + | 'columnCharts' | 'errorMessage' | 'invalidSortingColumnns' | 'noDataMessage' @@ -74,6 +77,7 @@ export interface UseIndexDataReturnType } export interface UseDataGridReturnType { + columnCharts: ChartData[]; errorMessage: string; invalidSortingColumnns: ColumnId[]; noDataMessage: string; @@ -83,6 +87,7 @@ export interface UseDataGridReturnType { pagination: IndexPagination; resetPagination: () => void; rowCount: number; + setColumnCharts: Dispatch>; setErrorMessage: Dispatch>; setNoDataMessage: Dispatch>; setPagination: Dispatch>; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts index 0d3e74464ea58..259be7ebcbc95 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts @@ -5,23 +5,16 @@ */ import moment from 'moment'; -import { useEffect, useState } from 'react'; import { BehaviorSubject } from 'rxjs'; import { useObservable } from 'react-use'; -import { euiPaletteColorBlind } from '@elastic/eui'; +import { euiPaletteColorBlind, EuiDataGridColumn } from '@elastic/eui'; import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; export const hoveredRow$ = new BehaviorSubject(null); -interface DataItem { - key: string; - x: string | number; - y: number; -} - const BAR_COLOR = euiPaletteColorBlind()[0]; const BAR_COLOR_BLUR = euiPaletteColorBlind(2)[10]; const MAX_CHART_COLUMNS = 20; @@ -41,22 +34,10 @@ const getXScaleType = ( } }; -export const useColumnChart = ( - indexPatternTitle: string, - columnType: any, - query: any, - api: any -) => { - const [cardinality, setCardinality] = useState(0); - const [data, setData] = useState([]); - const [interval, setInterval] = useState(0); - const [stats, setStats] = useState([0, 0]); - - const hoveredRow = useObservable(hoveredRow$); - +const getFieldType = (schema: EuiDataGridColumn['schema']) => { let fieldType: KBN_FIELD_TYPES; - switch (columnType.schema) { + switch (schema) { case 'datetime': fieldType = KBN_FIELD_TYPES.DATE; break; @@ -73,6 +54,17 @@ export const useColumnChart = ( fieldType = KBN_FIELD_TYPES.STRING; } + return fieldType; +}; + +export const fetchChartData = async ( + indexPatternTitle: string, + api: any, + query: any, + columnType: any +): Promise => { + const fieldType = getFieldType(columnType.schema); + const fetchCardinality = async () => { try { const resp: any = await api.esSearch({ @@ -90,7 +82,7 @@ export const useColumnChart = ( size: 0, }, }); - setCardinality(resp.aggregations.categories.value); + return resp.aggregations.categories.value; } catch (e) { throw new Error(e); } @@ -114,7 +106,7 @@ export const useColumnChart = ( }, }); - setStats([respStats.aggregations.min_max.min, respStats.aggregations.min_max.max]); + const stats = [respStats.aggregations.min_max.min, respStats.aggregations.min_max.max]; const delta = respStats.aggregations.min_max.max - respStats.aggregations.min_max.min; @@ -127,7 +119,6 @@ export const useColumnChart = ( if (delta <= 1) { aggInterval = delta / MAX_CHART_COLUMNS; } - setInterval(aggInterval); const resp: any = await api.esSearch({ index: indexPatternTitle, @@ -146,11 +137,16 @@ export const useColumnChart = ( }, }); - setData(resp.aggregations.chart.buckets); + return { + data: resp.aggregations.chart.buckets, + interval: aggInterval, + stats, + }; } catch (e) { throw new Error(e); } }; + const fetchChartTermsData = async () => { try { const resp: any = await api.esSearch({ @@ -169,49 +165,94 @@ export const useColumnChart = ( size: 0, }, }); - setData(resp.aggregations.chart.buckets); + return resp.aggregations.chart.buckets; } catch (e) { throw new Error(e); } }; - useEffect(() => { - if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { - fetchChartHistogramData(); - } - if ( - fieldType === KBN_FIELD_TYPES.STRING || - fieldType === KBN_FIELD_TYPES.IP || - fieldType === KBN_FIELD_TYPES.BOOLEAN - ) { - fetchCardinality(); - fetchChartTermsData(); - } - }, [JSON.stringify(query)]); + if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { + return (await fetchChartHistogramData()) as NumericChartData; + } + if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) { + // TODO query in parallel + const cardinality = await fetchCardinality(); + const terms = await fetchChartTermsData(); + return { cardinality, data: terms } as OrdinalChartData; + } + + throw new Error('Invalid fieldType'); +}; + +interface NumericDataItem { + key: number; + key_as_string?: string; + doc_count: number; +} + +interface NumericChartData { + data: NumericDataItem[]; + interval: number; + stats: [number, number]; +} + +export const isNumericChartData = (arg: any): arg is NumericChartData => { + return ( + arg.hasOwnProperty('data') && arg.hasOwnProperty('interval') && arg.hasOwnProperty('stats') + ); +}; + +interface OrdinalDataItem { + key: string; + doc_count: number; +} + +interface OrdinalChartData { + cardinality: number; + data: OrdinalDataItem[]; +} + +export const isOrdinalChartData = (arg: any): arg is OrdinalChartData => { + return arg.hasOwnProperty('data') && arg.hasOwnProperty('cardinality'); +}; + +type ChartDataItem = NumericDataItem | OrdinalDataItem; +export type ChartData = NumericChartData | OrdinalChartData; + +export const useColumnChart = (chartData: ChartData, columnType: EuiDataGridColumn) => { + const fieldType = getFieldType(columnType.schema); + + const hoveredRow = useObservable(hoveredRow$); const xScaleType = getXScaleType(fieldType); - const getColor = (d: DataItem) => { + const getColor = (d: ChartDataItem) => { if (hoveredRow === undefined || hoveredRow === null) { return BAR_COLOR; } - if (xScaleType === 'ordinal' && hoveredRow._source[columnType.id] === d.key) { + if ( + isOrdinalChartData(chartData) && + xScaleType === 'ordinal' && + hoveredRow._source[columnType.id] === d.key + ) { return BAR_COLOR; } if ( + isNumericChartData(chartData) && xScaleType === 'linear' && hoveredRow._source[columnType.id] >= +d.key && - hoveredRow._source[columnType.id] < +d.key + interval + hoveredRow._source[columnType.id] < +d.key + chartData.interval ) { return BAR_COLOR; } if ( + isNumericChartData(chartData) && xScaleType === 'time' && moment(hoveredRow._source[columnType.id]).unix() * 1000 >= +d.key && - moment(hoveredRow._source[columnType.id]).unix() * 1000 < +d.key + interval + moment(hoveredRow._source[columnType.id]).unix() * 1000 < +d.key + chartData.interval ) { return BAR_COLOR; } @@ -219,7 +260,27 @@ export const useColumnChart = ( return BAR_COLOR_BLUR; }; - const coloredData = data.map((d) => ({ ...d, color: getColor(d) })); - - return { cardinality, coloredData, fieldType, stats, xScaleType, MAX_CHART_COLUMNS }; + // The if/else if/else is a work-around because `.map()` doesn't work with union types. + // See TS Caveats for details: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-3.html#caveats + if (isOrdinalChartData(chartData)) { + const coloredData = chartData.data.map((d: ChartDataItem) => ({ ...d, color: getColor(d) })); + return { + cardinality: chartData.cardinality, + coloredData, + fieldType, + xScaleType, + MAX_CHART_COLUMNS, + }; + } else if (isNumericChartData(chartData)) { + const coloredData = chartData.data.map((d: ChartDataItem) => ({ ...d, color: getColor(d) })); + return { + coloredData, + fieldType, + stats: chartData.stats, + xScaleType, + MAX_CHART_COLUMNS, + }; + } else { + throw new Error('invalid chart data.'); + } }; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx index dd80acda82cf9..476974c0d6212 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx @@ -15,7 +15,7 @@ const mlDataGridChartRowClassName = `${mlDataGridChartClassName}Row`; type RefValue = HTMLElement | null; -export function useColumnCharts(columns: EuiDataGridColumn[], api: any, indexPatternTitle: string) { +export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: any) { const [histogramVisible, setHistogramVisible] = useState(false); const toggleHistogramVisibility = () => { @@ -36,6 +36,10 @@ export function useColumnCharts(columns: EuiDataGridColumn[], api: any, indexPat chartRow = chartRows[0]; if (!histogramVisible) { chartRow.remove(); + const columnHeaders = tBody.getElementsByClassName('euiDataGridHeaderCell'); + for (let i = 0; i < columnHeaders.length; i++) { + columnHeaders[i].classList.remove('mlDataGridHeaderCell--paddingChart'); + } return; } } else if (histogramVisible) { @@ -43,12 +47,15 @@ export function useColumnCharts(columns: EuiDataGridColumn[], api: any, indexPat chartRow.classList.add(mlDataGridChartRowClassName); chartRow.classList.add('euiDataGridRow'); tBody.insertBefore(chartRow, tBody.childNodes[0]); + + const columnHeaders = tBody.getElementsByClassName('euiDataGridHeaderCell'); + for (let i = 0; i < columnHeaders.length; i++) { + columnHeaders[i].classList.add('mlDataGridHeaderCell--paddingChart'); + } } else { return; } - const query = { match_all: {} }; - ReactDOM.render( <> {columns.map((d, i) => ( @@ -57,14 +64,7 @@ export function useColumnCharts(columns: EuiDataGridColumn[], api: any, indexPat className={`${mlDataGridChartClassName} ${mlDataGridChartClassName}-${i}`} style={{ width: `${d.initialWidth}px` }} > - {!d.isExpandable && ( - - )} + {!d.isExpandable && }
))} , diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts index 7843bf2ea801b..58ab4af51a2ad 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts @@ -20,6 +20,7 @@ import { OnSort, UseDataGridReturnType, } from './types'; +import { ChartData } from './use_column_chart'; export const useDataGrid = ( columns: EuiDataGridColumn[], @@ -33,6 +34,7 @@ export const useDataGrid = ( const [errorMessage, setErrorMessage] = useState(''); const [status, setStatus] = useState(INDEX_STATUS.UNUSED); const [rowCount, setRowCount] = useState(0); + const [columnCharts, setColumnCharts] = useState([]); const [tableItems, setTableItems] = useState([]); const [pagination, setPagination] = useState(defaultPagination); const [sortingColumns, setSortingColumns] = useState([]); @@ -87,6 +89,7 @@ export const useDataGrid = ( ); return { + columnCharts, errorMessage, invalidSortingColumnns, noDataMessage, @@ -96,6 +99,7 @@ export const useDataGrid = ( pagination, resetPagination, rowCount, + setColumnCharts, setErrorMessage, setNoDataMessage, setPagination, diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index e1aa27df5e979..2000fc0dadf9a 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -7,6 +7,7 @@ import { useEffect } from 'react'; import { + fetchChartData, getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, getErrorMessage, @@ -47,6 +48,7 @@ export const useIndexData = ( const { pagination, resetPagination, + setColumnCharts, setErrorMessage, setRowCount, setStatus, @@ -61,7 +63,7 @@ export const useIndexData = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(query)]); - const getIndexData = async function () { + const fetchDataGridData = async function () { setErrorMessage(''); setStatus(INDEX_STATUS.LOADING); @@ -92,15 +94,37 @@ export const useIndexData = ( } catch (e) { setErrorMessage(getErrorMessage(e)); setStatus(INDEX_STATUS.ERROR); + return; } }; + const fetchColumnChartsData = async function () { + const fetchers = dataGrid.visibleColumns.map((vc) => { + const columnType = columns.find((c) => c.id === vc); + return fetchChartData( + indexPattern.title, + api, + isDefaultQuery(query) ? matchAllQuery : query, + columnType + ); + }); + + const data = await Promise.all(fetchers); + setColumnCharts(data); + }; + useEffect(() => { - getIndexData(); + fetchDataGridData(); // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]); + useEffect(() => { + fetchColumnChartsData(); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [indexPattern.title, JSON.stringify([query, dataGrid.visibleColumns])]); + const renderCellValue = useRenderCellValue(indexPattern, pagination, tableItems); return { diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index 56be8d7bb7de7..43d4233df1139 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -14,6 +14,7 @@ export { } from '../../../../src/plugins/es_ui_shared/public'; export { + fetchChartData, getErrorMessage, getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, From f3873bdaea8125b23cf2413d2d7c1f4fa766ab9f Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 9 Jun 2020 09:40:26 +0200 Subject: [PATCH 06/42] [ML] WIP: Fix column toggling when charts are enabled. --- .../application/components/data_grid/index.ts | 2 +- .../components/data_grid/use_column_chart.ts | 26 ++++++++++++++----- .../data_grid/use_column_charts.tsx | 26 ++++++++++++------- .../public/app/hooks/use_index_data.ts | 13 +++++++--- .../transform/public/shared_imports.ts | 1 + 5 files changed, 47 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/index.ts b/x-pack/plugins/ml/public/application/components/data_grid/index.ts index 97f6c91d42480..c1f441b61f956 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/index.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/index.ts @@ -11,7 +11,7 @@ export { multiColumnSortFactory, useRenderCellValue, } from './common'; -export { fetchChartData } from './use_column_chart'; +export { fetchChartData, ChartData } from './use_column_chart'; export { useDataGrid } from './use_data_grid'; export { DataGrid } from './data_grid'; export { diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts index 259be7ebcbc95..c26b0491cb369 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts @@ -61,7 +61,7 @@ export const fetchChartData = async ( indexPatternTitle: string, api: any, query: any, - columnType: any + columnType: EuiDataGridColumn ): Promise => { const fieldType = getFieldType(columnType.schema); @@ -172,13 +172,18 @@ export const fetchChartData = async ( }; if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { - return (await fetchChartHistogramData()) as NumericChartData; + return { + ...((await fetchChartHistogramData()) as NumericChartData), + id: columnType.id, + }; } if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) { // TODO query in parallel - const cardinality = await fetchCardinality(); - const terms = await fetchChartTermsData(); - return { cardinality, data: terms } as OrdinalChartData; + return { + cardinality: await fetchCardinality(), + data: await fetchChartTermsData(), + id: columnType.id, + } as OrdinalChartData; } throw new Error('Invalid fieldType'); @@ -192,13 +197,17 @@ interface NumericDataItem { interface NumericChartData { data: NumericDataItem[]; + id: string; interval: number; stats: [number, number]; } export const isNumericChartData = (arg: any): arg is NumericChartData => { return ( - arg.hasOwnProperty('data') && arg.hasOwnProperty('interval') && arg.hasOwnProperty('stats') + arg.hasOwnProperty('data') && + arg.hasOwnProperty('id') && + arg.hasOwnProperty('interval') && + arg.hasOwnProperty('stats') ); }; @@ -210,10 +219,13 @@ interface OrdinalDataItem { interface OrdinalChartData { cardinality: number; data: OrdinalDataItem[]; + id: string; } export const isOrdinalChartData = (arg: any): arg is OrdinalChartData => { - return arg.hasOwnProperty('data') && arg.hasOwnProperty('cardinality'); + return ( + arg.hasOwnProperty('data') && arg.hasOwnProperty('cardinality') && arg.hasOwnProperty('id') + ); }; type ChartDataItem = NumericDataItem | OrdinalDataItem; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx index 476974c0d6212..58d28cfa02de6 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx @@ -10,12 +10,13 @@ import ReactDOM from 'react-dom'; import { EuiDataGridColumn, EuiDataGridOnColumnResizeHandler } from '@elastic/eui'; import { mlDataGridChartClassName, ColumnChart } from './column_chart'; +import { ChartData } from './use_column_chart'; const mlDataGridChartRowClassName = `${mlDataGridChartClassName}Row`; type RefValue = HTMLElement | null; -export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: any) { +export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: ChartData[]) { const [histogramVisible, setHistogramVisible] = useState(false); const toggleHistogramVisibility = () => { @@ -58,15 +59,20 @@ export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: any) { ReactDOM.render( <> - {columns.map((d, i) => ( -
- {!d.isExpandable && } -
- ))} + {columns.map((d, i) => { + const chartData = chartsData.find((cd) => cd.id === d.id); + return ( +
+ {!d.isExpandable && chartData !== undefined && ( + + )} +
+ ); + })} , chartRow ); diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index 2000fc0dadf9a..4b6636939adf8 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -6,6 +6,8 @@ import { useEffect } from 'react'; +import { EuiDataGridColumn } from '@elastic/eui'; + import { fetchChartData, getDataGridSchemaFromKibanaFieldType, @@ -13,6 +15,7 @@ import { getErrorMessage, useDataGrid, useRenderCellValue, + ChartData, EsSorting, SearchResponse7, UseIndexDataReturnType, @@ -35,7 +38,7 @@ export const useIndexData = ( const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern); // EuiDataGrid State - const columns = [ + const columns: EuiDataGridColumn[] = [ ...indexPatternFields.map((id) => { const field = indexPattern.fields.getByName(id); const schema = getDataGridSchemaFromKibanaFieldType(field); @@ -101,6 +104,10 @@ export const useIndexData = ( const fetchColumnChartsData = async function () { const fetchers = dataGrid.visibleColumns.map((vc) => { const columnType = columns.find((c) => c.id === vc); + + if (columnType === undefined) { + return Promise.resolve(undefined); + } return fetchChartData( indexPattern.title, api, @@ -109,8 +116,8 @@ export const useIndexData = ( ); }); - const data = await Promise.all(fetchers); - setColumnCharts(data); + const data = (await Promise.all(fetchers)) as ChartData[]; + setColumnCharts(data.filter((d) => d !== undefined)); }; useEffect(() => { diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index 43d4233df1139..cbc0a401bd3d9 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -21,6 +21,7 @@ export { multiColumnSortFactory, useDataGrid, useRenderCellValue, + ChartData, DataGrid, EsSorting, RenderCellValue, From 9e4423a665958f94f0ae878ef09e7cf228503431 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 9 Jun 2020 12:21:00 +0200 Subject: [PATCH 07/42] [ML] Fix column toggling. --- .../application/components/data_grid/use_column_charts.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx index 58d28cfa02de6..04bb494230ef4 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx @@ -68,7 +68,7 @@ export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: ChartD style={{ width: `${d.initialWidth}px` }} > {!d.isExpandable && chartData !== undefined && ( - + )}
); From b571b9c15fdeaf36eb4a29b507e7736de0ab38fb Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 9 Jun 2020 12:25:44 +0200 Subject: [PATCH 08/42] [ML] Translate chart legend. --- .../components/data_grid/column_chart.tsx | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index 251fe74e5756d..3674c6e0b08df 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -9,6 +9,8 @@ import React, { FC } from 'react'; import { BarSeries, Chart, Settings } from '@elastic/charts'; import { EuiDataGridColumn } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + import './column_chart.scss'; import { @@ -20,6 +22,41 @@ import { export const mlDataGridChartClassName = 'mlDataGridChart'; +const getLegendText = (chartData: ChartData, MAX_CHART_COLUMNS: number): string => { + if (isOrdinalChartData(chartData) && chartData.cardinality === 1) { + return i18n.translate('xpack.ml.dataGridChart.singleCategoryLegend', { + defaultMessage: `{cardinality} category`, + values: { cardinality: chartData.cardinality }, + }); + } + + if (isOrdinalChartData(chartData) && chartData.cardinality > MAX_CHART_COLUMNS) { + return i18n.translate('xpack.ml.dataGridChart.topCategoriesLegend', { + defaultMessage: `top {MAX_CHART_COLUMNS} of {cardinality} categories`, + values: { cardinality: chartData.cardinality, MAX_CHART_COLUMNS }, + }); + } + + if ( + isOrdinalChartData(chartData) && + chartData.cardinality <= MAX_CHART_COLUMNS && + chartData.cardinality > 1 + ) { + return i18n.translate('xpack.ml.dataGridChart.categoriesLegend', { + defaultMessage: `{cardinality} categories`, + values: { cardinality: chartData.cardinality }, + }); + } + + if (isNumericChartData(chartData)) { + return `${Math.round(chartData.stats[0] * 100) / 100} - ${ + Math.round(chartData.stats[1] * 100) / 100 + }`; + } + + throw new Error('Invalid chart data.'); +}; + interface Props { chartData: ChartData; columnType: EuiDataGridColumn; @@ -70,20 +107,7 @@ export const ColumnChart: FC = ({ chartData, columnType }) => { isNumericChartData(chartData) ? ' mlDataGridChart__legend--numeric' : '' }`} > - {isOrdinalChartData(chartData) && - chartData.cardinality === 1 && - `${chartData.cardinality} category`} - {isOrdinalChartData(chartData) && - chartData.cardinality > MAX_CHART_COLUMNS && - `top ${MAX_CHART_COLUMNS} of ${chartData.cardinality} categories`} - {isOrdinalChartData(chartData) && - chartData.cardinality <= MAX_CHART_COLUMNS && - chartData.cardinality > 1 && - `${chartData.cardinality} categories`} - {isNumericChartData(chartData) && - `${Math.round(chartData.stats[0] * 100) / 100} - ${ - Math.round(chartData.stats[1] * 100) / 100 - }`} + {getLegendText(chartData, MAX_CHART_COLUMNS)}
); From 34bfb7af958f6bffb15580ff85254881cd54b223 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 9 Jun 2020 17:33:44 +0200 Subject: [PATCH 09/42] [ML] Fix column resizing. --- .../ml/common/util/group_color_utils.ts | 16 +------- .../ml/common/util/string_utils.test.ts | 8 +++- x-pack/plugins/ml/common/util/string_utils.ts | 17 ++++++++ .../components/data_grid/data_grid.tsx | 11 ++---- .../data_grid/use_column_charts.tsx | 39 +++++++++++++------ 5 files changed, 56 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/ml/common/util/group_color_utils.ts b/x-pack/plugins/ml/common/util/group_color_utils.ts index 92f5c6b2c1347..7105919274185 100644 --- a/x-pack/plugins/ml/common/util/group_color_utils.ts +++ b/x-pack/plugins/ml/common/util/group_color_utils.ts @@ -6,6 +6,8 @@ import euiVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { stringHash } from './string_utils'; + const COLORS = [ euiVars.euiColorVis0, euiVars.euiColorVis1, @@ -33,17 +35,3 @@ export function tabColor(name: string): string { return colorMap[name]; } } - -function stringHash(str: string): number { - let hash = 0; - let chr = 0; - if (str.length === 0) { - return hash; - } - for (let i = 0; i < str.length; i++) { - chr = str.charCodeAt(i); - hash = (hash << 5) - hash + chr; // eslint-disable-line no-bitwise - hash |= 0; // eslint-disable-line no-bitwise - } - return hash < 0 ? hash * -2 : hash; -} diff --git a/x-pack/plugins/ml/common/util/string_utils.test.ts b/x-pack/plugins/ml/common/util/string_utils.test.ts index 026c8e6110c99..e6de1e20bf057 100644 --- a/x-pack/plugins/ml/common/util/string_utils.test.ts +++ b/x-pack/plugins/ml/common/util/string_utils.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderTemplate, getMedianStringLength } from './string_utils'; +import { renderTemplate, getMedianStringLength, stringHash } from './string_utils'; const strings: string[] = [ 'foo', @@ -46,4 +46,10 @@ describe('ML - string utils', () => { expect(result).toBe(0); }); }); + + describe('stringHash', () => { + test('should return a number based off a string', () => { + expect(typeof stringHash('the-string')).toBe('number'); + }); + }); }); diff --git a/x-pack/plugins/ml/common/util/string_utils.ts b/x-pack/plugins/ml/common/util/string_utils.ts index bd4ca02bf93cc..b4591fd2943e6 100644 --- a/x-pack/plugins/ml/common/util/string_utils.ts +++ b/x-pack/plugins/ml/common/util/string_utils.ts @@ -22,3 +22,20 @@ export function getMedianStringLength(strings: string[]) { const sortedStringLengths = strings.map((s) => s.length).sort((a, b) => a - b); return sortedStringLengths[Math.floor(sortedStringLengths.length / 2)] || 0; } + +/** + * Creates a deterministic number based hash out of a string. + */ +export function stringHash(str: string): number { + let hash = 0; + let chr = 0; + if (str.length === 0) { + return hash; + } + for (let i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + hash = (hash << 5) - hash + chr; // eslint-disable-line no-bitwise + hash |= 0; // eslint-disable-line no-bitwise + } + return hash < 0 ? hash * -2 : hash; +} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index 80661a7bed83d..915d909564979 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -80,12 +80,7 @@ export const DataGrid: FC = memo( visibleColumns, } = props; - const { - columnResizeHandler, - histogramVisible, - refFn, - toggleHistogramVisibility, - } = useColumnCharts( + const { columnResizeHandler, histogramVisible, refFn, toggleChartVisibility } = useColumnCharts( columns.filter((c) => visibleColumns.includes(c.id)), columnCharts ); @@ -191,7 +186,7 @@ export const DataGrid: FC = memo( { - c.initialWidth = 160; + c.initialWidth = 150; return c; })} columnVisibility={{ visibleColumns, setVisibleColumns }} @@ -209,7 +204,7 @@ export const DataGrid: FC = memo( size="xs" iconType="visBarVertical" color="text" - onClick={toggleHistogramVisibility} + onClick={toggleChartVisibility} > {i18n.translate('xpack.ml.dataGrid.histogramButtonText', { defaultMessage: 'Histogram Charts', diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx index 04bb494230ef4..228028c004e00 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx @@ -9,23 +9,29 @@ import ReactDOM from 'react-dom'; import { EuiDataGridColumn, EuiDataGridOnColumnResizeHandler } from '@elastic/eui'; +import { stringHash } from '../../../../common/util/string_utils'; + import { mlDataGridChartClassName, ColumnChart } from './column_chart'; import { ChartData } from './use_column_chart'; const mlDataGridChartRowClassName = `${mlDataGridChartClassName}Row`; +type ColumnWidths = Record; type RefValue = HTMLElement | null; export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: ChartData[]) { const [histogramVisible, setHistogramVisible] = useState(false); - const toggleHistogramVisibility = () => { + const toggleChartVisibility = () => { setHistogramVisible(!histogramVisible); }; const ref = useRef(null); - const refFn = (node: RefValue) => { + renderCharts(node); + }; + + const renderCharts = (node: RefValue, columnsWidths?: ColumnWidths) => { ref.current = node; if (node !== null) { @@ -59,13 +65,16 @@ export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: ChartD ReactDOM.render( <> - {columns.map((d, i) => { + {columns.map((d) => { const chartData = chartsData.find((cd) => cd.id === d.id); + const columnWidth = (columnsWidths && columnsWidths[d.id]) || d.initialWidth; return (
{!d.isExpandable && chartData !== undefined && ( @@ -80,14 +89,20 @@ export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: ChartD }; const columnResizeHandler: EuiDataGridOnColumnResizeHandler = ({ columnId, width }) => { - if (ref !== null && ref.current !== null) { - const columnIndex = columns.findIndex((c) => c.id === columnId); - const chartElement = ref.current.querySelector(`.${mlDataGridChartClassName}-${columnIndex}`); - if (chartElement !== null) { - (chartElement as HTMLElement).style.width = `${width}px`; + const columnWidths = columns.reduce((p, c) => { + if (columnId === c.id) { + p[c.id] = width; + } else if (ref !== null && ref.current !== null) { + const chartElement = ref.current.querySelector( + `.${mlDataGridChartClassName}-${stringHash(c.id)}` + ); + p[c.id] = + chartElement !== null ? (chartElement as HTMLElement).offsetWidth : c?.initialWidth ?? 0; } - } + return p; + }, {} as ColumnWidths); + renderCharts(ref.current, columnWidths); }; - return { columnResizeHandler, histogramVisible, refFn, toggleHistogramVisibility }; + return { columnResizeHandler, histogramVisible, refFn, toggleChartVisibility }; } From 072b6981c74526075d3f88fa6bd2ca8d8a17cc70 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 9 Jun 2020 17:59:45 +0200 Subject: [PATCH 10/42] [ML] Only fetch charts data when they are visible. --- .../components/data_grid/data_grid.tsx | 9 ++++++--- .../application/components/data_grid/types.ts | 4 ++++ .../data_grid/use_column_charts.tsx | 20 +++++++++---------- .../components/data_grid/use_data_grid.ts | 7 +++++++ .../public/app/hooks/use_index_data.ts | 7 +++++-- 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index 915d909564979..cc742ee475492 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -60,6 +60,7 @@ type Props = PropsWithHeader | PropsWithoutHeader; export const DataGrid: FC = memo( (props) => { const { + chartsVisible, columnCharts, columns, dataTestSubj, @@ -77,12 +78,14 @@ export const DataGrid: FC = memo( status, tableItems: data, toastNotifications, + toggleChartVisibility, visibleColumns, } = props; - const { columnResizeHandler, histogramVisible, refFn, toggleChartVisibility } = useColumnCharts( + const { columnResizeHandler, refFn } = useColumnCharts( columns.filter((c) => visibleColumns.includes(c.id)), - columnCharts + columnCharts, + chartsVisible ); // TODO Fix row hovering + bar highlighting @@ -199,7 +202,7 @@ export const DataGrid: FC = memo( additionalControls: ( { export interface UseIndexDataReturnType extends Pick< UseDataGridReturnType, + | 'chartsVisible' | 'columnCharts' | 'errorMessage' | 'invalidSortingColumnns' @@ -70,6 +71,7 @@ export interface UseIndexDataReturnType | 'sortingColumns' | 'status' | 'tableItems' + | 'toggleChartVisibility' | 'visibleColumns' > { columns: EuiDataGridColumn[]; @@ -77,6 +79,7 @@ export interface UseIndexDataReturnType } export interface UseDataGridReturnType { + chartsVisible: boolean; columnCharts: ChartData[]; errorMessage: string; invalidSortingColumnns: ColumnId[]; @@ -99,5 +102,6 @@ export interface UseDataGridReturnType { sortingColumns: EuiDataGridSorting['columns']; status: INDEX_STATUS; tableItems: DataGridItem[]; + toggleChartVisibility: () => void; visibleColumns: ColumnId[]; } diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx index 228028c004e00..802195f50a3cf 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useRef, useState } from 'react'; +import React, { useRef } from 'react'; import ReactDOM from 'react-dom'; import { EuiDataGridColumn, EuiDataGridOnColumnResizeHandler } from '@elastic/eui'; @@ -19,13 +19,11 @@ const mlDataGridChartRowClassName = `${mlDataGridChartClassName}Row`; type ColumnWidths = Record; type RefValue = HTMLElement | null; -export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: ChartData[]) { - const [histogramVisible, setHistogramVisible] = useState(false); - - const toggleChartVisibility = () => { - setHistogramVisible(!histogramVisible); - }; - +export function useColumnCharts( + columns: EuiDataGridColumn[], + chartsData: ChartData[], + chartsVisible: boolean +) { const ref = useRef(null); const refFn = (node: RefValue) => { renderCharts(node); @@ -41,7 +39,7 @@ export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: ChartD let chartRow; if (chartRows.length > 0) { chartRow = chartRows[0]; - if (!histogramVisible) { + if (!chartsVisible) { chartRow.remove(); const columnHeaders = tBody.getElementsByClassName('euiDataGridHeaderCell'); for (let i = 0; i < columnHeaders.length; i++) { @@ -49,7 +47,7 @@ export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: ChartD } return; } - } else if (histogramVisible) { + } else if (chartsVisible) { chartRow = document.createElement('div'); chartRow.classList.add(mlDataGridChartRowClassName); chartRow.classList.add('euiDataGridRow'); @@ -104,5 +102,5 @@ export function useColumnCharts(columns: EuiDataGridColumn[], chartsData: ChartD renderCharts(ref.current, columnWidths); }; - return { columnResizeHandler, histogramVisible, refFn, toggleChartVisibility }; + return { columnResizeHandler, refFn }; } diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts index 58ab4af51a2ad..9b0bacfb021b6 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts @@ -38,6 +38,11 @@ export const useDataGrid = ( const [tableItems, setTableItems] = useState([]); const [pagination, setPagination] = useState(defaultPagination); const [sortingColumns, setSortingColumns] = useState([]); + const [chartsVisible, setChartsVisible] = useState(false); + + const toggleChartVisibility = () => { + setChartsVisible(!chartsVisible); + }; const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback((pageSize) => { setPagination((p) => { @@ -89,6 +94,7 @@ export const useDataGrid = ( ); return { + chartsVisible, columnCharts, errorMessage, invalidSortingColumnns, @@ -111,6 +117,7 @@ export const useDataGrid = ( sortingColumns, status, tableItems, + toggleChartVisibility, visibleColumns, }; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index 4b6636939adf8..023ab66a093f3 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -49,6 +49,7 @@ export const useIndexData = ( const dataGrid = useDataGrid(columns); const { + chartsVisible, pagination, resetPagination, setColumnCharts, @@ -127,10 +128,12 @@ export const useIndexData = ( }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]); useEffect(() => { - fetchColumnChartsData(); + if (chartsVisible) { + fetchColumnChartsData(); + } // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps - }, [indexPattern.title, JSON.stringify([query, dataGrid.visibleColumns])]); + }, [chartsVisible, indexPattern.title, JSON.stringify([query, dataGrid.visibleColumns])]); const renderCellValue = useRenderCellValue(indexPattern, pagination, tableItems); From 6d3a878ea0b574c8df3df5e1da8d5f8fa470b2ae Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 10 Jun 2020 12:28:34 +0200 Subject: [PATCH 11/42] [ML] Return empty dataset for stats returning null. --- .../components/data_grid/use_column_chart.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts index c26b0491cb369..31f0168a948bc 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts @@ -107,6 +107,13 @@ export const fetchChartData = async ( }); const stats = [respStats.aggregations.min_max.min, respStats.aggregations.min_max.max]; + if (stats.includes(null)) { + return { + data: [], + interval: 0, + stats: [0, 0], + }; + } const delta = respStats.aggregations.min_max.max - respStats.aggregations.min_max.min; @@ -176,14 +183,19 @@ export const fetchChartData = async ( ...((await fetchChartHistogramData()) as NumericChartData), id: columnType.id, }; - } - if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) { + } else if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) { // TODO query in parallel return { cardinality: await fetchCardinality(), data: await fetchChartTermsData(), id: columnType.id, } as OrdinalChartData; + } else if (fieldType === KBN_FIELD_TYPES.OBJECT) { + return { + cardinality: 0, + data: [], + id: columnType.id, + }; } throw new Error('Invalid fieldType'); From 5f7df12c2f554759199ce2b9cb71c8d6d2ffc9df Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 10 Jun 2020 12:29:11 +0200 Subject: [PATCH 12/42] [ML] Tweak legend style. --- .../public/application/components/data_grid/column_chart.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss index e616369deb0aa..5499365058747 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss @@ -16,13 +16,13 @@ .mlDataGridChart__legend { color: $euiColorMediumShade; display: block; - overflow: hidden; + overflow-x: hidden; margin: $euiSizeXS $euiSizeXS 0px $euiSizeXS; text-overflow: ellipsis; white-space: nowrap; font-size: 0.75rem; font-style: italic; - line-height: 1; + line-height: 1.4; text-align: left; } From 50de1605acbe38b446964df4431b257ed1948390 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 10 Jun 2020 15:08:26 +0200 Subject: [PATCH 13/42] [ML] Add charts to more data grids. Fix non-aggregatable columns. --- .../components/data_grid/common.ts | 11 +++++- .../components/data_grid/use_column_chart.ts | 14 ++++++-- .../data_grid/use_column_charts.tsx | 2 +- .../hooks/use_index_data.ts | 34 +++++++++++++++++-- .../use_exploration_results.ts | 25 ++++++++++++++ .../outlier_exploration/use_outlier_data.ts | 25 ++++++++++++++ 6 files changed, 105 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index 7d0559c215114..33fc3516d5f3f 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -102,6 +102,8 @@ export const getDataGridSchemasFromFieldTypes = (fieldTypes: FieldTypes, results case 'boolean': schema = 'boolean'; break; + case 'text': + schema = NON_AGGREGATABLE; } if ( @@ -122,7 +124,10 @@ export const getDataGridSchemasFromFieldTypes = (fieldTypes: FieldTypes, results }); }; -export const getDataGridSchemaFromKibanaFieldType = (field: IFieldType | undefined) => { +export const NON_AGGREGATABLE = 'non-aggregatable'; +export const getDataGridSchemaFromKibanaFieldType = ( + field: IFieldType | undefined +): string | undefined => { // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] // To fall back to the default string schema it needs to be undefined. let schema; @@ -143,6 +148,10 @@ export const getDataGridSchemaFromKibanaFieldType = (field: IFieldType | undefin break; } + if (schema === undefined && field?.aggregatable === false) { + return NON_AGGREGATABLE; + } + return schema; }; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts index 31f0168a948bc..1a1a51edcc45e 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts @@ -13,6 +13,8 @@ import { euiPaletteColorBlind, EuiDataGridColumn } from '@elastic/eui'; import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; +import { NON_AGGREGATABLE } from './common'; + export const hoveredRow$ = new BehaviorSubject(null); const BAR_COLOR = euiPaletteColorBlind()[0]; @@ -34,7 +36,11 @@ const getXScaleType = ( } }; -const getFieldType = (schema: EuiDataGridColumn['schema']) => { +const getFieldType = (schema: EuiDataGridColumn['schema']): KBN_FIELD_TYPES | undefined => { + if (schema === NON_AGGREGATABLE) { + return undefined; + } + let fieldType: KBN_FIELD_TYPES; switch (schema) { @@ -190,7 +196,7 @@ export const fetchChartData = async ( data: await fetchChartTermsData(), id: columnType.id, } as OrdinalChartData; - } else if (fieldType === KBN_FIELD_TYPES.OBJECT) { + } else if (fieldType === KBN_FIELD_TYPES.OBJECT || fieldType === undefined) { return { cardinality: 0, data: [], @@ -248,6 +254,10 @@ export const useColumnChart = (chartData: ChartData, columnType: EuiDataGridColu const hoveredRow = useObservable(hoveredRow$); + if (fieldType === undefined) { + throw new Error('Invalid fieldType'); + } + const xScaleType = getXScaleType(fieldType); const getColor = (d: ChartDataItem) => { diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx index 802195f50a3cf..56df1a592f228 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx @@ -74,7 +74,7 @@ export function useColumnCharts( )}`} style={{ width: `${columnWidth}px` }} > - {!d.isExpandable && chartData !== undefined && ( + {chartData !== undefined && chartData.data.length > 0 && ( )}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts index e8f25584201e3..3bd48739e89e3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts @@ -6,12 +6,16 @@ import { useEffect } from 'react'; +import { EuiDataGridColumn } from '@elastic/eui'; + import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; import { + fetchChartData, getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, useDataGrid, useRenderCellValue, + ChartData, EsSorting, SearchResponse7, UseIndexDataReturnType, @@ -26,11 +30,11 @@ export const useIndexData = (indexPattern: IndexPattern, query: any): UseIndexDa const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern); // EuiDataGrid State - const columns = [ + const columns: EuiDataGridColumn[] = [ ...indexPatternFields.map((id) => { const field = indexPattern.fields.getByName(id); const schema = getDataGridSchemaFromKibanaFieldType(field); - return { id, schema }; + return { id, schema, isExpandable: schema !== 'boolean' }; }), ]; @@ -93,6 +97,32 @@ export const useIndexData = (indexPattern: IndexPattern, query: any): UseIndexDa // eslint-disable-next-line react-hooks/exhaustive-deps }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]); + const fetchColumnChartsData = async function () { + const fetchers = dataGrid.visibleColumns.map((vc) => { + const columnType = columns.find((c) => c.id === vc); + + if (columnType === undefined) { + return Promise.resolve(undefined); + } + return fetchChartData(indexPattern.title, ml, query, columnType); + }); + + const data = (await Promise.all(fetchers)) as ChartData[]; + dataGrid.setColumnCharts(data.filter((d) => d !== undefined)); + }; + + useEffect(() => { + if (dataGrid.chartsVisible) { + fetchColumnChartsData(); + } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + dataGrid.chartsVisible, + indexPattern.title, + JSON.stringify([query, dataGrid.visibleColumns]), + ]); + const renderCellValue = useRenderCellValue(indexPattern, pagination, tableItems); return { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts index b8b5a16c84e85..9b7027da60fe8 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts @@ -11,12 +11,15 @@ import { EuiDataGridColumn } from '@elastic/eui'; import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { + fetchChartData, getDataGridSchemasFromFieldTypes, useDataGrid, useRenderCellValue, + ChartData, UseIndexDataReturnType, } from '../../../../../components/data_grid'; import { SavedSearchQuery } from '../../../../../contexts/ml'; +import { ml } from '../../../../../services/ml_api_service'; import { getIndexData, getIndexFields, DataFrameAnalyticsConfig } from '../../../../common'; import { @@ -66,6 +69,28 @@ export const useExplorationResults = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]); + const fetchColumnChartsData = async function () { + const fetchers = dataGrid.visibleColumns.map((vc) => { + const columnType = columns.find((c) => c.id === vc); + + if (columnType === undefined || jobConfig === undefined) { + return Promise.resolve(undefined); + } + return fetchChartData(jobConfig.dest.index, ml, { match_all: {} }, columnType); + }); + + const data = (await Promise.all(fetchers)) as ChartData[]; + dataGrid.setColumnCharts(data.filter((d) => d !== undefined)); + }; + + useEffect(() => { + if (dataGrid.chartsVisible) { + fetchColumnChartsData(); + } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dataGrid.chartsVisible, jobConfig?.dest.index, JSON.stringify([dataGrid.visibleColumns])]); + const renderCellValue = useRenderCellValue( indexPattern, dataGrid.pagination, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts index ebac6ccb2298e..2fa74aa7bf22f 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts @@ -16,12 +16,15 @@ import { COLOR_RANGE_SCALE, } from '../../../../../components/color_range_legend'; import { + fetchChartData, getDataGridSchemasFromFieldTypes, useDataGrid, useRenderCellValue, + ChartData, UseIndexDataReturnType, } from '../../../../../components/data_grid'; import { SavedSearchQuery } from '../../../../../contexts/ml'; +import { ml } from '../../../../../services/ml_api_service'; import { getIndexData, getIndexFields, DataFrameAnalyticsConfig } from '../../../../common'; import { DEFAULT_RESULTS_FIELD, FEATURE_INFLUENCE } from '../../../../common/constants'; @@ -75,6 +78,28 @@ export const useOutlierData = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]); + const fetchColumnChartsData = async function () { + const fetchers = dataGrid.visibleColumns.map((vc) => { + const columnType = columns.find((c) => c.id === vc); + + if (columnType === undefined || jobConfig === undefined) { + return Promise.resolve(undefined); + } + return fetchChartData(jobConfig.dest.index, ml, { match_all: {} }, columnType); + }); + + const data = (await Promise.all(fetchers)) as ChartData[]; + dataGrid.setColumnCharts(data.filter((d) => d !== undefined)); + }; + + useEffect(() => { + if (dataGrid.chartsVisible) { + fetchColumnChartsData(); + } + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dataGrid.chartsVisible, jobConfig?.dest.index, JSON.stringify([dataGrid.visibleColumns])]); + const colorRange = useColorRange( COLOR_RANGE.BLUE, COLOR_RANGE_SCALE.INFLUENCER, From 6151122f4d3e0a5534e244cb758864774cfd3c9d Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 10 Jun 2020 15:50:25 +0200 Subject: [PATCH 14/42] [ML] Types cleanup. --- .../components/step_define/step_define_form.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 3c90a251587ea..e0b350542a8f8 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -43,7 +43,6 @@ import { useIndexData } from '../../../../hooks/use_index_data'; import { usePivotData } from '../../../../hooks/use_pivot_data'; import { useToastNotifications } from '../../../../app_dependencies'; import { SearchItems } from '../../../../hooks/use_search_items'; -import { useApi } from '../../../../hooks/use_api'; import { AdvancedPivotEditor } from '../advanced_pivot_editor'; import { AdvancedPivotEditorSwitch } from '../advanced_pivot_editor_switch'; @@ -66,8 +65,6 @@ export const StepDefineForm: FC = React.memo((props) => { const { searchItems } = props; const { indexPattern } = searchItems; - const api = useApi(); - const toastNotifications = useToastNotifications(); const stepDefineForm = useStepDefineForm(props); @@ -296,9 +293,7 @@ export const StepDefineForm: FC = React.memo((props) => { - {/* TODO move mini charts data fetching to outer hook - // @ts-ignore */} - + From 53b34be1a9122d2adf97a73191d02287c09bca19 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 10 Jun 2020 16:18:18 +0200 Subject: [PATCH 15/42] [ML] Custom legend for boolean histograms. --- .../components/data_grid/column_chart.scss | 5 +++++ .../components/data_grid/column_chart.tsx | 15 ++++++++++++++- .../components/data_grid/use_column_chart.ts | 16 +++++++++++++--- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss index 5499365058747..58388e796a61e 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss @@ -29,3 +29,8 @@ .mlDataGridChart__legend--numeric { text-align: right; } + +.mlDataGridChart__legendBoolean { + width: 100%; + td { text-align: center } +} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index 3674c6e0b08df..7e35671393aba 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -22,7 +22,20 @@ import { export const mlDataGridChartClassName = 'mlDataGridChart'; -const getLegendText = (chartData: ChartData, MAX_CHART_COLUMNS: number): string => { +const getLegendText = (chartData: ChartData, MAX_CHART_COLUMNS: number): string | JSX.Element => { + if (chartData.type === 'boolean') { + return ( + + + + + + + +
{chartData.data[0].key_as_string}{chartData.data[1].key_as_string}
+ ); + } + if (isOrdinalChartData(chartData) && chartData.cardinality === 1) { return i18n.translate('xpack.ml.dataGridChart.singleCategoryLegend', { defaultMessage: `{cardinality} category`, diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts index 1a1a51edcc45e..7222280b09741 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts @@ -187,17 +187,20 @@ export const fetchChartData = async ( if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { return { ...((await fetchChartHistogramData()) as NumericChartData), + type: 'numeric', id: columnType.id, }; } else if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) { // TODO query in parallel return { + type: fieldType === KBN_FIELD_TYPES.STRING ? 'ordinal' : 'boolean', cardinality: await fetchCardinality(), data: await fetchChartTermsData(), id: columnType.id, - } as OrdinalChartData; + }; } else if (fieldType === KBN_FIELD_TYPES.OBJECT || fieldType === undefined) { return { + type: 'ordinal', cardinality: 0, data: [], id: columnType.id, @@ -218,6 +221,7 @@ interface NumericChartData { id: string; interval: number; stats: [number, number]; + type: 'numeric'; } export const isNumericChartData = (arg: any): arg is NumericChartData => { @@ -225,16 +229,19 @@ export const isNumericChartData = (arg: any): arg is NumericChartData => { arg.hasOwnProperty('data') && arg.hasOwnProperty('id') && arg.hasOwnProperty('interval') && - arg.hasOwnProperty('stats') + arg.hasOwnProperty('stats') && + arg.hasOwnProperty('type') ); }; interface OrdinalDataItem { key: string; + key_as_string?: string; doc_count: number; } interface OrdinalChartData { + type: 'ordinal' | 'boolean'; cardinality: number; data: OrdinalDataItem[]; id: string; @@ -242,7 +249,10 @@ interface OrdinalChartData { export const isOrdinalChartData = (arg: any): arg is OrdinalChartData => { return ( - arg.hasOwnProperty('data') && arg.hasOwnProperty('cardinality') && arg.hasOwnProperty('id') + arg.hasOwnProperty('data') && + arg.hasOwnProperty('cardinality') && + arg.hasOwnProperty('id') && + arg.hasOwnProperty('type') ); }; From dfb6d11312b276c297cac656a2b4fb6f973c795c Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 10 Jun 2020 18:26:20 +0200 Subject: [PATCH 16/42] [ML] Optimized data fetching for charts. --- .../application/components/data_grid/index.ts | 2 +- .../components/data_grid/use_column_chart.ts | 250 +++++++++--------- .../hooks/use_index_data.ts | 21 +- .../use_exploration_results.ts | 23 +- .../outlier_exploration/use_outlier_data.ts | 23 +- .../public/app/hooks/use_index_data.ts | 26 +- .../transform/public/shared_imports.ts | 2 +- 7 files changed, 167 insertions(+), 180 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/index.ts b/x-pack/plugins/ml/public/application/components/data_grid/index.ts index c1f441b61f956..d8ad9238c3348 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/index.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/index.ts @@ -11,7 +11,7 @@ export { multiColumnSortFactory, useRenderCellValue, } from './common'; -export { fetchChartData, ChartData } from './use_column_chart'; +export { fetchChartsData, ChartData } from './use_column_chart'; export { useDataGrid } from './use_data_grid'; export { DataGrid } from './data_grid'; export { diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts index 7222280b09741..0dfee0e4a1a7f 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts @@ -13,6 +13,8 @@ import { euiPaletteColorBlind, EuiDataGridColumn } from '@elastic/eui'; import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; +import { stringHash } from '../../../../common/util/string_utils'; + import { NON_AGGREGATABLE } from './common'; export const hoveredRow$ = new BehaviorSubject(null); @@ -63,65 +65,46 @@ const getFieldType = (schema: EuiDataGridColumn['schema']): KBN_FIELD_TYPES | un return fieldType; }; -export const fetchChartData = async ( +export const fetchChartsData = async ( indexPatternTitle: string, api: any, query: any, - columnType: EuiDataGridColumn -): Promise => { - const fieldType = getFieldType(columnType.schema); - - const fetchCardinality = async () => { - try { - const resp: any = await api.esSearch({ - index: indexPatternTitle, - size: 0, - body: { - query, - aggs: { - categories: { - cardinality: { - field: columnType.id, - }, - }, - }, - size: 0, - }, - }); - return resp.aggregations.categories.value; - } catch (e) { - throw new Error(e); - } - }; - - const fetchChartHistogramData = async () => { - try { - const respStats: any = await api.esSearch({ - index: indexPatternTitle, + columnTypes: EuiDataGridColumn[] +): Promise => { + const numericColumns = columnTypes.filter((cT) => { + const fieldType = getFieldType(cT.schema); + return fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE; + }); + + const minMaxAggs = numericColumns.reduce((aggs, c) => { + const id = stringHash(c.id); + aggs[id] = { + stats: { + field: c.id, + }, + }; + return aggs; + }, {} as Record); + + let respStats: any; + try { + respStats = await api.esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: minMaxAggs, size: 0, - body: { - query, - aggs: { - min_max: { - stats: { - field: columnType.id, - }, - }, - }, - size: 0, - }, - }); - - const stats = [respStats.aggregations.min_max.min, respStats.aggregations.min_max.max]; - if (stats.includes(null)) { - return { - data: [], - interval: 0, - stats: [0, 0], - }; - } + }, + }); + } catch (e) { + throw new Error(e); + } - const delta = respStats.aggregations.min_max.max - respStats.aggregations.min_max.min; + const aggIntervals = Object.keys(respStats.aggregations).reduce((p, aggName) => { + const stats = [respStats.aggregations[aggName].min, respStats.aggregations[aggName].max]; + if (!stats.includes(null)) { + const delta = respStats.aggregations[aggName].max - respStats.aggregations[aggName].min; let aggInterval = 1; @@ -133,81 +116,104 @@ export const fetchChartData = async ( aggInterval = delta / MAX_CHART_COLUMNS; } - const resp: any = await api.esSearch({ - index: indexPatternTitle, - size: 0, - body: { - query, - aggs: { - chart: { - histogram: { - field: columnType.id, - interval: aggInterval, - }, - }, - }, - size: 0, - }, - }); - - return { - data: resp.aggregations.chart.buckets, - interval: aggInterval, - stats, - }; - } catch (e) { - throw new Error(e); + p[aggName] = aggInterval; } - }; - const fetchChartTermsData = async () => { - try { - const resp: any = await api.esSearch({ - index: indexPatternTitle, - size: 0, - body: { - query, - aggs: { - chart: { - terms: { - field: columnType.id, - size: MAX_CHART_COLUMNS, - }, - }, + return p; + }, {} as Record); + + const chartDataAggs = columnTypes.reduce((aggs, c) => { + const fieldType = getFieldType(c.schema); + const id = stringHash(c.id); + if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { + if (aggIntervals[id] !== undefined) { + aggs[`${id}_histogram`] = { + histogram: { + field: c.id, + interval: aggIntervals[id], }, - size: 0, + }; + } + } else if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) { + if (fieldType === KBN_FIELD_TYPES.STRING) { + aggs[`${id}_cardinality`] = { + cardinality: { + field: c.id, + }, + }; + } + aggs[`${id}_terms`] = { + terms: { + field: c.id, + size: MAX_CHART_COLUMNS, }, - }); - return resp.aggregations.chart.buckets; - } catch (e) { - throw new Error(e); + }; } - }; - - if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { - return { - ...((await fetchChartHistogramData()) as NumericChartData), - type: 'numeric', - id: columnType.id, - }; - } else if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) { - // TODO query in parallel - return { - type: fieldType === KBN_FIELD_TYPES.STRING ? 'ordinal' : 'boolean', - cardinality: await fetchCardinality(), - data: await fetchChartTermsData(), - id: columnType.id, - }; - } else if (fieldType === KBN_FIELD_TYPES.OBJECT || fieldType === undefined) { - return { - type: 'ordinal', - cardinality: 0, - data: [], - id: columnType.id, - }; + return aggs; + }, {} as Record); + + let respChartsData: any; + try { + respChartsData = await api.esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: chartDataAggs, + size: 0, + }, + }); + } catch (e) { + throw new Error(e); } - throw new Error('Invalid fieldType'); + const chartsData: ChartData[] = columnTypes.map( + (c): ChartData => { + const fieldType = getFieldType(c.schema); + const id = stringHash(c.id); + + if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { + if (aggIntervals[id] === undefined) { + return { + type: 'numeric', + data: [], + interval: 0, + stats: [0, 0], + id: c.id, + }; + } + + return { + data: respChartsData.aggregations[`${id}_histogram`].buckets, + interval: aggIntervals[id], + stats: [respStats.aggregations[id].min, respStats.aggregations[id].max], + type: 'numeric', + id: c.id, + }; + } else if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) { + return { + type: fieldType === KBN_FIELD_TYPES.STRING ? 'ordinal' : 'boolean', + cardinality: + fieldType === KBN_FIELD_TYPES.STRING + ? respChartsData.aggregations[`${id}_cardinality`].value + : 2, + data: respChartsData.aggregations[`${id}_terms`].buckets, + id: c.id, + }; + } else if (fieldType === KBN_FIELD_TYPES.OBJECT || fieldType === undefined) { + return { + type: 'ordinal', + cardinality: 0, + data: [], + id: c.id, + }; + } + + throw new Error('Invalid fieldType'); + } + ); + + return chartsData; }; interface NumericDataItem { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts index 3bd48739e89e3..68eda86d10883 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts @@ -10,12 +10,11 @@ import { EuiDataGridColumn } from '@elastic/eui'; import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; import { - fetchChartData, + fetchChartsData, getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, useDataGrid, useRenderCellValue, - ChartData, EsSorting, SearchResponse7, UseIndexDataReturnType, @@ -98,17 +97,13 @@ export const useIndexData = (indexPattern: IndexPattern, query: any): UseIndexDa }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]); const fetchColumnChartsData = async function () { - const fetchers = dataGrid.visibleColumns.map((vc) => { - const columnType = columns.find((c) => c.id === vc); - - if (columnType === undefined) { - return Promise.resolve(undefined); - } - return fetchChartData(indexPattern.title, ml, query, columnType); - }); - - const data = (await Promise.all(fetchers)) as ChartData[]; - dataGrid.setColumnCharts(data.filter((d) => d !== undefined)); + const columnChartsData = await fetchChartsData( + indexPattern.title, + ml, + query, + columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) + ); + dataGrid.setColumnCharts(columnChartsData); }; useEffect(() => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts index 9b7027da60fe8..a23466fc952b3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts @@ -11,11 +11,10 @@ import { EuiDataGridColumn } from '@elastic/eui'; import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { - fetchChartData, + fetchChartsData, getDataGridSchemasFromFieldTypes, useDataGrid, useRenderCellValue, - ChartData, UseIndexDataReturnType, } from '../../../../../components/data_grid'; import { SavedSearchQuery } from '../../../../../contexts/ml'; @@ -70,17 +69,15 @@ export const useExplorationResults = ( }, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]); const fetchColumnChartsData = async function () { - const fetchers = dataGrid.visibleColumns.map((vc) => { - const columnType = columns.find((c) => c.id === vc); - - if (columnType === undefined || jobConfig === undefined) { - return Promise.resolve(undefined); - } - return fetchChartData(jobConfig.dest.index, ml, { match_all: {} }, columnType); - }); - - const data = (await Promise.all(fetchers)) as ChartData[]; - dataGrid.setColumnCharts(data.filter((d) => d !== undefined)); + if (jobConfig !== undefined) { + const columnChartsData = await fetchChartsData( + jobConfig.dest.index, + ml, + { match_all: {} }, + columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) + ); + dataGrid.setColumnCharts(columnChartsData); + } }; useEffect(() => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts index 2fa74aa7bf22f..26e648178faac 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts @@ -16,11 +16,10 @@ import { COLOR_RANGE_SCALE, } from '../../../../../components/color_range_legend'; import { - fetchChartData, + fetchChartsData, getDataGridSchemasFromFieldTypes, useDataGrid, useRenderCellValue, - ChartData, UseIndexDataReturnType, } from '../../../../../components/data_grid'; import { SavedSearchQuery } from '../../../../../contexts/ml'; @@ -79,17 +78,15 @@ export const useOutlierData = ( }, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]); const fetchColumnChartsData = async function () { - const fetchers = dataGrid.visibleColumns.map((vc) => { - const columnType = columns.find((c) => c.id === vc); - - if (columnType === undefined || jobConfig === undefined) { - return Promise.resolve(undefined); - } - return fetchChartData(jobConfig.dest.index, ml, { match_all: {} }, columnType); - }); - - const data = (await Promise.all(fetchers)) as ChartData[]; - dataGrid.setColumnCharts(data.filter((d) => d !== undefined)); + if (jobConfig !== undefined) { + const columnChartsData = await fetchChartsData( + jobConfig.dest.index, + ml, + searchQuery, + columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) + ); + dataGrid.setColumnCharts(columnChartsData); + } }; useEffect(() => { diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index 023ab66a093f3..71910c838946e 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -9,7 +9,7 @@ import { useEffect } from 'react'; import { EuiDataGridColumn } from '@elastic/eui'; import { - fetchChartData, + fetchChartsData, getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, getErrorMessage, @@ -103,22 +103,14 @@ export const useIndexData = ( }; const fetchColumnChartsData = async function () { - const fetchers = dataGrid.visibleColumns.map((vc) => { - const columnType = columns.find((c) => c.id === vc); - - if (columnType === undefined) { - return Promise.resolve(undefined); - } - return fetchChartData( - indexPattern.title, - api, - isDefaultQuery(query) ? matchAllQuery : query, - columnType - ); - }); - - const data = (await Promise.all(fetchers)) as ChartData[]; - setColumnCharts(data.filter((d) => d !== undefined)); + const columnChartsData = await fetchChartsData( + indexPattern.title, + api, + isDefaultQuery(query) ? matchAllQuery : query, + columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) + ); + + setColumnCharts(columnChartsData); }; useEffect(() => { diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index cbc0a401bd3d9..6103f694055f3 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -14,7 +14,7 @@ export { } from '../../../../src/plugins/es_ui_shared/public'; export { - fetchChartData, + fetchChartsData, getErrorMessage, getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, From c4acf111be69f7710868c53b60d061e2e40c9da9 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 15 Jun 2020 08:15:38 +0200 Subject: [PATCH 17/42] [ML] Use EuiDataGrid's own display prop to render chart instead of ref. --- .../components/data_grid/column_chart.scss | 5 +- .../components/data_grid/data_grid.tsx | 17 +-- .../application/components/data_grid/types.ts | 5 +- .../data_grid/use_column_charts.tsx | 106 ------------------ .../{use_data_grid.ts => use_data_grid.tsx} | 18 ++- .../hooks/use_index_data.ts | 1 - .../public/app/hooks/use_index_data.ts | 2 - 7 files changed, 25 insertions(+), 129 deletions(-) delete mode 100644 x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx rename x-pack/plugins/ml/public/application/components/data_grid/{use_data_grid.ts => use_data_grid.tsx} (88%) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss index 58388e796a61e..7e89dab60dcc5 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss @@ -5,7 +5,7 @@ } .mlDataGridChart + .mlDataGridChart { - border-left: solid 0px $euiColorLightShade; + border-left: none; } .mlDataGridChart__histogram { @@ -17,11 +17,12 @@ color: $euiColorMediumShade; display: block; overflow-x: hidden; - margin: $euiSizeXS $euiSizeXS 0px $euiSizeXS; + margin: $euiSizeXS 0px 0px 0px; text-overflow: ellipsis; white-space: nowrap; font-size: 0.75rem; font-style: italic; + font-weight: normal; line-height: 1.4; text-align: left; } diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index cc742ee475492..5201c396a5299 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -30,7 +30,6 @@ import { euiDataGridStyle, euiDataGridToolbarSettings } from './common'; import { UseIndexDataReturnType } from './types'; // TODO Fix row hovering + bar highlighting // import { hoveredRow$ } from './column_chart'; -import { useColumnCharts } from './use_column_charts'; import './data_grid.scss'; @@ -61,8 +60,7 @@ export const DataGrid: FC = memo( (props) => { const { chartsVisible, - columnCharts, - columns, + columnsWithCharts, dataTestSubj, errorMessage, invalidSortingColumnns, @@ -82,12 +80,6 @@ export const DataGrid: FC = memo( visibleColumns, } = props; - const { columnResizeHandler, refFn } = useColumnCharts( - columns.filter((c) => visibleColumns.includes(c.id)), - columnCharts, - chartsVisible - ); - // TODO Fix row hovering + bar highlighting // const getRowProps = (item: any) => { // return { @@ -185,10 +177,10 @@ export const DataGrid: FC = memo(
)} -
+
{ + columns={columnsWithCharts.map((c) => { c.initialWidth = 150; return c; })} @@ -221,7 +213,6 @@ export const DataGrid: FC = memo( onChangeItemsPerPage, onChangePage, }} - onColumnResize={columnResizeHandler} />
@@ -232,7 +223,7 @@ export const DataGrid: FC = memo( function pickProps(props: Props) { return [ - props.columns, + props.columnsWithCharts, props.dataTestSubj, props.errorMessage, props.invalidSortingColumnns, diff --git a/x-pack/plugins/ml/public/application/components/data_grid/types.ts b/x-pack/plugins/ml/public/application/components/data_grid/types.ts index a485121f38704..aa9c722cb85ff 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/types.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/types.ts @@ -57,7 +57,7 @@ export interface UseIndexDataReturnType extends Pick< UseDataGridReturnType, | 'chartsVisible' - | 'columnCharts' + | 'columnsWithCharts' | 'errorMessage' | 'invalidSortingColumnns' | 'noDataMessage' @@ -74,13 +74,12 @@ export interface UseIndexDataReturnType | 'toggleChartVisibility' | 'visibleColumns' > { - columns: EuiDataGridColumn[]; renderCellValue: RenderCellValue; } export interface UseDataGridReturnType { chartsVisible: boolean; - columnCharts: ChartData[]; + columnsWithCharts: EuiDataGridColumn[]; errorMessage: string; invalidSortingColumnns: ColumnId[]; noDataMessage: string; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx deleted file mode 100644 index 56df1a592f228..0000000000000 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_charts.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 React, { useRef } from 'react'; -import ReactDOM from 'react-dom'; - -import { EuiDataGridColumn, EuiDataGridOnColumnResizeHandler } from '@elastic/eui'; - -import { stringHash } from '../../../../common/util/string_utils'; - -import { mlDataGridChartClassName, ColumnChart } from './column_chart'; -import { ChartData } from './use_column_chart'; - -const mlDataGridChartRowClassName = `${mlDataGridChartClassName}Row`; - -type ColumnWidths = Record; -type RefValue = HTMLElement | null; - -export function useColumnCharts( - columns: EuiDataGridColumn[], - chartsData: ChartData[], - chartsVisible: boolean -) { - const ref = useRef(null); - const refFn = (node: RefValue) => { - renderCharts(node); - }; - - const renderCharts = (node: RefValue, columnsWidths?: ColumnWidths) => { - ref.current = node; - - if (node !== null) { - const tBody = node.getElementsByClassName('euiDataGrid__content')[0]; - const chartRows = tBody.getElementsByClassName(mlDataGridChartRowClassName); - - let chartRow; - if (chartRows.length > 0) { - chartRow = chartRows[0]; - if (!chartsVisible) { - chartRow.remove(); - const columnHeaders = tBody.getElementsByClassName('euiDataGridHeaderCell'); - for (let i = 0; i < columnHeaders.length; i++) { - columnHeaders[i].classList.remove('mlDataGridHeaderCell--paddingChart'); - } - return; - } - } else if (chartsVisible) { - chartRow = document.createElement('div'); - chartRow.classList.add(mlDataGridChartRowClassName); - chartRow.classList.add('euiDataGridRow'); - tBody.insertBefore(chartRow, tBody.childNodes[0]); - - const columnHeaders = tBody.getElementsByClassName('euiDataGridHeaderCell'); - for (let i = 0; i < columnHeaders.length; i++) { - columnHeaders[i].classList.add('mlDataGridHeaderCell--paddingChart'); - } - } else { - return; - } - - ReactDOM.render( - <> - {columns.map((d) => { - const chartData = chartsData.find((cd) => cd.id === d.id); - const columnWidth = (columnsWidths && columnsWidths[d.id]) || d.initialWidth; - return ( -
- {chartData !== undefined && chartData.data.length > 0 && ( - - )} -
- ); - })} - , - chartRow - ); - } - }; - - const columnResizeHandler: EuiDataGridOnColumnResizeHandler = ({ columnId, width }) => { - const columnWidths = columns.reduce((p, c) => { - if (columnId === c.id) { - p[c.id] = width; - } else if (ref !== null && ref.current !== null) { - const chartElement = ref.current.querySelector( - `.${mlDataGridChartClassName}-${stringHash(c.id)}` - ); - p[c.id] = - chartElement !== null ? (chartElement as HTMLElement).offsetWidth : c?.initialWidth ?? 0; - } - return p; - }, {} as ColumnWidths); - renderCharts(ref.current, columnWidths); - }; - - return { columnResizeHandler, refFn }; -} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx similarity index 88% rename from x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts rename to x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx index 9b0bacfb021b6..83c1e0045a7a2 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { EuiDataGridSorting, EuiDataGridColumn } from '@elastic/eui'; import { INDEX_STATUS } from '../../data_frame_analytics/common'; +import { ColumnChart } from './column_chart'; import { INIT_MAX_COLUMNS } from './common'; import { ColumnId, @@ -95,7 +96,20 @@ export const useDataGrid = ( return { chartsVisible, - columnCharts, + columnsWithCharts: columns.map((c) => { + const chartData = columnCharts.find((cd) => cd.id === c.id); + + return { + ...c, + display: + chartData !== undefined && chartsVisible === true ? ( + <> + + {c.id} + + ) : undefined, + }; + }), errorMessage, invalidSortingColumnns, noDataMessage, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts index 68eda86d10883..99e0963771ac9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts @@ -122,7 +122,6 @@ export const useIndexData = (indexPattern: IndexPattern, query: any): UseIndexDa return { ...dataGrid, - columns, renderCellValue, }; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index 71910c838946e..cfab75f0217b8 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -15,7 +15,6 @@ import { getErrorMessage, useDataGrid, useRenderCellValue, - ChartData, EsSorting, SearchResponse7, UseIndexDataReturnType, @@ -131,7 +130,6 @@ export const useIndexData = ( return { ...dataGrid, - columns, renderCellValue, }; }; From 936473390244c6fa73e3dfce07d2bf047d0b86c7 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 15 Jun 2020 08:21:29 +0200 Subject: [PATCH 18/42] [ML] SCSS cleanup. --- .../application/components/data_grid/column_chart.scss | 10 ---------- .../application/components/data_grid/data_grid.scss | 3 --- .../application/components/data_grid/data_grid.tsx | 2 -- 3 files changed, 15 deletions(-) delete mode 100644 x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss index 7e89dab60dcc5..e5011fe3efdcb 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss @@ -1,13 +1,3 @@ -.mlDataGridChart { - border-left: solid 1px $euiColorLightShade; - border-right: solid 1px $euiColorLightShade; - background-color: $euiColorLightestShade; -} - -.mlDataGridChart + .mlDataGridChart { - border-left: none; -} - .mlDataGridChart__histogram { width: 100%; height: $euiSizeXL + $euiSizeXXL; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss deleted file mode 100644 index bb46708dd5399..0000000000000 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss +++ /dev/null @@ -1,3 +0,0 @@ -.mlDataGridHeaderCell--paddingChart { - padding: 0 $euiSizeXS 0px $euiSizeXS !important; -} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index 5201c396a5299..3cd6ad3bdc905 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -31,8 +31,6 @@ import { UseIndexDataReturnType } from './types'; // TODO Fix row hovering + bar highlighting // import { hoveredRow$ } from './column_chart'; -import './data_grid.scss'; - export const DataGridTitle: FC<{ title: string }> = ({ title }) => ( {title} From ebcef6129bfdc1c1808824be5fd752dc59fbc429 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 15 Jun 2020 08:36:15 +0200 Subject: [PATCH 19/42] [ML] Fix header alignment. --- .../application/components/data_grid/column_chart.scss | 5 +++++ .../public/application/components/data_grid/column_chart.tsx | 5 ++--- .../application/components/data_grid/use_data_grid.tsx | 5 +---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss index e5011fe3efdcb..698448712b44c 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss @@ -25,3 +25,8 @@ width: 100%; td { text-align: center } } + +/* Override to align column header to bottom of cell when no chart is available */ +.mlDataGrid .euiDataGridHeaderCell__content { + margin-top: auto; +} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index 7e35671393aba..ed13bbe225e65 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -20,8 +20,6 @@ import { ChartData, } from './use_column_chart'; -export const mlDataGridChartClassName = 'mlDataGridChart'; - const getLegendText = (chartData: ChartData, MAX_CHART_COLUMNS: number): string | JSX.Element => { if (chartData.type === 'boolean') { return ( @@ -79,7 +77,7 @@ export const ColumnChart: FC = ({ chartData, columnType }) => { const { coloredData, xScaleType, MAX_CHART_COLUMNS } = useColumnChart(chartData, columnType); if (coloredData.length === 0) { - return null; + return columnType.id; } return ( @@ -122,6 +120,7 @@ export const ColumnChart: FC = ({ chartData, columnType }) => { > {getLegendText(chartData, MAX_CHART_COLUMNS)} + {columnType.id} ); }; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx index 83c1e0045a7a2..f6644c65524ee 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx @@ -103,10 +103,7 @@ export const useDataGrid = ( ...c, display: chartData !== undefined && chartsVisible === true ? ( - <> - - {c.id} - + ) : undefined, }; }), From 1b37d3fe4e3bd29fdfc2ef653ff92886c4bf96e1 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 15 Jun 2020 08:43:35 +0200 Subject: [PATCH 20/42] [ML] Tweak legend SCSS. --- .../application/components/data_grid/column_chart.scss | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss index 698448712b44c..37d8871ab3562 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss @@ -4,16 +4,15 @@ } .mlDataGridChart__legend { + @include euiTextTruncate; + @include euiFontSizeXS; + color: $euiColorMediumShade; display: block; overflow-x: hidden; margin: $euiSizeXS 0px 0px 0px; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 0.75rem; font-style: italic; font-weight: normal; - line-height: 1.4; text-align: left; } From 3393f59279ae94da5e2ba62446e5e7dde7b411e1 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 16 Jun 2020 07:41:57 +0200 Subject: [PATCH 21/42] [ML] Fix fieldType check. --- .../components/data_grid/use_column_chart.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts index 0dfee0e4a1a7f..368cfe7e51ee9 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts @@ -200,16 +200,14 @@ export const fetchChartsData = async ( data: respChartsData.aggregations[`${id}_terms`].buckets, id: c.id, }; - } else if (fieldType === KBN_FIELD_TYPES.OBJECT || fieldType === undefined) { - return { - type: 'ordinal', - cardinality: 0, - data: [], - id: c.id, - }; } - throw new Error('Invalid fieldType'); + return { + type: 'ordinal', + cardinality: 0, + data: [], + id: c.id, + }; } ); @@ -270,10 +268,6 @@ export const useColumnChart = (chartData: ChartData, columnType: EuiDataGridColu const hoveredRow = useObservable(hoveredRow$); - if (fieldType === undefined) { - throw new Error('Invalid fieldType'); - } - const xScaleType = getXScaleType(fieldType); const getColor = (d: ChartDataItem) => { From e79ca57e251f667476c206949870a36344e856e2 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 16 Jun 2020 07:42:47 +0200 Subject: [PATCH 22/42] [ML] Reduce INIT_MAX_COLUMNS from 20 to 10. --- .../ml/public/application/components/data_grid/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index 33fc3516d5f3f..5f0d53a4c082c 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -37,7 +37,7 @@ import { mlFieldFormatService } from '../../services/field_format_service'; import { DataGridItem, IndexPagination, RenderCellValue } from './types'; -export const INIT_MAX_COLUMNS = 20; +export const INIT_MAX_COLUMNS = 10; export const euiDataGridStyle: EuiDataGridStyle = { border: 'all', From 17b8752a546a97d159f14b2159ddbc9c1dda0654 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 16 Jun 2020 07:54:52 +0200 Subject: [PATCH 23/42] [ML] Use classNames instead of custom string. --- .../application/components/data_grid/column_chart.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index ed13bbe225e65..bc71bbcf27300 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -5,6 +5,7 @@ */ import React, { FC } from 'react'; +import classNames from 'classnames'; import { BarSeries, Chart, Settings } from '@elastic/charts'; import { EuiDataGridColumn } from '@elastic/eui'; @@ -114,9 +115,9 @@ export const ColumnChart: FC = ({ chartData, columnType }) => {
{getLegendText(chartData, MAX_CHART_COLUMNS)}
From 4a66f7d0d3d5b965d45df8b31b701a2dfdacd5a9 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 16 Jun 2020 09:20:26 +0200 Subject: [PATCH 24/42] [ML] i18n cleanup. --- .../components/data_grid/column_chart.tsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index bc71bbcf27300..55eb808fa503d 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -35,9 +35,9 @@ const getLegendText = (chartData: ChartData, MAX_CHART_COLUMNS: number): string ); } - if (isOrdinalChartData(chartData) && chartData.cardinality === 1) { + if (isOrdinalChartData(chartData) && chartData.cardinality <= MAX_CHART_COLUMNS) { return i18n.translate('xpack.ml.dataGridChart.singleCategoryLegend', { - defaultMessage: `{cardinality} category`, + defaultMessage: `{cardinality, plural, one {# category} other {# categories}}`, values: { cardinality: chartData.cardinality }, }); } @@ -49,17 +49,6 @@ const getLegendText = (chartData: ChartData, MAX_CHART_COLUMNS: number): string }); } - if ( - isOrdinalChartData(chartData) && - chartData.cardinality <= MAX_CHART_COLUMNS && - chartData.cardinality > 1 - ) { - return i18n.translate('xpack.ml.dataGridChart.categoriesLegend', { - defaultMessage: `{cardinality} categories`, - values: { cardinality: chartData.cardinality }, - }); - } - if (isNumericChartData(chartData)) { return `${Math.round(chartData.stats[0] * 100) / 100} - ${ Math.round(chartData.stats[1] * 100) / 100 From c8a69804f26b4fdb094a1e86112af917ecc27f42 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 16 Jun 2020 09:21:53 +0200 Subject: [PATCH 25/42] [ML] Fix types. Set MAX_COLUMNS to 10. Fix to consider query update. --- .../components/data_grid/column_chart.tsx | 2 +- .../components/data_grid/use_column_chart.ts | 4 +- .../data_frame_analytics/common/fields.ts | 2 +- .../exploration_results_table.tsx | 12 +++- .../use_exploration_results.ts | 9 ++- .../outlier_exploration.tsx | 59 ++++++++++--------- .../outlier_exploration/use_outlier_data.ts | 1 - .../public/app/hooks/use_pivot_data.ts | 5 +- 8 files changed, 52 insertions(+), 42 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index 55eb808fa503d..1cafa3a463c4e 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -67,7 +67,7 @@ export const ColumnChart: FC = ({ chartData, columnType }) => { const { coloredData, xScaleType, MAX_CHART_COLUMNS } = useColumnChart(chartData, columnType); if (coloredData.length === 0) { - return columnType.id; + return <>{columnType.id}; } return ( diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts index 368cfe7e51ee9..d282cb7880803 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts @@ -20,11 +20,11 @@ import { NON_AGGREGATABLE } from './common'; export const hoveredRow$ = new BehaviorSubject(null); const BAR_COLOR = euiPaletteColorBlind()[0]; -const BAR_COLOR_BLUR = euiPaletteColorBlind(2)[10]; +const BAR_COLOR_BLUR = euiPaletteColorBlind({ rotations: 2 })[10]; const MAX_CHART_COLUMNS = 20; const getXScaleType = ( - kbnFieldType: KBN_FIELD_TYPES + kbnFieldType: KBN_FIELD_TYPES | undefined ): 'ordinal' | 'time' | 'linear' | undefined => { switch (kbnFieldType) { case KBN_FIELD_TYPES.BOOLEAN: diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts index 0a64886c80a63..1b28875a624f8 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts @@ -30,7 +30,7 @@ export interface EsDoc extends Record { _source: EsDocSource; } -export const MAX_COLUMNS = 20; +export const MAX_COLUMNS = 10; export const DEFAULT_REGRESSION_COLUMNS = 8; export const BASIC_NUMERICAL_TYPES = new Set([ diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx index 24e5785c6e808..ce01fec02f80d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx @@ -68,8 +68,14 @@ export const ExplorationResultsTable: FC = React.memo( }, [JSON.stringify(searchQuery)]); const classificationData = useExplorationResults(indexPattern, jobConfig, searchQuery); - const docFieldsCount = classificationData.columns.length; - const { columns, errorMessage, status, tableItems, visibleColumns } = classificationData; + const docFieldsCount = classificationData.columnsWithCharts.length; + const { + columnsWithCharts, + errorMessage, + status, + tableItems, + visibleColumns, + } = classificationData; if (jobConfig === undefined || classificationData === undefined) { return null; @@ -139,7 +145,7 @@ export const ExplorationResultsTable: FC = React.memo( - {(columns.length > 0 || searchQuery !== defaultSearchQuery) && ( + {(columnsWithCharts.length > 0 || searchQuery !== defaultSearchQuery) && ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts index a23466fc952b3..654808e4e8866 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts @@ -73,7 +73,7 @@ export const useExplorationResults = ( const columnChartsData = await fetchChartsData( jobConfig.dest.index, ml, - { match_all: {} }, + searchQuery, columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) ); dataGrid.setColumnCharts(columnChartsData); @@ -86,7 +86,11 @@ export const useExplorationResults = ( } // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dataGrid.chartsVisible, jobConfig?.dest.index, JSON.stringify([dataGrid.visibleColumns])]); + }, [ + dataGrid.chartsVisible, + jobConfig?.dest.index, + JSON.stringify([searchQuery, dataGrid.visibleColumns]), + ]); const renderCellValue = useRenderCellValue( indexPattern, @@ -97,7 +101,6 @@ export const useExplorationResults = ( return { ...dataGrid, - columns, renderCellValue, }; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx index 58f8528236bb9..c575edc85da1f 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx @@ -53,7 +53,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); const outlierData = useOutlierData(indexPattern, jobConfig, searchQuery); - const { columns, errorMessage, status, tableItems } = outlierData; + const { columnsWithCharts, errorMessage, status, tableItems } = outlierData; // if it's a searchBar syntax error leave the table visible so they can try again if (status === INDEX_STATUS.ERROR && !errorMessage.includes('parsing_exception')) { @@ -98,35 +98,36 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = )} - {(columns.length > 0 || searchQuery !== defaultSearchQuery) && indexPattern !== undefined && ( - <> - - - - - - - 0 || searchQuery !== defaultSearchQuery) && + indexPattern !== undefined && ( + <> + + + + + + + + + + + {columnsWithCharts.length > 0 && tableItems.length > 0 && ( + - - - - {columns.length > 0 && tableItems.length > 0 && ( - - )} - - )} + )} + + )} ); }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts index f91fc895cead3..ed75f6bc5a991 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts @@ -137,7 +137,6 @@ export const useOutlierData = ( return { ...dataGrid, - columns, renderCellValue, }; }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index 13544b80ed1b2..8badfbedc7c62 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -7,6 +7,8 @@ import moment from 'moment-timezone'; import { useEffect, useMemo, useState } from 'react'; +import { EuiDataGridColumn } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/common'; @@ -85,7 +87,7 @@ export const usePivotData = ( columnKeys.sort(sortColumns(groupByArr)); // EuiDataGrid State - const columns = columnKeys.map((id) => { + const columns: EuiDataGridColumn[] = columnKeys.map((id) => { const field = previewMappings.properties[id]; // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] @@ -251,7 +253,6 @@ export const usePivotData = ( return { ...dataGrid, - columns, renderCellValue, }; }; From 730c8d3131ba9eb95d4d9dbea2ccbb5c94560d38 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 16 Jun 2020 09:49:06 +0200 Subject: [PATCH 26/42] [ML] Fix to consider query to outlier results. Fix if interval is 0. --- .../application/components/data_grid/column_chart.tsx | 7 ++++--- .../application/components/data_grid/use_column_chart.ts | 2 +- .../components/outlier_exploration/use_outlier_data.ts | 6 +++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index 1cafa3a463c4e..fd901eafe040b 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -50,9 +50,10 @@ const getLegendText = (chartData: ChartData, MAX_CHART_COLUMNS: number): string } if (isNumericChartData(chartData)) { - return `${Math.round(chartData.stats[0] * 100) / 100} - ${ - Math.round(chartData.stats[1] * 100) / 100 - }`; + const fromValue = Math.round(chartData.stats[0] * 100) / 100; + const toValue = Math.round(chartData.stats[1] * 100) / 100; + + return fromValue !== toValue ? `${fromValue} - ${toValue}` : '' + fromValue; } throw new Error('Invalid chart data.'); diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts index d282cb7880803..76e12aa149e3d 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts @@ -130,7 +130,7 @@ export const fetchChartsData = async ( aggs[`${id}_histogram`] = { histogram: { field: c.id, - interval: aggIntervals[id], + interval: aggIntervals[id] !== 0 ? aggIntervals[id] : 1, }, }; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts index ed75f6bc5a991..ddedcf2450a74 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts @@ -95,7 +95,11 @@ export const useOutlierData = ( } // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dataGrid.chartsVisible, jobConfig?.dest.index, JSON.stringify([dataGrid.visibleColumns])]); + }, [ + dataGrid.chartsVisible, + jobConfig?.dest.index, + JSON.stringify([searchQuery, dataGrid.visibleColumns]), + ]); const colorRange = useColorRange( COLOR_RANGE.BLUE, From 510f7b6a37194c85bf753852a4e759627aaf64a2 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 16 Jun 2020 10:10:17 +0200 Subject: [PATCH 27/42] [ML] Adds chartsButtonVisible prop. --- .../components/data_grid/data_grid.tsx | 35 +++++++++++-------- .../application/components/data_grid/types.ts | 2 ++ .../components/data_grid/use_data_grid.tsx | 1 + .../public/app/hooks/use_pivot_data.ts | 1 + 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index 3cd6ad3bdc905..e11df0c9498ed 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -58,6 +58,7 @@ export const DataGrid: FC = memo( (props) => { const { chartsVisible, + chartsButtonVisible, columnsWithCharts, dataTestSubj, errorMessage, @@ -189,21 +190,25 @@ export const DataGrid: FC = memo( sorting={{ columns: sortingColumns, onSort }} toolbarVisibility={{ ...euiDataGridToolbarSettings, - additionalControls: ( - - {i18n.translate('xpack.ml.dataGrid.histogramButtonText', { - defaultMessage: 'Histogram Charts', - })} - - ), + ...(chartsButtonVisible + ? { + additionalControls: ( + + {i18n.translate('xpack.ml.dataGrid.histogramButtonText', { + defaultMessage: 'Histogram Charts', + })} + + ), + } + : {}), }} pagination={{ ...pagination, diff --git a/x-pack/plugins/ml/public/application/components/data_grid/types.ts b/x-pack/plugins/ml/public/application/components/data_grid/types.ts index aa9c722cb85ff..756f74c8f9302 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/types.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/types.ts @@ -57,6 +57,7 @@ export interface UseIndexDataReturnType extends Pick< UseDataGridReturnType, | 'chartsVisible' + | 'chartsButtonVisible' | 'columnsWithCharts' | 'errorMessage' | 'invalidSortingColumnns' @@ -79,6 +80,7 @@ export interface UseIndexDataReturnType export interface UseDataGridReturnType { chartsVisible: boolean; + chartsButtonVisible: boolean; columnsWithCharts: EuiDataGridColumn[]; errorMessage: string; invalidSortingColumnns: ColumnId[]; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx index f6644c65524ee..bc4502a819b46 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx @@ -96,6 +96,7 @@ export const useDataGrid = ( return { chartsVisible, + chartsButtonVisible: true, columnsWithCharts: columns.map((c) => { const chartData = columnCharts.find((cd) => cd.id === c.id); diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index 8badfbedc7c62..675198ac9dfa5 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -253,6 +253,7 @@ export const usePivotData = ( return { ...dataGrid, + chartsButtonVisible: false, renderCellValue, }; }; From 813065d6e7a1527f92dedad02b307563161dae61 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 16 Jun 2020 10:37:01 +0200 Subject: [PATCH 28/42] [ML] Text tweak. --- .../ml/public/application/components/data_grid/data_grid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index e11df0c9498ed..6dcc6f4aba192 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -203,7 +203,7 @@ export const DataGrid: FC = memo( onClick={toggleChartVisibility} > {i18n.translate('xpack.ml.dataGrid.histogramButtonText', { - defaultMessage: 'Histogram Charts', + defaultMessage: 'Histogram charts', })} ), From 90b5939b07ce43ed592655c86560064eae6b638e Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 17 Jun 2020 10:58:55 +0200 Subject: [PATCH 29/42] [ML] Fix functional tests. Fix hook render loop. --- x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts | 6 ++---- .../functional/apps/transform/creation_index_pattern.ts | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index 675198ac9dfa5..69831d8f310bc 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -78,7 +78,7 @@ export const usePivotData = ( const api = useApi(); const aggsArr = useMemo(() => dictionaryToArray(aggs), [aggs]); - const groupByArr = dictionaryToArray(groupBy); + const groupByArr = useMemo(() => dictionaryToArray(groupBy), [groupBy]); // Filters mapping properties of type `object`, which get returned for nested field parents. const columnKeys = Object.keys(previewMappings.properties).filter( @@ -196,9 +196,7 @@ export const usePivotData = ( /* eslint-disable react-hooks/exhaustive-deps */ }, [ indexPatternTitle, - aggsArr, - JSON.stringify(groupByArr), - JSON.stringify(query), + JSON.stringify([aggsArr, groupByArr, query]), /* eslint-enable react-hooks/exhaustive-deps */ ]); diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index d6dbcde436dcc..6e2f62021aa0d 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -147,7 +147,7 @@ export default function ({ getService }: FtrProviderContext) { progress: '100', }, indexPreview: { - columns: 20, + columns: 10, rows: 5, }, }, @@ -229,7 +229,7 @@ export default function ({ getService }: FtrProviderContext) { progress: '100', }, indexPreview: { - columns: 20, + columns: 10, rows: 5, }, }, From aaf6bdfb02a7ca5428f604d0ca9bfb0dd8472d11 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 17 Jun 2020 12:53:11 +0200 Subject: [PATCH 30/42] [ML] Fix data fetching when there are no numeric columns. --- .../components/data_grid/use_column_chart.ts | 63 ++++++++++++++++--- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts index 76e12aa149e3d..53638f22627b5 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts @@ -65,17 +65,27 @@ const getFieldType = (schema: EuiDataGridColumn['schema']): KBN_FIELD_TYPES | un return fieldType; }; -export const fetchChartsData = async ( +interface NumericColumnStats { + interval: number; + min: number; + max: number; +} +type NumericColumnStatsMap = Record; +const getAggIntervals = async ( indexPatternTitle: string, api: any, query: any, columnTypes: EuiDataGridColumn[] -): Promise => { +): Promise => { const numericColumns = columnTypes.filter((cT) => { const fieldType = getFieldType(cT.schema); return fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE; }); + if (numericColumns.length === 0) { + return {}; + } + const minMaxAggs = numericColumns.reduce((aggs, c) => { const id = stringHash(c.id); aggs[id] = { @@ -101,7 +111,7 @@ export const fetchChartsData = async ( throw new Error(e); } - const aggIntervals = Object.keys(respStats.aggregations).reduce((p, aggName) => { + return Object.keys(respStats.aggregations).reduce((p, aggName) => { const stats = [respStats.aggregations[aggName].min, respStats.aggregations[aggName].max]; if (!stats.includes(null)) { const delta = respStats.aggregations[aggName].max - respStats.aggregations[aggName].min; @@ -116,11 +126,42 @@ export const fetchChartsData = async ( aggInterval = delta / MAX_CHART_COLUMNS; } - p[aggName] = aggInterval; + p[aggName] = { interval: aggInterval, min: stats[0], max: stats[1] }; } return p; - }, {} as Record); + }, {} as NumericColumnStatsMap); +}; + +interface AggHistogram { + histogram: { + field: string; + interval: number; + }; +} + +interface AggCardinality { + cardinality: { + field: string; + }; +} + +interface AggTerms { + terms: { + field: string; + size: number; + }; +} + +type ChartRequestAgg = AggHistogram | AggCardinality | AggTerms; + +export const fetchChartsData = async ( + indexPatternTitle: string, + api: any, + query: any, + columnTypes: EuiDataGridColumn[] +): Promise => { + const aggIntervals = await getAggIntervals(indexPatternTitle, api, query, columnTypes); const chartDataAggs = columnTypes.reduce((aggs, c) => { const fieldType = getFieldType(c.schema); @@ -130,7 +171,7 @@ export const fetchChartsData = async ( aggs[`${id}_histogram`] = { histogram: { field: c.id, - interval: aggIntervals[id] !== 0 ? aggIntervals[id] : 1, + interval: aggIntervals[id].interval !== 0 ? aggIntervals[id].interval : 1, }, }; } @@ -150,7 +191,11 @@ export const fetchChartsData = async ( }; } return aggs; - }, {} as Record); + }, {} as Record); + + if (Object.keys(chartDataAggs).length === 0) { + return []; + } let respChartsData: any; try { @@ -185,8 +230,8 @@ export const fetchChartsData = async ( return { data: respChartsData.aggregations[`${id}_histogram`].buckets, - interval: aggIntervals[id], - stats: [respStats.aggregations[id].min, respStats.aggregations[id].max], + interval: aggIntervals[id].interval, + stats: [aggIntervals[id].min, aggIntervals[id].max], type: 'numeric', id: c.id, }; From ff8903d1e69667d0448b86a4b913927be99d87d2 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 17 Jun 2020 14:35:48 +0200 Subject: [PATCH 31/42] [ML] Improved types. Display message for unsupported column types. --- .../components/data_grid/column_chart.tsx | 114 ++++++------------ ...e_column_chart.ts => use_column_chart.tsx} | 112 +++++++++++++---- 2 files changed, 122 insertions(+), 104 deletions(-) rename x-pack/plugins/ml/public/application/components/data_grid/{use_column_chart.ts => use_column_chart.tsx} (76%) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index fd901eafe040b..b31a7e47894b4 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -10,106 +10,64 @@ import classNames from 'classnames'; import { BarSeries, Chart, Settings } from '@elastic/charts'; import { EuiDataGridColumn } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - import './column_chart.scss'; import { isNumericChartData, - isOrdinalChartData, + isUnsupportedChartData, useColumnChart, ChartData, } from './use_column_chart'; -const getLegendText = (chartData: ChartData, MAX_CHART_COLUMNS: number): string | JSX.Element => { - if (chartData.type === 'boolean') { - return ( - - - - - - - -
{chartData.data[0].key_as_string}{chartData.data[1].key_as_string}
- ); - } - - if (isOrdinalChartData(chartData) && chartData.cardinality <= MAX_CHART_COLUMNS) { - return i18n.translate('xpack.ml.dataGridChart.singleCategoryLegend', { - defaultMessage: `{cardinality, plural, one {# category} other {# categories}}`, - values: { cardinality: chartData.cardinality }, - }); - } - - if (isOrdinalChartData(chartData) && chartData.cardinality > MAX_CHART_COLUMNS) { - return i18n.translate('xpack.ml.dataGridChart.topCategoriesLegend', { - defaultMessage: `top {MAX_CHART_COLUMNS} of {cardinality} categories`, - values: { cardinality: chartData.cardinality, MAX_CHART_COLUMNS }, - }); - } - - if (isNumericChartData(chartData)) { - const fromValue = Math.round(chartData.stats[0] * 100) / 100; - const toValue = Math.round(chartData.stats[1] * 100) / 100; - - return fromValue !== toValue ? `${fromValue} - ${toValue}` : '' + fromValue; - } - - throw new Error('Invalid chart data.'); -}; - interface Props { chartData: ChartData; columnType: EuiDataGridColumn; } export const ColumnChart: FC = ({ chartData, columnType }) => { - const { coloredData, xScaleType, MAX_CHART_COLUMNS } = useColumnChart(chartData, columnType); - - if (coloredData.length === 0) { - return <>{columnType.id}; - } + const { data, legendText, xScaleType } = useColumnChart(chartData, columnType); return ( <> -
- - - d.datum.color} - data={coloredData} - /> - -
+ {!isUnsupportedChartData(chartData) && data.length > 0 && ( +
+ + + d.datum.color} + data={data} + /> + +
+ )}
- {getLegendText(chartData, MAX_CHART_COLUMNS)} + {legendText}
{columnType.id} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx similarity index 76% rename from x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts rename to x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx index 53638f22627b5..cdbdcf7fbd767 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx @@ -6,11 +6,14 @@ import moment from 'moment'; import { BehaviorSubject } from 'rxjs'; +import React from 'react'; import { useObservable } from 'react-use'; import { euiPaletteColorBlind, EuiDataGridColumn } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; import { stringHash } from '../../../../common/util/string_utils'; @@ -23,9 +26,8 @@ const BAR_COLOR = euiPaletteColorBlind()[0]; const BAR_COLOR_BLUR = euiPaletteColorBlind({ rotations: 2 })[10]; const MAX_CHART_COLUMNS = 20; -const getXScaleType = ( - kbnFieldType: KBN_FIELD_TYPES | undefined -): 'ordinal' | 'time' | 'linear' | undefined => { +type XScaleType = 'ordinal' | 'time' | 'linear' | undefined; +const getXScaleType = (kbnFieldType: KBN_FIELD_TYPES | undefined): XScaleType => { switch (kbnFieldType) { case KBN_FIELD_TYPES.BOOLEAN: case KBN_FIELD_TYPES.IP: @@ -248,9 +250,7 @@ export const fetchChartsData = async ( } return { - type: 'ordinal', - cardinality: 0, - data: [], + type: 'unsupported', id: c.id, }; } @@ -305,10 +305,72 @@ export const isOrdinalChartData = (arg: any): arg is OrdinalChartData => { ); }; +interface UnsupportedChartData { + id: string; + type: 'unsupported'; +} + +export const isUnsupportedChartData = (arg: any): arg is UnsupportedChartData => { + return arg.hasOwnProperty('type') && arg.type === 'unsupported'; +}; + type ChartDataItem = NumericDataItem | OrdinalDataItem; -export type ChartData = NumericChartData | OrdinalChartData; +export type ChartData = NumericChartData | OrdinalChartData | UnsupportedChartData; + +type LegendText = string | JSX.Element; +const getLegendText = (chartData: ChartData): LegendText => { + if (chartData.type === 'unsupported') { + return i18n.translate('xpack.ml.dataGridChart.histogramNotAvailable', { + defaultMessage: `Histogram chart not supported for this type of column.`, + }); + } + if (chartData.type === 'boolean') { + return ( + + + + + + + +
{chartData.data[0].key_as_string}{chartData.data[1].key_as_string}
+ ); + } + + if (isOrdinalChartData(chartData) && chartData.cardinality <= MAX_CHART_COLUMNS) { + return i18n.translate('xpack.ml.dataGridChart.singleCategoryLegend', { + defaultMessage: `{cardinality, plural, one {# category} other {# categories}}`, + values: { cardinality: chartData.cardinality }, + }); + } + + if (isOrdinalChartData(chartData) && chartData.cardinality > MAX_CHART_COLUMNS) { + return i18n.translate('xpack.ml.dataGridChart.topCategoriesLegend', { + defaultMessage: `top {MAX_CHART_COLUMNS} of {cardinality} categories`, + values: { cardinality: chartData.cardinality, MAX_CHART_COLUMNS }, + }); + } + + if (isNumericChartData(chartData)) { + const fromValue = Math.round(chartData.stats[0] * 100) / 100; + const toValue = Math.round(chartData.stats[1] * 100) / 100; + + return fromValue !== toValue ? `${fromValue} - ${toValue}` : '' + fromValue; + } + + throw new Error('Invalid chart data.'); +}; -export const useColumnChart = (chartData: ChartData, columnType: EuiDataGridColumn) => { +interface ColumnChart { + data: ChartDataItem[]; + legendText: LegendText; + xScaleType: XScaleType; +} + +export const useColumnChart = ( + chartData: ChartData, + columnType: EuiDataGridColumn +): ColumnChart => { const fieldType = getFieldType(columnType.schema); const hoveredRow = useObservable(hoveredRow$); @@ -349,27 +411,25 @@ export const useColumnChart = (chartData: ChartData, columnType: EuiDataGridColu return BAR_COLOR_BLUR; }; + let data: ChartDataItem[] = []; + // The if/else if/else is a work-around because `.map()` doesn't work with union types. // See TS Caveats for details: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-3.html#caveats if (isOrdinalChartData(chartData)) { - const coloredData = chartData.data.map((d: ChartDataItem) => ({ ...d, color: getColor(d) })); - return { - cardinality: chartData.cardinality, - coloredData, - fieldType, - xScaleType, - MAX_CHART_COLUMNS, - }; + data = chartData.data.map((d: OrdinalDataItem) => ({ + ...d, + color: getColor(d), + })); } else if (isNumericChartData(chartData)) { - const coloredData = chartData.data.map((d: ChartDataItem) => ({ ...d, color: getColor(d) })); - return { - coloredData, - fieldType, - stats: chartData.stats, - xScaleType, - MAX_CHART_COLUMNS, - }; - } else { - throw new Error('invalid chart data.'); + data = chartData.data.map((d: NumericDataItem) => ({ + ...d, + color: getColor(d), + })); } + + return { + data, + legendText: getLegendText(chartData), + xScaleType, + }; }; From 8364f20a6766244892b90d7c31abc1cafa2da53d Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 17 Jun 2020 14:45:47 +0200 Subject: [PATCH 32/42] [ML] Fix boolean chart legend. --- .../application/components/data_grid/use_column_chart.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx index cdbdcf7fbd767..4cc99c28892b6 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx @@ -329,8 +329,8 @@ const getLegendText = (chartData: ChartData): LegendText => { - - + {chartData.data[0] !== undefined && } + {chartData.data[1] !== undefined && }
{chartData.data[0].key_as_string}{chartData.data[1].key_as_string}{chartData.data[0].key_as_string}{chartData.data[1].key_as_string}
From fbc92214c71fee246cbca664ca739e1ab146ea04 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 17 Jun 2020 14:51:34 +0200 Subject: [PATCH 33/42] [ML] Improve stringHash test. --- x-pack/plugins/ml/common/util/string_utils.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/common/util/string_utils.test.ts b/x-pack/plugins/ml/common/util/string_utils.test.ts index e6de1e20bf057..8afc7e52c9fa5 100644 --- a/x-pack/plugins/ml/common/util/string_utils.test.ts +++ b/x-pack/plugins/ml/common/util/string_utils.test.ts @@ -48,8 +48,10 @@ describe('ML - string utils', () => { }); describe('stringHash', () => { - test('should return a number based off a string', () => { - expect(typeof stringHash('the-string')).toBe('number'); + test('should return a unique number based off a string', () => { + const hash1 = stringHash('the-string-1'); + const hash2 = stringHash('the-string-2'); + expect(hash1).not.toBe(hash2); }); }); }); From a7179642129a046249a659130d118ced3c251b95 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 17 Jun 2020 16:16:22 +0200 Subject: [PATCH 34/42] [ML] Fix state/loop issue. --- .../public/app/hooks/use_pivot_data.ts | 3 ++- .../expanded_row_preview_pane.tsx | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index 69831d8f310bc..b073dace30340 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -196,7 +196,8 @@ export const usePivotData = ( /* eslint-disable react-hooks/exhaustive-deps */ }, [ indexPatternTitle, - JSON.stringify([aggsArr, groupByArr, query]), + aggsArr, + JSON.stringify([groupByArr, query]), /* eslint-enable react-hooks/exhaustive-deps */ ]); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx index e183712b390cf..a917fc73ad8fb 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC } from 'react'; +import React, { useMemo, FC } from 'react'; import { DataGrid } from '../../../../../shared_imports'; @@ -24,14 +24,22 @@ interface ExpandedRowPreviewPaneProps { export const ExpandedRowPreviewPane: FC = ({ transformConfig }) => { const toastNotifications = useToastNotifications(); - const { aggList, groupByList, searchQuery } = applyTransformConfigToDefineState( - getDefaultStepDefineState({} as SearchItems), - transformConfig + + const { aggList, groupByList, searchQuery } = useMemo( + () => + applyTransformConfigToDefineState( + getDefaultStepDefineState({} as SearchItems), + transformConfig + ), + [transformConfig] ); - const pivotQuery = getPivotQuery(searchQuery); + + const pivotQuery = useMemo(() => getPivotQuery(searchQuery), [searchQuery]); + const indexPatternTitle = Array.isArray(transformConfig.source.index) ? transformConfig.source.index.join(',') : transformConfig.source.index; + const pivotPreviewProps = usePivotData(indexPatternTitle, pivotQuery, aggList, groupByList); return ( From 8570a4beaf299eb4164337d73f44e9ac3651d8bd Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 17 Jun 2020 17:06:07 +0200 Subject: [PATCH 35/42] [ML] Improve legend text when chart not available or no data available. --- .../application/components/data_grid/column_chart.tsx | 3 ++- .../application/components/data_grid/data_grid.tsx | 2 +- .../components/data_grid/use_column_chart.tsx | 9 ++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index b31a7e47894b4..3da8128381e9c 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -34,6 +34,7 @@ export const ColumnChart: FC = ({ chartData, columnType }) => { = ({ chartData, columnType }) => { )}
{legendText} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index 6dcc6f4aba192..a1060d1f94c51 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -180,7 +180,7 @@ export const DataGrid: FC = memo( { - c.initialWidth = 150; + c.initialWidth = 165; return c; })} columnVisibility={{ visibleColumns, setVisibleColumns }} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx index 4cc99c28892b6..b338b0819aa48 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx @@ -321,9 +321,16 @@ type LegendText = string | JSX.Element; const getLegendText = (chartData: ChartData): LegendText => { if (chartData.type === 'unsupported') { return i18n.translate('xpack.ml.dataGridChart.histogramNotAvailable', { - defaultMessage: `Histogram chart not supported for this type of column.`, + defaultMessage: 'Chart not supported.', }); } + + if (chartData.data.length === 0) { + return i18n.translate('xpack.ml.dataGridChart.notEnoughData', { + defaultMessage: `0 documents contain field.`, + }); + } + if (chartData.type === 'boolean') { return ( From eee4bfa87385da0af8ce920a1557bd9028f7f89c Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 17 Jun 2020 18:50:27 +0200 Subject: [PATCH 36/42] [ML] Remove unneeded import and margin. --- .../components/data_grid/column_chart.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index 3da8128381e9c..89c9fca397aa9 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -12,12 +12,7 @@ import { EuiDataGridColumn } from '@elastic/eui'; import './column_chart.scss'; -import { - isNumericChartData, - isUnsupportedChartData, - useColumnChart, - ChartData, -} from './use_column_chart'; +import { isUnsupportedChartData, useColumnChart, ChartData } from './use_column_chart'; interface Props { chartData: ChartData; @@ -36,9 +31,9 @@ export const ColumnChart: FC = ({ chartData, columnType }) => { theme={{ background: { color: 'transparent' }, chartMargins: { - left: 4, - right: 4, - top: 5, + left: 0, + right: 0, + top: 0, bottom: 1, }, chartPaddings: { From 0deff76f9ae8d068100c7cd4881b9effab2ccf9e Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 18 Jun 2020 10:52:38 +0200 Subject: [PATCH 37/42] [ML] Add e2e tests. --- .../components/data_grid/column_chart.tsx | 12 ++-- .../components/data_grid/data_grid.tsx | 2 + .../components/data_grid/use_data_grid.tsx | 8 ++- .../apps/transform/creation_index_pattern.ts | 28 +++++++++ .../functional/services/transform/wizard.ts | 58 +++++++++++++++++++ 5 files changed, 101 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx index 89c9fca397aa9..00e2d5b14a96b 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx @@ -17,15 +17,16 @@ import { isUnsupportedChartData, useColumnChart, ChartData } from './use_column_ interface Props { chartData: ChartData; columnType: EuiDataGridColumn; + dataTestSubj: string; } -export const ColumnChart: FC = ({ chartData, columnType }) => { +export const ColumnChart: FC = ({ chartData, columnType, dataTestSubj }) => { const { data, legendText, xScaleType } = useColumnChart(chartData, columnType); return ( - <> +
{!isUnsupportedChartData(chartData) && data.length > 0 && ( -
+
= ({ chartData, columnType }) => { className={classNames('mlDataGridChart__legend', { 'mlDataGridChart__legend--numeric': columnType.schema === 'number', })} + data-test-subj={`${dataTestSubj}-legend`} > {legendText}
- {columnType.id} - +
{columnType.id}
+
); }; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index a1060d1f94c51..9af7a869e0e56 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -194,9 +194,11 @@ export const DataGrid: FC = memo( ? { additionalControls: ( { + columnsWithCharts: columns.map((c, index) => { const chartData = columnCharts.find((cd) => cd.id === c.id); return { ...c, display: chartData !== undefined && chartsVisible === true ? ( - + ) : undefined, }; }), diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index 2fefde8f2cdcc..e48bcc03d4c7a 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -150,6 +150,22 @@ export default function ({ getService }: FtrProviderContext) { columns: 10, rows: 5, }, + histogramCharts: [ + { chartAvailable: false, id: 'category', legend: 'Chart not supported.' }, + { chartAvailable: true, id: 'currency', legend: '1 category' }, + { + chartAvailable: false, + id: 'customer_birth_date', + legend: '0 documents contain field.', + }, + { chartAvailable: false, id: 'customer_first_name', legend: 'Chart not supported.' }, + { chartAvailable: false, id: 'customer_full_name', legend: 'Chart not supported.' }, + { chartAvailable: true, id: 'customer_gender', legend: '2 categories' }, + { chartAvailable: true, id: 'customer_id', legend: 'top 20 of 46 categories' }, + { chartAvailable: false, id: 'customer_last_name', legend: 'Chart not supported.' }, + { chartAvailable: true, id: 'customer_phone', legend: '1 category' }, + { chartAvailable: true, id: 'day_of_week', legend: '7 categories' }, + ], }, }, { @@ -232,6 +248,7 @@ export default function ({ getService }: FtrProviderContext) { columns: 10, rows: 5, }, + histogramCharts: [], }, }, ]; @@ -289,6 +306,16 @@ export default function ({ getService }: FtrProviderContext) { await transform.wizard.assertAdvancedQueryEditorSwitchCheckState(false); }); + it('enables the index preview histogram charts', async () => { + await transform.wizard.enableIndexPreviewHistogramCharts(); + }); + + it('displays the index preview histogram charts', async () => { + await transform.wizard.assertIndexPreviewHistogramCharts( + testData.expected.histogramCharts + ); + }); + it('adds the group by entries', async () => { for (const [index, entry] of testData.groupByEntries.entries()) { await transform.wizard.assertGroupByInputExists(); @@ -323,6 +350,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('shows the pivot preview', async () => { + await transform.wizard.assertPivotPreviewChartHistogramButtonExists(); await transform.wizard.assertPivotPreviewColumnValues( testData.expected.pivotPreview.column, testData.expected.pivotPreview.values diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index 8b61e8c895e30..cb276145741a3 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -76,6 +76,12 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { await testSubjects.existOrFail(selector); }, + async assertPivotPreviewChartHistogramButtonExists() { + // the button should not exist because histogram charts + // for the pivot preview are not supported yet + await testSubjects.missingOrFail('transformPivotPreviewHistogramButton'); + }, + async parseEuiDataGrid(tableSubj: string) { const table = await testSubjects.find(`~${tableSubj}`); const $ = await table.parseDomContent(); @@ -155,6 +161,58 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { await this.assertPivotPreviewExists('empty'); }, + async assertIndexPreviewHistogramChartButtonExists() { + await testSubjects.existOrFail('transformIndexPreviewHistogramButton'); + }, + + async enableIndexPreviewHistogramCharts() { + await this.assertIndexPreviewHistogramChartButtonCheckState(false); + await testSubjects.click('transformIndexPreviewHistogramButton'); + await this.assertIndexPreviewHistogramChartButtonCheckState(true); + }, + + async assertIndexPreviewHistogramChartButtonCheckState(expectedCheckState: boolean) { + const actualCheckState = + (await testSubjects.getAttribute( + 'transformIndexPreviewHistogramButton', + 'aria-checked' + )) === 'true'; + expect(actualCheckState).to.eql( + expectedCheckState, + `Chart histogram button check state should be '${expectedCheckState}' (got '${actualCheckState}')` + ); + }, + + async assertIndexPreviewHistogramCharts( + expectedHistogramCharts: Array<{ chartAvailable: boolean; id: string; legend: string }> + ) { + // For each chart, get the content of each header cell and assert + // the legend text and column id and if the chart should be present or not. + await retry.tryForTime(5000, async () => { + for (const [index, expected] of expectedHistogramCharts.entries()) { + await testSubjects.existOrFail(`mlDataGridChart-${index}`); + + if (expected.chartAvailable) { + await testSubjects.existOrFail(`mlDataGridChart-${index}-histogram`); + } else { + await testSubjects.missingOrFail(`mlDataGridChart-${index}-histogram`); + } + + const actualLegend = await testSubjects.getVisibleText(`mlDataGridChart-${index}-legend`); + expect(actualLegend).to.eql( + expected.legend, + `Query input text should be '${expected.legend}' (got ${actualLegend})` + ); + + const actualId = await testSubjects.getVisibleText(`mlDataGridChart-${index}-id`); + expect(actualId).to.eql( + expected.id, + `Query input text should be '${expected.id}' (got ${actualId})` + ); + } + }); + }, + async assertQueryInputExists() { await testSubjects.existOrFail('transformQueryInput'); }, From 1bc0bcaf18ac42dc68e2c05846d0643b9f278e2d Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 18 Jun 2020 11:30:46 +0200 Subject: [PATCH 38/42] [ML] e2e tweaks. --- .../functional/apps/transform/creation_index_pattern.ts | 2 +- x-pack/test/functional/services/transform/wizard.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index e48bcc03d4c7a..55b2f4a0220ad 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -350,7 +350,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('shows the pivot preview', async () => { - await transform.wizard.assertPivotPreviewChartHistogramButtonExists(); + await transform.wizard.assertPivotPreviewChartHistogramButtonMissing(); await transform.wizard.assertPivotPreviewColumnValues( testData.expected.pivotPreview.column, testData.expected.pivotPreview.values diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index cb276145741a3..9cfdbadac8a3b 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -76,7 +76,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { await testSubjects.existOrFail(selector); }, - async assertPivotPreviewChartHistogramButtonExists() { + async assertPivotPreviewChartHistogramButtonMissing() { // the button should not exist because histogram charts // for the pivot preview are not supported yet await testSubjects.missingOrFail('transformPivotPreviewHistogramButton'); @@ -201,13 +201,13 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { const actualLegend = await testSubjects.getVisibleText(`mlDataGridChart-${index}-legend`); expect(actualLegend).to.eql( expected.legend, - `Query input text should be '${expected.legend}' (got ${actualLegend})` + `Legend text for column '${index}' should be '${expected.legend}' (got '${actualLegend}')` ); const actualId = await testSubjects.getVisibleText(`mlDataGridChart-${index}-id`); expect(actualId).to.eql( expected.id, - `Query input text should be '${expected.id}' (got ${actualId})` + `Id text for column '${index}' should be '${expected.id}' (got '${actualId}')` ); } }); From f9a1399c467dbf8b61410e398f602c4ad7655a8f Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 18 Jun 2020 23:59:24 +0200 Subject: [PATCH 39/42] [ML] Fix error messages. --- .../components/data_grid/common.ts | 20 ++++++++++++++ .../application/components/data_grid/index.ts | 1 + .../configuration_step_form.tsx | 3 ++- .../hooks/use_index_data.ts | 27 +++++++++++++------ .../exploration_results_table.tsx | 7 ++++- .../use_exploration_results.ts | 26 +++++++++++------- .../outlier_exploration/use_outlier_data.ts | 22 +++++++++------ .../public/app/hooks/use_index_data.ts | 24 +++++++++++------ .../transform/public/shared_imports.ts | 1 + 9 files changed, 96 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index 5f0d53a4c082c..1f0fcb63f019d 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -13,6 +13,10 @@ import { EuiDataGridStyle, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { CoreSetup } from 'src/core/public'; + import { IndexPattern, IFieldType, @@ -20,6 +24,8 @@ import { KBN_FIELD_TYPES, } from '../../../../../../../src/plugins/data/public'; +import { extractErrorMessage } from '../../../../common/util/errors'; + import { BASIC_NUMERICAL_TYPES, EXTENDED_NUMERICAL_TYPES, @@ -298,3 +304,17 @@ export const multiColumnSortFactory = (sortingColumns: EuiDataGridSorting['colum return sortFn; }; + +export const showDataGridColumnChartErrorMessageToast = ( + e: any, + toastNotifications: CoreSetup['notifications']['toasts'] +) => { + const error = extractErrorMessage(e); + + toastNotifications.addDanger( + i18n.translate('xpack.ml.dataGrid.columnChart.ErrorMessageToast', { + defaultMessage: 'An error occurred fetching the histogram charts data: {error}', + values: { error: error !== '' ? error : e }, + }) + ); +}; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/index.ts b/x-pack/plugins/ml/public/application/components/data_grid/index.ts index d8ad9238c3348..80bc6b861f742 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/index.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/index.ts @@ -9,6 +9,7 @@ export { getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, multiColumnSortFactory, + showDataGridColumnChartErrorMessageToast, useRenderCellValue, } from './common'; export { fetchChartsData, ChartData } from './use_column_chart'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index e63756686a4ba..2c3079e24cf1d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -87,7 +87,8 @@ export const ConfigurationStepForm: FC = ({ const indexData = useIndexData( currentIndexPattern, - savedSearchQuery !== undefined ? savedSearchQuery : jobConfigQuery + savedSearchQuery !== undefined ? savedSearchQuery : jobConfigQuery, + toastNotifications ); const indexPreviewProps = { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts index 99e0963771ac9..64de87e174ffd 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts @@ -8,11 +8,14 @@ import { useEffect } from 'react'; import { EuiDataGridColumn } from '@elastic/eui'; +import { CoreSetup } from 'src/core/public'; + import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; import { fetchChartsData, getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, + showDataGridColumnChartErrorMessageToast, useDataGrid, useRenderCellValue, EsSorting, @@ -25,7 +28,11 @@ import { ml } from '../../../../services/ml_api_service'; type IndexSearchResponse = SearchResponse7; -export const useIndexData = (indexPattern: IndexPattern, query: any): UseIndexDataReturnType => { +export const useIndexData = ( + indexPattern: IndexPattern, + query: any, + toastNotifications: CoreSetup['notifications']['toasts'] +): UseIndexDataReturnType => { const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern); // EuiDataGrid State @@ -97,13 +104,17 @@ export const useIndexData = (indexPattern: IndexPattern, query: any): UseIndexDa }, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]); const fetchColumnChartsData = async function () { - const columnChartsData = await fetchChartsData( - indexPattern.title, - ml, - query, - columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) - ); - dataGrid.setColumnCharts(columnChartsData); + try { + const columnChartsData = await fetchChartsData( + indexPattern.title, + ml, + query, + columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) + ); + dataGrid.setColumnCharts(columnChartsData); + } catch (e) { + showDataGridColumnChartErrorMessageToast(e, toastNotifications); + } }; useEffect(() => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx index 7b96119632148..941fbefd78084 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx @@ -67,7 +67,12 @@ export const ExplorationResultsTable: FC = React.memo( setEvaluateSearchQuery(searchQuery); }, [JSON.stringify(searchQuery)]); - const classificationData = useExplorationResults(indexPattern, jobConfig, searchQuery); + const classificationData = useExplorationResults( + indexPattern, + jobConfig, + searchQuery, + getToastNotifications() + ); const docFieldsCount = classificationData.columnsWithCharts.length; const { columnsWithCharts, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts index 654808e4e8866..320d2f4ace184 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts @@ -8,11 +8,14 @@ import { useEffect } from 'react'; import { EuiDataGridColumn } from '@elastic/eui'; +import { CoreSetup } from 'src/core/public'; + import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { fetchChartsData, getDataGridSchemasFromFieldTypes, + showDataGridColumnChartErrorMessageToast, useDataGrid, useRenderCellValue, UseIndexDataReturnType, @@ -31,7 +34,8 @@ import { sortExplorationResultsFields, ML__ID_COPY } from '../../../../common/fi export const useExplorationResults = ( indexPattern: IndexPattern | undefined, jobConfig: DataFrameAnalyticsConfig | undefined, - searchQuery: SavedSearchQuery + searchQuery: SavedSearchQuery, + toastNotifications: CoreSetup['notifications']['toasts'] ): UseIndexDataReturnType => { const needsDestIndexFields = indexPattern !== undefined && indexPattern.title === jobConfig?.source.index[0]; @@ -69,14 +73,18 @@ export const useExplorationResults = ( }, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]); const fetchColumnChartsData = async function () { - if (jobConfig !== undefined) { - const columnChartsData = await fetchChartsData( - jobConfig.dest.index, - ml, - searchQuery, - columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) - ); - dataGrid.setColumnCharts(columnChartsData); + try { + if (jobConfig !== undefined) { + const columnChartsData = await fetchChartsData( + jobConfig.dest.index, + ml, + searchQuery, + columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) + ); + dataGrid.setColumnCharts(columnChartsData); + } + } catch (e) { + showDataGridColumnChartErrorMessageToast(e, toastNotifications); } }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts index ddedcf2450a74..e97209d5ba627 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts @@ -18,12 +18,14 @@ import { import { fetchChartsData, getDataGridSchemasFromFieldTypes, + showDataGridColumnChartErrorMessageToast, useDataGrid, useRenderCellValue, UseIndexDataReturnType, } from '../../../../../components/data_grid'; import { SavedSearchQuery } from '../../../../../contexts/ml'; import { ml } from '../../../../../services/ml_api_service'; +import { getToastNotifications } from '../../../../../util/dependency_cache'; import { getIndexData, getIndexFields, DataFrameAnalyticsConfig } from '../../../../common'; import { DEFAULT_RESULTS_FIELD, FEATURE_INFLUENCE } from '../../../../common/constants'; @@ -78,14 +80,18 @@ export const useOutlierData = ( }, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]); const fetchColumnChartsData = async function () { - if (jobConfig !== undefined) { - const columnChartsData = await fetchChartsData( - jobConfig.dest.index, - ml, - searchQuery, - columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) - ); - dataGrid.setColumnCharts(columnChartsData); + try { + if (jobConfig !== undefined) { + const columnChartsData = await fetchChartsData( + jobConfig.dest.index, + ml, + searchQuery, + columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) + ); + dataGrid.setColumnCharts(columnChartsData); + } + } catch (e) { + showDataGridColumnChartErrorMessageToast(e, getToastNotifications()); } }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index cfab75f0217b8..36a70a07ced03 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -13,6 +13,7 @@ import { getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, getErrorMessage, + showDataGridColumnChartErrorMessageToast, useDataGrid, useRenderCellValue, EsSorting, @@ -26,6 +27,8 @@ import { isDefaultQuery, matchAllQuery, PivotQuery } from '../common'; import { SearchItems } from './use_search_items'; import { useApi } from './use_api'; +import { useToastNotifications } from '../app_dependencies'; + type IndexSearchResponse = SearchResponse7; export const useIndexData = ( @@ -33,6 +36,7 @@ export const useIndexData = ( query: PivotQuery ): UseIndexDataReturnType => { const api = useApi(); + const toastNotifications = useToastNotifications(); const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern); @@ -102,14 +106,18 @@ export const useIndexData = ( }; const fetchColumnChartsData = async function () { - const columnChartsData = await fetchChartsData( - indexPattern.title, - api, - isDefaultQuery(query) ? matchAllQuery : query, - columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) - ); - - setColumnCharts(columnChartsData); + try { + const columnChartsData = await fetchChartsData( + indexPattern.title, + api, + isDefaultQuery(query) ? matchAllQuery : query, + columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) + ); + + setColumnCharts(columnChartsData); + } catch (e) { + showDataGridColumnChartErrorMessageToast(e, toastNotifications); + } }; useEffect(() => { diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index a7ad71cea8f8f..f50ae9274fa4a 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -20,6 +20,7 @@ export { getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, multiColumnSortFactory, + showDataGridColumnChartErrorMessageToast, useDataGrid, useRenderCellValue, ChartData, From 4da1352c72e65d34a846716a8b9881c3357d45c1 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 19 Jun 2020 00:08:44 +0200 Subject: [PATCH 40/42] [ML] Fix types for api/ml.esSearch. --- .../components/data_grid/use_column_chart.tsx | 10 +++++----- .../pages/analytics_creation/hooks/use_index_data.ts | 2 +- .../use_exploration_results.ts | 2 +- .../components/outlier_exploration/use_outlier_data.ts | 2 +- .../transform/public/app/hooks/use_index_data.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx index b338b0819aa48..ac53a0b6cf37b 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx @@ -75,7 +75,7 @@ interface NumericColumnStats { type NumericColumnStatsMap = Record; const getAggIntervals = async ( indexPatternTitle: string, - api: any, + esSearch: (payload: any) => Promise, query: any, columnTypes: EuiDataGridColumn[] ): Promise => { @@ -100,7 +100,7 @@ const getAggIntervals = async ( let respStats: any; try { - respStats = await api.esSearch({ + respStats = await esSearch({ index: indexPatternTitle, size: 0, body: { @@ -159,11 +159,11 @@ type ChartRequestAgg = AggHistogram | AggCardinality | AggTerms; export const fetchChartsData = async ( indexPatternTitle: string, - api: any, + esSearch: (payload: any) => Promise, query: any, columnTypes: EuiDataGridColumn[] ): Promise => { - const aggIntervals = await getAggIntervals(indexPatternTitle, api, query, columnTypes); + const aggIntervals = await getAggIntervals(indexPatternTitle, esSearch, query, columnTypes); const chartDataAggs = columnTypes.reduce((aggs, c) => { const fieldType = getFieldType(c.schema); @@ -201,7 +201,7 @@ export const fetchChartsData = async ( let respChartsData: any; try { - respChartsData = await api.esSearch({ + respChartsData = await esSearch({ index: indexPatternTitle, size: 0, body: { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts index 64de87e174ffd..ee0e5c1955ead 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts @@ -107,7 +107,7 @@ export const useIndexData = ( try { const columnChartsData = await fetchChartsData( indexPattern.title, - ml, + ml.esSearch, query, columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts index 320d2f4ace184..796670f6a864d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts @@ -77,7 +77,7 @@ export const useExplorationResults = ( if (jobConfig !== undefined) { const columnChartsData = await fetchChartsData( jobConfig.dest.index, - ml, + ml.esSearch, searchQuery, columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts index e97209d5ba627..beb6836bf801f 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts @@ -84,7 +84,7 @@ export const useOutlierData = ( if (jobConfig !== undefined) { const columnChartsData = await fetchChartsData( jobConfig.dest.index, - ml, + ml.esSearch, searchQuery, columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) ); diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index 36a70a07ced03..c821c183ad370 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -109,7 +109,7 @@ export const useIndexData = ( try { const columnChartsData = await fetchChartsData( indexPattern.title, - api, + api.esSearch, isDefaultQuery(query) ? matchAllQuery : query, columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id)) ); From 82447aedf902354da39ae956a32e2547b6ed94e3 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 19 Jun 2020 00:19:23 +0200 Subject: [PATCH 41/42] [ML] Remove unneeded try/catch blocks. --- .../components/data_grid/use_column_chart.tsx | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx index ac53a0b6cf37b..c5233ee0b6b96 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx @@ -98,20 +98,15 @@ const getAggIntervals = async ( return aggs; }, {} as Record); - let respStats: any; - try { - respStats = await esSearch({ - index: indexPatternTitle, + const respStats = await esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: minMaxAggs, size: 0, - body: { - query, - aggs: minMaxAggs, - size: 0, - }, - }); - } catch (e) { - throw new Error(e); - } + }, + }); return Object.keys(respStats.aggregations).reduce((p, aggName) => { const stats = [respStats.aggregations[aggName].min, respStats.aggregations[aggName].max]; @@ -199,20 +194,15 @@ export const fetchChartsData = async ( return []; } - let respChartsData: any; - try { - respChartsData = await esSearch({ - index: indexPatternTitle, + const respChartsData = await esSearch({ + index: indexPatternTitle, + size: 0, + body: { + query, + aggs: chartDataAggs, size: 0, - body: { - query, - aggs: chartDataAggs, - size: 0, - }, - }); - } catch (e) { - throw new Error(e); - } + }, + }); const chartsData: ChartData[] = columnTypes.map( (c): ChartData => { From 2f6f6427d6282ff26f9d4b59cc6c879b3e1a5080 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 19 Jun 2020 00:28:29 +0200 Subject: [PATCH 42/42] [ML] Return an empty legend string instead of throwing an error. --- .../application/components/data_grid/use_column_chart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx index c5233ee0b6b96..6b207a999eb52 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx @@ -355,7 +355,7 @@ const getLegendText = (chartData: ChartData): LegendText => { return fromValue !== toValue ? `${fromValue} - ${toValue}` : '' + fromValue; } - throw new Error('Invalid chart data.'); + return ''; }; interface ColumnChart {