diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts index b1eedc1378d4..f910b8ea8a23 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts @@ -33,6 +33,16 @@ interface RegressionAnalysis { export const SEARCH_SIZE = 1000; +export const defaultSearchQuery = { + match_all: {}, +}; + +export interface SearchQuery { + track_total_hits?: boolean; + query: SavedSearchQuery; + sort?: any; +} + export enum INDEX_STATUS { UNUSED, LOADING, diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/index.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/index.ts index 112f828f9897..02a1c30259cc 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/index.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/index.ts @@ -24,6 +24,8 @@ export { getPredictedFieldName, INDEX_STATUS, SEARCH_SIZE, + defaultSearchQuery, + SearchQuery, } from './analytics'; export { 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 11bb62dec162..469ae164cef3 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 @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect, useState } from 'react'; +import React, { FC, Fragment, useEffect, useState } from 'react'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; @@ -18,13 +18,16 @@ import { EuiCheckbox, EuiFlexGroup, EuiFlexItem, + EuiFormRow, EuiPanel, EuiPopover, EuiPopoverTitle, EuiProgress, + EuiSpacer, EuiText, EuiTitle, EuiToolTip, + Query, } from '@elastic/eui'; import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; @@ -51,12 +54,18 @@ import { EsDoc, MAX_COLUMNS, INDEX_STATUS, + SEARCH_SIZE, + defaultSearchQuery, } from '../../../../common'; import { getOutlierScoreFieldName } from './common'; import { useExploreData } from './use_explore_data'; -import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; +import { + DATA_FRAME_TASK_STATE, + Query as QueryType, +} from '../../../analytics_management/components/analytics_list/common'; import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns'; +import { SavedSearchQuery } from '../../../../../contexts/kibana'; const customColorScaleFactory = (n: number) => (t: number) => { if (t < 1 / n) { @@ -99,6 +108,10 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(25); + const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); + const [searchError, setSearchError] = useState(undefined); + const [searchString, setSearchString] = useState(undefined); + useEffect(() => { (async function() { const analyticsConfigs: GetDataFrameAnalyticsResponse = await ml.dataFrameAnalytics.getDataFrameAnalytics( @@ -119,23 +132,9 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { ? euiThemeDark : euiThemeLight; - const [clearTable, setClearTable] = useState(false); - const [selectedFields, setSelectedFields] = useState([] as EsFieldName[]); const [isColumnsPopoverVisible, setColumnsPopoverVisible] = useState(false); - // EuiInMemoryTable has an issue with dynamic sortable columns - // and will trigger a full page Kibana error in such a case. - // The following is a workaround until this is solved upstream: - // - If the sortable/columns config changes, - // the table will be unmounted/not rendered. - // This is what setClearTable(true) in toggleColumn() does. - // - After that on next render it gets re-enabled. To make sure React - // doesn't consolidate the state updates, setTimeout is used. - if (clearTable) { - setTimeout(() => setClearTable(false), 0); - } - function toggleColumnsPopover() { setColumnsPopoverVisible(!isColumnsPopoverVisible); } @@ -146,7 +145,6 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { function toggleColumn(column: EsFieldName) { if (tableItems.length > 0 && jobConfig !== undefined) { - setClearTable(true); // spread to a new array otherwise the component wouldn't re-render setSelectedFields([...toggleSelectedField(selectedFields, column)]); } @@ -309,6 +307,17 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { ); } + useEffect(() => { + if (jobConfig !== undefined) { + 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, searchQuery }); + } + }, [JSON.stringify(searchQuery)]); + useEffect(() => { // by default set the sorting to descending on the `outlier_score` field. // if that's not available sort ascending on the first column. @@ -319,7 +328,7 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { const field = outlierScoreFieldSelected ? outlierScoreFieldName : selectedFields[0]; const direction = outlierScoreFieldSelected ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC; - loadExploreData({ field, direction }); + loadExploreData({ field, direction, searchQuery }); return; } }, [jobConfig, columns.length, sortField, sortDirection, tableItems.length]); @@ -344,8 +353,7 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { setPageSize(size); if (sort.field !== sortField || sort.direction !== sortDirection) { - setClearTable(true); - loadExploreData(sort); + loadExploreData({ ...sort, searchQuery }); } }; } @@ -358,11 +366,37 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { hidePerPageOptions: false, }; + const onQueryChange = ({ query, error }: { query: QueryType; error: any }) => { + if (error) { + setSearchError(error.message); + } else { + try { + const esQueryDsl = Query.toESQuery(query); + setSearchQuery(esQueryDsl); + setSearchString(query.text); + setSearchError(undefined); + } catch (e) { + setSearchError(e.toString()); + } + } + }; + + const search = { + onChange: onQueryChange, + defaultQuery: searchString, + box: { + incremental: false, + placeholder: i18n.translate('xpack.ml.dataframe.analytics.exploration.searchBoxPlaceholder', { + defaultMessage: 'E.g. avg>0.5', + }), + }, + }; + if (jobConfig === undefined) { return null; } - - if (status === INDEX_STATUS.ERROR) { + // 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')) { return ( @@ -379,32 +413,16 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { ); } - if (status === INDEX_STATUS.LOADED && tableItems.length === 0) { - return ( - - - - - - - {getTaskStateBadge(jobStatus)} - - - -

- {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.', - })} -

-
-
- ); + let tableError = + status === INDEX_STATUS.ERROR && errorMessage.includes('parsing_exception') + ? errorMessage + : searchError; + + if (status === INDEX_STATUS.LOADED && tableItems.length === 0 && tableError === undefined) { + tableError = 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 ( @@ -483,20 +501,38 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { {status !== INDEX_STATUS.LOADING && ( )} - {clearTable === false && columns.length > 0 && sortField !== '' && ( - + {(columns.length > 0 || searchQuery !== defaultSearchQuery) && sortField !== '' && ( + + {tableItems.length === SEARCH_SIZE && ( + + + + )} + + + )}
); 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 2a07bc1251a3..a0728e0bae44 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 @@ -20,16 +20,21 @@ import { EsFieldName, INDEX_STATUS, SEARCH_SIZE, + defaultSearchQuery, + SearchQuery, } from '../../../../common'; import { getOutlierScoreFieldName } from './common'; +import { SavedSearchQuery } from '../../../../../contexts/kibana'; type TableItem = Record; interface LoadExploreDataArg { field: string; direction: SortDirection; + searchQuery: SavedSearchQuery; } + export interface UseExploreDataReturnType { errorMessage: string; loadExploreData: (arg: LoadExploreDataArg) => void; @@ -50,7 +55,7 @@ export const useExploreData = ( const [sortField, setSortField] = useState(''); const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); - const loadExploreData = async ({ field, direction }: LoadExploreDataArg) => { + const loadExploreData = async ({ field, direction, searchQuery }: LoadExploreDataArg) => { if (jobConfig !== undefined) { setErrorMessage(''); setStatus(INDEX_STATUS.LOADING); @@ -58,19 +63,24 @@ export const useExploreData = ( try { const resultsField = jobConfig.dest.results_field; + const body: SearchQuery = { + query: searchQuery, + }; + + if (field !== undefined) { + body.sort = [ + { + [field]: { + order: direction, + }, + }, + ]; + } + const resp: SearchResponse = await ml.esSearch({ index: jobConfig.dest.index, size: SEARCH_SIZE, - body: { - query: { match_all: {} }, - sort: [ - { - [field]: { - order: direction, - }, - }, - ], - }, + body, }); setSortField(field); @@ -135,6 +145,7 @@ export const useExploreData = ( loadExploreData({ field: getOutlierScoreFieldName(jobConfig), direction: SORT_DIRECTION.DESC, + searchQuery: defaultSearchQuery, }); } }, [jobConfig && jobConfig.id]); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx index 8bb44da74087..d877ed40e587 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx @@ -25,8 +25,8 @@ import { getEvalQueryBody, isRegressionResultsSearchBoolQuery, RegressionResultsSearchQuery, + SearchQuery, } from '../../../../common/analytics'; -import { SearchQuery } from './use_explore_data'; interface Props { jobConfig: DataFrameAnalyticsConfig; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx index b188334934ae..1fdababda646 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx @@ -12,8 +12,7 @@ import { DataFrameAnalyticsConfig } from '../../../../common'; import { EvaluatePanel } from './evaluate_panel'; import { ResultsTable } from './results_table'; import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; -import { defaultSearchQuery } from './use_explore_data'; -import { RegressionResultsSearchQuery } from '../../../../common/analytics'; +import { RegressionResultsSearchQuery, defaultSearchQuery } from '../../../../common/analytics'; interface GetDataFrameAnalyticsResponse { count: number; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx index a1d4261d2cf3..ec504492e0a5 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx @@ -50,11 +50,12 @@ import { getPredictedFieldName, INDEX_STATUS, SEARCH_SIZE, + defaultSearchQuery, } from '../../../../common'; import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns'; import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; -import { useExploreData, defaultSearchQuery } from './use_explore_data'; +import { useExploreData } from './use_explore_data'; import { ExplorationTitle } from './regression_exploration'; const PAGE_SIZE_OPTIONS = [5, 10, 25, 50]; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts index 332451c6e4d7..bf3565abd8de 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts @@ -27,12 +27,10 @@ import { getPredictedFieldName, INDEX_STATUS, SEARCH_SIZE, + defaultSearchQuery, + SearchQuery, } from '../../../../common'; -export const defaultSearchQuery = { - match_all: {}, -}; - type TableItem = Record; interface LoadExploreDataArg { @@ -49,12 +47,6 @@ export interface UseExploreDataReturnType { tableItems: TableItem[]; } -export interface SearchQuery { - track_total_hits?: boolean; - query: SavedSearchQuery; - sort?: any; -} - export const useExploreData = ( jobConfig: DataFrameAnalyticsConfig | undefined, selectedFields: EsFieldName[], diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index cb4f0790e831..fdb9c40d3c60 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7750,7 +7750,6 @@ "xpack.ml.dataframe.analytics.exploration.indexObjectToolTipContent": "无法显示此基于对象的列的完整内容。", "xpack.ml.dataframe.analytics.exploration.jobIdTitle": "作业 ID {jobId}", "xpack.ml.dataframe.analytics.exploration.noDataCalloutBody": "该索引的查询未返回结果。请确保索引包含文档且您的查询限制不过于严格。", - "xpack.ml.dataframe.analytics.exploration.noDataCalloutTitle": "空的索引查询结果。", "xpack.ml.dataframe.analytics.exploration.selectColumnsAriaLabel": "选择列", "xpack.ml.dataframe.analytics.exploration.selectFieldsPopoverTitle": "选择字段", "xpack.ml.dataframe.analytics.exploration.title": "分析浏览",