diff --git a/x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts b/x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts index 14ea20a238ada6..d5a0a50fe6a54e 100644 --- a/x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts +++ b/x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts @@ -137,13 +137,27 @@ interface InitialPageOptions extends PageSizeOptions { initialPageIndex: number; initialPageSize: number; } -type Pagination = boolean | PageSizeOptions | InitialPageOptions; +type PaginationProp = boolean | PageSizeOptions | InitialPageOptions; -type PropertySortType = any; -type Sorting = boolean | { sort: PropertySortType }; +export enum SORT_DIRECTION { + ASC = 'asc', + DESC = 'desc', +} +export type SortDirection = SORT_DIRECTION.ASC | SORT_DIRECTION.DESC; +export interface Sorting { + sort: { + field: string; + direction: SortDirection; + }; +} +export type SortingPropType = boolean | Sorting; type SelectionType = any; +export interface OnTableChangeArg extends Sorting { + page: { index: number; size: number }; +} + type ItemIdTypeFunc = (item: Item) => string; type ItemIdType = | string // the name of the item id property @@ -160,8 +174,8 @@ export type EuiInMemoryTableProps = CommonProps & { error?: string; compressed?: boolean; search?: SearchType; - pagination?: Pagination; - sorting?: Sorting; + pagination?: PaginationProp; + sorting?: SortingPropType; // Set `allowNeutralSort` to false to force column sorting. Defaults to true. allowNeutralSort?: boolean; responsive?: boolean; @@ -170,10 +184,7 @@ export type EuiInMemoryTableProps = CommonProps & { itemIdToExpandedRowMap?: Record; rowProps?: () => void | Record; cellProps?: () => void | Record; - onTableChange?: (arg: { - page: { index: number; size: number }; - sort: { field: string; direction: string }; - }) => void; + onTableChange?: (arg: OnTableChangeArg) => void; }; interface ComponentWithConstructor extends Component { diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx index a08f60c868b7b9..c26ce59343f453 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx @@ -28,7 +28,12 @@ import { RIGHT_ALIGNMENT, } from '@elastic/eui'; -import { ColumnType, MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table'; +import { + ColumnType, + MlInMemoryTable, + SortingPropType, + SORT_DIRECTION, +} from '../../../../../../common/types/eui/in_memory_table'; import { KBN_FIELD_TYPES } from '../../../../../../common/constants/field_types'; import { Dictionary } from '../../../../../../common/types/common'; @@ -52,23 +57,6 @@ type ItemIdToExpandedRowMap = Dictionary; const CELL_CLICK_ENABLED = false; -// Defining our own ENUM here. -// EUI's SortDirection wasn't usable as a union type -// required for the Sorting interface. -enum SORT_DIRECTON { - ASC = 'asc', - DESC = 'desc', -} - -interface Sorting { - sort: { - field: string; - direction: SORT_DIRECTON.ASC | SORT_DIRECTON.DESC; - }; -} - -type TableSorting = Sorting | boolean; - interface SourceIndexPreviewTitle { indexPatternTitle: string; } @@ -301,13 +289,13 @@ export const SourceIndexPreview: React.SFC = React.memo(({ cellClick, que return column; }); - let sorting: TableSorting = false; + let sorting: SortingPropType = false; if (columns.length > 0) { sorting = { sort: { field: `_source["${selectedFields[0]}"]`, - direction: SORT_DIRECTON.ASC, + direction: SORT_DIRECTION.ASC, }, }; } diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx index 5a83cab415297d..7a85693b3c581b 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx @@ -19,10 +19,13 @@ import { EuiProgress, EuiText, EuiTitle, - SortDirection, } from '@elastic/eui'; -import { ColumnType, MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table'; +import { + ColumnType, + MlInMemoryTable, + SORT_DIRECTION, +} from '../../../../../../common/types/eui/in_memory_table'; import { dictionaryToArray } from '../../../../../../common/types/common'; import { ES_FIELD_TYPES } from '../../../../../../common/constants/field_types'; import { formatHumanReadableDateTimeSeconds } from '../../../../../util/date_utils'; @@ -271,7 +274,7 @@ export const PivotPreview: SFC = React.memo(({ aggs, groupBy, const sorting = { sort: { field: columns[0].field, - direction: SortDirection.ASC, + direction: SORT_DIRECTION.ASC, }, }; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx index 50f1570f6f60e3..0b1d741d8245d8 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx @@ -6,7 +6,8 @@ import React, { FC, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { SortDirection } from '@elastic/eui'; +import { SortDirection, SORT_DIRECTION } from '../../../../../../common/types/eui/in_memory_table'; + import { ml } from '../../../../../services/ml_api_service'; import { @@ -75,7 +76,7 @@ export const ExpandedRowPreviewPane: FC = ({ transformConfig }) => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); const [sortField, setSortField] = useState(''); - const [sortDirection, setSortDirection] = useState(SortDirection.ASC); + const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); const [isLoading, setIsLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(''); @@ -148,10 +149,10 @@ export const ExpandedRowPreviewPane: FC = ({ transformConfig }) => { const onTableChange = ({ page = { index: 0, size: 10 }, - sort = { field: columns[0].field, direction: SortDirection.ASC }, + sort = { field: columns[0].field, direction: SORT_DIRECTION.ASC }, }: { page: { index: number; size: number }; - sort: { field: string; direction: string }; + sort: { field: string; direction: SortDirection }; }) => { const { index, size } = page; setPageIndex(index); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx index a17a7824524563..0eb51ddfa7c20d 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx @@ -16,9 +16,14 @@ import { EuiEmptyPrompt, EuiPopover, EuiTitle, - SortDirection, } from '@elastic/eui'; +import { + OnTableChangeArg, + SortDirection, + SORT_DIRECTION, +} from '../../../../../../common/types/eui/in_memory_table'; + import { DataFrameTransformId, moveToDataFrameWizard } from '../../../../common'; import { checkPermission } from '../../../../../privilege/check_privilege'; import { getTaskStateBadge } from './columns'; @@ -91,7 +96,7 @@ export const DataFrameTransformList: SFC = ({ const [pageSize, setPageSize] = useState(10); const [sortField, setSortField] = useState(DataFrameTransformListColumn.id); - const [sortDirection, setSortDirection] = useState(SortDirection.ASC); + const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); const disabled = !checkPermission('canCreateDataFrame') || @@ -336,11 +341,8 @@ export const DataFrameTransformList: SFC = ({ const onTableChange = ({ page = { index: 0, size: 10 }, - sort = { field: DataFrameTransformListColumn.id, direction: SortDirection.ASC }, - }: { - page: { index: number; size: number }; - sort: { field: string; direction: string }; - }) => { + sort = { field: DataFrameTransformListColumn.id, direction: SORT_DIRECTION.ASC }, + }: OnTableChangeArg) => { const { index, size } = page; setPageIndex(index); setPageSize(size); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/common.test.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/common.test.ts new file mode 100644 index 00000000000000..91a5ba2db6908e --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/common.test.ts @@ -0,0 +1,33 @@ +/* + * 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 { DataFrameAnalyticsConfig } from '../../../../common'; + +import { getOutlierScoreFieldName } from './common'; + +describe('Data Frame Analytics: common utils', () => { + test('getOutlierScoreFieldName()', () => { + const jobConfig: DataFrameAnalyticsConfig = { + id: 'the-id', + analysis: { outlier_detection: {} }, + dest: { + index: 'the-dest-index', + results_field: 'the-results-field', + }, + source: { + index: 'the-source-index', + }, + analyzed_fields: { includes: [], excludes: [] }, + model_memory_limit: '50mb', + create_time: 1234, + version: '1.0.0', + }; + + const outlierScoreFieldName = getOutlierScoreFieldName(jobConfig); + + expect(outlierScoreFieldName).toMatch('the-results-field.outlier_score'); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/common.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/common.ts new file mode 100644 index 00000000000000..48db8e34c934e6 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/common.ts @@ -0,0 +1,12 @@ +/* + * 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 { DataFrameAnalyticsConfig } from '../../../../common'; + +const OUTLIER_SCORE = 'outlier_score'; + +export const getOutlierScoreFieldName = (jobConfig: DataFrameAnalyticsConfig) => + `${jobConfig.dest.results_field}.${OUTLIER_SCORE}`; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx index 6e73b694b21922..fa9e68d0b25d33 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx @@ -30,7 +30,13 @@ import { import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; import euiThemeDark from '@elastic/eui/dist/eui_theme_dark.json'; -import { ColumnType, MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table'; +import { + ColumnType, + MlInMemoryTable, + OnTableChangeArg, + SortingPropType, + SORT_DIRECTION, +} from '../../../../../../common/types/eui/in_memory_table'; import { useUiChromeContext } from '../../../../../contexts/ui/use_ui_chrome_context'; @@ -46,6 +52,7 @@ import { MAX_COLUMNS, } from '../../../../common'; +import { getOutlierScoreFieldName } from './common'; import { INDEX_STATUS, useExploreData } from './use_explore_data'; const customColorScaleFactory = (n: number) => (t: number) => { @@ -65,22 +72,7 @@ interface GetDataFrameAnalyticsResponse { data_frame_analytics: DataFrameAnalyticsConfig[]; } -// Defining our own ENUM here. -// EUI's SortDirection wasn't usable as a union type -// required for the Sorting interface. -enum SORT_DIRECTON { - ASC = 'asc', - DESC = 'desc', -} - -interface Sorting { - sort: { - field: string; - direction: SORT_DIRECTON.ASC | SORT_DIRECTON.DESC; - }; -} - -type TableSorting = Sorting | boolean; +const PAGE_SIZE_OPTIONS = [5, 10, 25, 50]; const ExplorationTitle: React.SFC<{ jobId: string }> = ({ jobId }) => ( @@ -100,6 +92,9 @@ interface Props { export const Exploration: FC = React.memo(({ jobId }) => { const [jobConfig, setJobConfig] = useState(undefined); + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(25); + useEffect(() => { (async function() { const analyticsConfigs: GetDataFrameAnalyticsResponse = await ml.dataFrameAnalytics.getDataFrameAnalytics( @@ -153,65 +148,26 @@ export const Exploration: FC = React.memo(({ jobId }) => { } } - const { errorMessage, status, tableItems } = useExploreData( - jobConfig, - selectedFields, - setSelectedFields - ); - - if (jobConfig === undefined) { - return null; - } - - if (status === INDEX_STATUS.ERROR) { - return ( - - - -

{errorMessage}

-
-
- ); - } - - if (status === INDEX_STATUS.LOADED && tableItems.length === 0) { - return ( - - - -

- {i18n.translate('xpack.ml.dataframe.analytics.exploration.noDataCalloutBody', { - defaultMessage: - 'The query for the index returned no results. Please make sure the index contains documents and your query is not too restrictive.', - })} -

-
-
- ); - } + const { + errorMessage, + loadExploreData, + sortField, + sortDirection, + status, + tableItems, + } = useExploreData(jobConfig, selectedFields, setSelectedFields); let docFields: EsFieldName[] = []; let docFieldsCount = 0; if (tableItems.length > 0) { - docFields = Object.keys(tableItems[0]._source); + docFields = Object.keys(tableItems[0]); docFields.sort(); docFieldsCount = docFields.length; } const columns: ColumnType[] = []; - if (selectedFields.length > 0 && tableItems.length > 0) { + if (jobConfig !== undefined && selectedFields.length > 0 && tableItems.length > 0) { // table cell color coding takes into account: // - whether the theme is dark/light // - the number of analysis features @@ -222,146 +178,222 @@ export const Exploration: FC = React.memo(({ jobId }) => { // typings for .range() incorrectly don't allow passing in a color extent. // @ts-ignore .range([d3.rgb(euiTheme.euiColorEmptyShade), d3.rgb(euiTheme.euiColorVis1)]); - const featureCount = Object.keys(tableItems[0]._source).filter(key => + const featureCount = Object.keys(tableItems[0]).filter(key => key.includes(`${jobConfig.dest.results_field}.${FEATURE_INFLUENCE}.`) ).length; const customScale = customColorScaleFactory(featureCount); const cellBgColor = (n: number) => cellBgColorScale(customScale(n)); columns.push( - ...selectedFields - .sort(sortColumns(tableItems[0]._source, jobConfig.dest.results_field)) - .map(k => { - const column: ColumnType = { - field: `_source["${k}"]`, - name: k, - sortable: true, - truncateText: true, - }; - - const render = (d: any, fullItem: EsDoc) => { - if (Array.isArray(d) && d.every(item => typeof item === 'string')) { - // If the cells data is an array of strings, return as a comma separated list. - // The list will get limited to 5 items with `…` at the end if there's more in the original array. - return `${d.slice(0, 5).join(', ')}${d.length > 5 ? ', …' : ''}`; - } else if (Array.isArray(d)) { - // If the cells data is an array of e.g. objects, display a 'array' badge with a - // tooltip that explains that this type of field is not supported in this table. - return ( - { + const column: ColumnType = { + field: k, + name: k, + sortable: true, + truncateText: true, + }; + + const render = (d: any, fullItem: EsDoc) => { + if (Array.isArray(d) && d.every(item => typeof item === 'string')) { + // If the cells data is an array of strings, return as a comma separated list. + // The list will get limited to 5 items with `…` at the end if there's more in the original array. + return `${d.slice(0, 5).join(', ')}${d.length > 5 ? ', …' : ''}`; + } else if (Array.isArray(d)) { + // If the cells data is an array of e.g. objects, display a 'array' badge with a + // tooltip that explains that this type of field is not supported in this table. + return ( + + + {i18n.translate( + 'xpack.ml.dataframe.analytics.exploration.indexArrayBadgeContent', { - defaultMessage: - 'The full content of this array based column cannot be displayed.', + defaultMessage: 'array', } )} - > - - {i18n.translate( - 'xpack.ml.dataframe.analytics.exploration.indexArrayBadgeContent', - { - defaultMessage: 'array', - } - )} - - - ); - } else if (typeof d === 'object' && d !== null) { - // If the cells data is an object, display a 'object' badge with a - // tooltip that explains that this type of field is not supported in this table. - return ( - + + ); + } else if (typeof d === 'object' && d !== null) { + // If the cells data is an object, display a 'object' badge with a + // tooltip that explains that this type of field is not supported in this table. + return ( + + + {i18n.translate( + 'xpack.ml.dataframe.analytics.exploration.indexObjectBadgeContent', { - defaultMessage: - 'The full content of this object based column cannot be displayed.', + defaultMessage: 'object', } )} - > - - {i18n.translate( - 'xpack.ml.dataframe.analytics.exploration.indexObjectBadgeContent', - { - defaultMessage: 'object', - } - )} - - - ); - } - - const split = k.split('.'); - let backgroundColor; - const color = undefined; - const resultsField = jobConfig.dest.results_field; - - if (fullItem._source[`${resultsField}.${FEATURE_INFLUENCE}.${k}`] !== undefined) { - backgroundColor = cellBgColor( - fullItem._source[`${resultsField}.${FEATURE_INFLUENCE}.${k}`] - ); - } - - if (split.length > 2 && split[0] === resultsField && split[1] === FEATURE_INFLUENCE) { - backgroundColor = cellBgColor(d); - } - - return ( -
- {d} -
+ +
); - }; + } - let columnType; + const split = k.split('.'); + let backgroundColor; + const color = undefined; + const resultsField = jobConfig.dest.results_field; + + if (fullItem[`${resultsField}.${FEATURE_INFLUENCE}.${k}`] !== undefined) { + backgroundColor = cellBgColor(fullItem[`${resultsField}.${FEATURE_INFLUENCE}.${k}`]); + } - if (tableItems.length > 0) { - columnType = typeof tableItems[0]._source[k]; + if (split.length > 2 && split[0] === resultsField && split[1] === FEATURE_INFLUENCE) { + backgroundColor = cellBgColor(d); } - if (typeof columnType !== 'undefined') { - switch (columnType) { - case 'boolean': - column.dataType = 'boolean'; - break; - case 'Date': - column.align = 'right'; - column.render = (d: any) => - formatHumanReadableDateTimeSeconds(moment(d).unix() * 1000); - break; - case 'number': - column.dataType = 'number'; - column.render = render; - break; - default: - column.render = render; - break; - } - } else { - column.render = render; + return ( +
+ {d} +
+ ); + }; + + let columnType; + + if (tableItems.length > 0) { + columnType = typeof tableItems[0][k]; + } + + if (typeof columnType !== 'undefined') { + switch (columnType) { + case 'boolean': + column.dataType = 'boolean'; + break; + case 'Date': + column.align = 'right'; + column.render = (d: any) => + formatHumanReadableDateTimeSeconds(moment(d).unix() * 1000); + break; + case 'number': + column.dataType = 'number'; + column.render = render; + break; + default: + column.render = render; + break; } + } else { + column.render = render; + } - return column; - }) + return column; + }) ); } - let sorting: TableSorting = false; + useEffect(() => { + // by default set the sorting to descending on the `outlier_score` field. + // if that's not available sort ascending on the first column. + // also check if the current sorting field is still available. + if (jobConfig !== undefined && columns.length > 0 && !selectedFields.includes(sortField)) { + const outlierScoreFieldName = getOutlierScoreFieldName(jobConfig); + const outlierScoreFieldSelected = selectedFields.includes(outlierScoreFieldName); + + const field = outlierScoreFieldSelected ? outlierScoreFieldName : selectedFields[0]; + const direction = outlierScoreFieldSelected ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC; + loadExploreData({ field, direction }); + return; + } + }, [jobConfig, columns.length, sortField, sortDirection, tableItems.length]); - if (columns.length > 0) { + let sorting: SortingPropType = false; + let onTableChange; + + if (columns.length > 0 && sortField !== '') { sorting = { sort: { - field: `_source["${selectedFields[0]}"]`, - direction: SORT_DIRECTON.ASC, + field: sortField, + direction: sortDirection, }, }; + + onTableChange = ({ + page = { index: 0, size: 10 }, + sort = { field: sortField, direction: sortDirection }, + }: OnTableChangeArg) => { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + + if (sort.field !== sortField || sort.direction !== sortDirection) { + setClearTable(true); + loadExploreData(sort); + } + }; + } + + const pagination = { + initialPageIndex: pageIndex, + initialPageSize: pageSize, + totalItemCount: tableItems.length, + pageSizeOptions: PAGE_SIZE_OPTIONS, + hidePerPageOptions: false, + }; + + if (jobConfig === undefined) { + return null; + } + + if (status === INDEX_STATUS.ERROR) { + return ( + + + +

{errorMessage}

+
+
+ ); + } + + if (status === INDEX_STATUS.LOADED && tableItems.length === 0) { + return ( + + + +

+ {i18n.translate('xpack.ml.dataframe.analytics.exploration.noDataCalloutBody', { + defaultMessage: + 'The query for the index returned no results. Please make sure the index contains documents and your query is not too restrictive.', + })} +

+
+
+ ); } return ( @@ -433,7 +465,7 @@ export const Exploration: FC = React.memo(({ jobId }) => { {status !== INDEX_STATUS.LOADING && ( )} - {clearTable === false && columns.length > 0 && ( + {clearTable === false && columns.length > 0 && sortField !== '' && ( = React.memo(({ jobId }) => { hasActions={false} isSelectable={false} items={tableItems} - pagination={{ - initialPageSize: 25, - pageSizeOptions: [5, 10, 25, 50], - }} + onTableChange={onTableChange} + pagination={pagination} responsive={false} sorting={sorting} /> diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts index 4d94f360f9d69d..800640f01daa9f 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts @@ -8,6 +8,8 @@ import React, { useEffect, useState } from 'react'; import { SearchResponse } from 'elasticsearch'; +import { SortDirection, SORT_DIRECTION } from '../../../../../../common/types/eui/in_memory_table'; + import { ml } from '../../../../../services/ml_api_service'; import { getNestedProperty } from '../../../../../util/object_utils'; @@ -15,11 +17,11 @@ import { getDefaultSelectableFields, getFlattenedFields, DataFrameAnalyticsConfig, - EsDoc, - EsDocSource, EsFieldName, } from '../../../../common'; +import { getOutlierScoreFieldName } from './common'; + const SEARCH_SIZE = 1000; export enum INDEX_STATUS { @@ -29,10 +31,19 @@ export enum INDEX_STATUS { ERROR, } +type TableItem = Record; + +interface LoadExploreDataArg { + field: string; + direction: SortDirection; +} export interface UseExploreDataReturnType { errorMessage: string; + loadExploreData: (arg: LoadExploreDataArg) => void; + sortField: EsFieldName; + sortDirection: SortDirection; status: INDEX_STATUS; - tableItems: EsDoc[]; + tableItems: TableItem[]; } export const useExploreData = ( @@ -42,89 +53,98 @@ export const useExploreData = ( ): UseExploreDataReturnType => { const [errorMessage, setErrorMessage] = useState(''); const [status, setStatus] = useState(INDEX_STATUS.UNUSED); - const [tableItems, setTableItems] = useState([]); - - useEffect(() => { - (async () => { - if (jobConfig !== undefined) { - setErrorMessage(''); - setStatus(INDEX_STATUS.LOADING); - - try { - const resultsField = jobConfig.dest.results_field; - - const resp: SearchResponse = await ml.esSearch({ - index: jobConfig.dest.index, - size: SEARCH_SIZE, - body: { - query: { match_all: {} }, - sort: [ - { - [`${resultsField}.outlier_score`]: { - order: 'desc', - }, + const [tableItems, setTableItems] = useState([]); + const [sortField, setSortField] = useState(''); + const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); + + const loadExploreData = async ({ field, direction }: LoadExploreDataArg) => { + if (jobConfig !== undefined) { + setErrorMessage(''); + setStatus(INDEX_STATUS.LOADING); + + try { + const resultsField = jobConfig.dest.results_field; + + const resp: SearchResponse = await ml.esSearch({ + index: jobConfig.dest.index, + size: SEARCH_SIZE, + body: { + query: { match_all: {} }, + sort: [ + { + [field]: { + order: direction, }, - ], - }, - }); + }, + ], + }, + }); - const docs = resp.hits.hits; - - if (docs.length === 0) { - setTableItems([]); - setStatus(INDEX_STATUS.LOADED); - return; - } - - if (selectedFields.length === 0) { - const newSelectedFields = getDefaultSelectableFields(docs, resultsField); - setSelectedFields(newSelectedFields); - } - - // Create a version of the doc's source with flattened field names. - // This avoids confusion later on if a field name has dots in its name - // or is a nested fields when displaying it via EuiInMemoryTable. - const flattenedFields = getFlattenedFields(docs[0]._source, resultsField); - const transformedTableItems = docs.map(doc => { - const item: EsDocSource = {}; - flattenedFields.forEach(ff => { - item[ff] = getNestedProperty(doc._source, ff); - if (item[ff] === undefined) { - // If the attribute is undefined, it means it was not a nested property - // but had dots in its actual name. This selects the property by its - // full name and assigns it to `item[ff]`. - item[ff] = doc._source[`"${ff}"`]; - } - if (item[ff] === undefined) { - const parts = ff.split('.'); - if (parts[0] === resultsField && parts.length >= 2) { - parts.shift(); - if (doc._source[resultsField] !== undefined) { - item[ff] = doc._source[resultsField][parts.join('.')]; - } + setSortField(field); + setSortDirection(direction); + + const docs = resp.hits.hits; + + if (docs.length === 0) { + setTableItems([]); + setStatus(INDEX_STATUS.LOADED); + return; + } + + if (selectedFields.length === 0) { + const newSelectedFields = getDefaultSelectableFields(docs, resultsField); + setSelectedFields(newSelectedFields); + } + + // Create a version of the doc's source with flattened field names. + // This avoids confusion later on if a field name has dots in its name + // or is a nested fields when displaying it via EuiInMemoryTable. + const flattenedFields = getFlattenedFields(docs[0]._source, resultsField); + const transformedTableItems = docs.map(doc => { + const item: TableItem = {}; + flattenedFields.forEach(ff => { + item[ff] = getNestedProperty(doc._source, ff); + if (item[ff] === undefined) { + // If the attribute is undefined, it means it was not a nested property + // but had dots in its actual name. This selects the property by its + // full name and assigns it to `item[ff]`. + item[ff] = doc._source[`"${ff}"`]; + } + if (item[ff] === undefined) { + const parts = ff.split('.'); + if (parts[0] === resultsField && parts.length >= 2) { + parts.shift(); + if (doc._source[resultsField] !== undefined) { + item[ff] = doc._source[resultsField][parts.join('.')]; } } - }); - return { - ...doc, - _source: item, - }; + } }); - - setTableItems(transformedTableItems); - setStatus(INDEX_STATUS.LOADED); - } catch (e) { - if (e.message !== undefined) { - setErrorMessage(e.message); - } else { - setErrorMessage(JSON.stringify(e)); - } - setTableItems([]); - setStatus(INDEX_STATUS.ERROR); + return item; + }); + + setTableItems(transformedTableItems); + setStatus(INDEX_STATUS.LOADED); + } catch (e) { + if (e.message !== undefined) { + setErrorMessage(e.message); + } else { + setErrorMessage(JSON.stringify(e)); } + setTableItems([]); + setStatus(INDEX_STATUS.ERROR); } - })(); + } + }; + + useEffect(() => { + if (jobConfig !== undefined) { + loadExploreData({ + field: getOutlierScoreFieldName(jobConfig), + direction: SORT_DIRECTION.DESC, + }); + } }, [jobConfig && jobConfig.id]); - return { errorMessage, status, tableItems }; + return { errorMessage, loadExploreData, sortField, sortDirection, status, tableItems }; }; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 24819399ae2b72..de87ca71e51818 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -13,9 +13,14 @@ import { EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt, - SortDirection, } from '@elastic/eui'; +import { + OnTableChangeArg, + SortDirection, + SORT_DIRECTION, +} from '../../../../../../common/types/eui/in_memory_table'; + import { DataFrameAnalyticsId, useRefreshAnalyticsList } from '../../../../common'; import { checkPermission } from '../../../../../privilege/check_privilege'; import { getTaskStateBadge } from './columns'; @@ -85,7 +90,7 @@ export const DataFrameAnalyticsList: FC = ({ const [pageSize, setPageSize] = useState(10); const [sortField, setSortField] = useState(DataFrameAnalyticsListColumn.id); - const [sortDirection, setSortDirection] = useState(SortDirection.ASC); + const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); const disabled = !checkPermission('canCreateDataFrameAnalytics') || @@ -291,11 +296,8 @@ export const DataFrameAnalyticsList: FC = ({ const onTableChange = ({ page = { index: 0, size: 10 }, - sort = { field: DataFrameAnalyticsListColumn.id, direction: SortDirection.ASC }, - }: { - page: { index: number; size: number }; - sort: { field: string; direction: string }; - }) => { + sort = { field: DataFrameAnalyticsListColumn.id, direction: SORT_DIRECTION.ASC }, + }: OnTableChangeArg) => { const { index, size } = page; setPageIndex(index); setPageSize(size);