- {i18n.translate('xpack.ml.dataframe.analytics.regressionExploration.noIndexCalloutBody', {
+ {i18n.translate('xpack.ml.dataframe.analytics.errorCallout.noIndexCalloutBody', {
defaultMessage:
'The query for the index returned no results. Please make sure the destination index exists and contains documents.',
})}
@@ -46,16 +46,13 @@ export const ErrorCallout: FC = ({ error }) => {
// Job was started but no results have been written yet
errorCallout = (
- {i18n.translate('xpack.ml.dataframe.analytics.regressionExploration.noDataCalloutBody', {
+ {i18n.translate('xpack.ml.dataframe.analytics.errorCallout.noDataCalloutBody', {
defaultMessage:
'The query for the index returned no results. Please make sure the job has completed and the index contains documents.',
})}
@@ -66,22 +63,16 @@ export const ErrorCallout: FC = ({ error }) => {
// query bar syntax is incorrect
errorCallout = (
- {i18n.translate(
- 'xpack.ml.dataframe.analytics.regressionExploration.queryParsingErrorBody',
- {
- defaultMessage:
- 'The query syntax is invalid and returned no results. Please check the query syntax and try again.',
- }
- )}
+ {i18n.translate('xpack.ml.dataframe.analytics.errorCallout.queryParsingErrorBody', {
+ defaultMessage:
+ 'The query syntax is invalid and returned no results. Please check the query syntax and try again.',
+ })}
);
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_data_grid/exploration_data_grid.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_data_grid/exploration_data_grid.tsx
deleted file mode 100644
index e88bc1cd06f95..0000000000000
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_data_grid/exploration_data_grid.tsx
+++ /dev/null
@@ -1,161 +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, { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react';
-
-import { i18n } from '@kbn/i18n';
-
-import { EuiDataGrid, EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui';
-
-import { euiDataGridStyle, euiDataGridToolbarSettings } from '../../../../common';
-
-import { mlFieldFormatService } from '../../../../../services/field_format_service';
-
-import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
-
-const FEATURE_INFLUENCE = 'feature_influence';
-const PAGE_SIZE_OPTIONS = [5, 10, 25, 50];
-
-type Pagination = Pick;
-type TableItem = Record;
-
-interface ExplorationDataGridProps {
- colorRange: (d: number) => string;
- columns: any[];
- indexPattern: IndexPattern;
- pagination: Pagination;
- resultsField: string;
- rowCount: number;
- selectedFields: string[];
- setPagination: Dispatch>;
- setSelectedFields: Dispatch>;
- setSortingColumns: Dispatch>;
- sortingColumns: EuiDataGridSorting['columns'];
- tableItems: TableItem[];
-}
-
-export const ExplorationDataGrid: FC = ({
- colorRange,
- columns,
- indexPattern,
- pagination,
- resultsField,
- rowCount,
- selectedFields,
- setPagination,
- setSelectedFields,
- setSortingColumns,
- sortingColumns,
- tableItems,
-}) => {
- const renderCellValue = useMemo(() => {
- return ({
- rowIndex,
- columnId,
- setCellProps,
- }: {
- rowIndex: number;
- columnId: string;
- setCellProps: any;
- }) => {
- const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize;
-
- const fullItem = tableItems[adjustedRowIndex];
-
- if (fullItem === undefined) {
- return null;
- }
-
- let format: any;
-
- if (indexPattern !== undefined) {
- format = mlFieldFormatService.getFieldFormatFromIndexPattern(indexPattern, columnId, '');
- }
-
- const cellValue =
- fullItem.hasOwnProperty(columnId) && fullItem[columnId] !== undefined
- ? fullItem[columnId]
- : null;
-
- const split = columnId.split('.');
- let backgroundColor;
-
- // column with feature values get color coded by its corresponding influencer value
- if (fullItem[`${resultsField}.${FEATURE_INFLUENCE}.${columnId}`] !== undefined) {
- backgroundColor = colorRange(fullItem[`${resultsField}.${FEATURE_INFLUENCE}.${columnId}`]);
- }
-
- // column with influencer values get color coded by its own value
- if (split.length > 2 && split[0] === resultsField && split[1] === FEATURE_INFLUENCE) {
- backgroundColor = colorRange(cellValue);
- }
-
- if (backgroundColor !== undefined) {
- setCellProps({
- style: { backgroundColor },
- });
- }
-
- if (format !== undefined) {
- return format.convert(cellValue, 'text');
- }
-
- if (typeof cellValue === 'string' || cellValue === null) {
- return cellValue;
- }
-
- if (typeof cellValue === 'boolean') {
- return cellValue ? 'true' : 'false';
- }
-
- if (typeof cellValue === 'object' && cellValue !== null) {
- return JSON.stringify(cellValue);
- }
-
- return cellValue;
- };
- }, [resultsField, rowCount, tableItems, pagination.pageIndex, pagination.pageSize]);
-
- const onChangeItemsPerPage = useCallback(
- pageSize => {
- setPagination(p => {
- const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize);
- return { pageIndex, pageSize };
- });
- },
- [setPagination]
- );
-
- const onChangePage = useCallback(pageIndex => setPagination(p => ({ ...p, pageIndex })), [
- setPagination,
- ]);
-
- const onSort = useCallback(sc => setSortingColumns(sc), [setSortingColumns]);
-
- return (
-
- );
-};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx
new file mode 100644
index 0000000000000..1986c486974c9
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.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, { FC, useState } from 'react';
+
+import { EuiSpacer } from '@elastic/eui';
+
+import { useResultsViewConfig, DataFrameAnalyticsConfig } from '../../../../common';
+import { ResultsSearchQuery, defaultSearchQuery } from '../../../../common/analytics';
+
+import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
+
+import { ExplorationResultsTable } from '../exploration_results_table';
+import { JobConfigErrorCallout } from '../job_config_error_callout';
+import { LoadingPanel } from '../loading_panel';
+
+export interface EvaluatePanelProps {
+ jobConfig: DataFrameAnalyticsConfig;
+ jobStatus?: DATA_FRAME_TASK_STATE;
+ searchQuery: ResultsSearchQuery;
+}
+
+interface Props {
+ jobId: string;
+ title: string;
+ EvaluatePanel: FC;
+}
+
+export const ExplorationPageWrapper: FC = ({ jobId, title, EvaluatePanel }) => {
+ const {
+ indexPattern,
+ isInitialized,
+ isLoadingJobConfig,
+ jobCapsServiceErrorMessage,
+ jobConfig,
+ jobConfigErrorMessage,
+ jobStatus,
+ } = useResultsViewConfig(jobId);
+ const [searchQuery, setSearchQuery] = useState(defaultSearchQuery);
+
+ if (jobConfigErrorMessage !== undefined || jobCapsServiceErrorMessage !== undefined) {
+ return (
+
+ );
+ }
+
+ return (
+ <>
+ {isLoadingJobConfig === true && jobConfig === undefined && }
+ {isLoadingJobConfig === false && jobConfig !== undefined && isInitialized === true && (
+
+ )}
+
+ {isLoadingJobConfig === true && jobConfig === undefined && }
+ {isLoadingJobConfig === false &&
+ jobConfig !== undefined &&
+ indexPattern !== undefined &&
+ isInitialized === true && (
+
+ )}
+ >
+ );
+};
diff --git a/x-pack/plugins/ml/server/lib/check_privileges/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/index.ts
similarity index 73%
rename from x-pack/plugins/ml/server/lib/check_privileges/index.ts
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/index.ts
index 67b435116aa00..bf294a3cd08c9 100644
--- a/x-pack/plugins/ml/server/lib/check_privileges/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { privilegesProvider, MlCapabilities } from './check_privileges';
+export { EvaluatePanelProps, ExplorationPageWrapper } from './exploration_page_wrapper';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_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
similarity index 57%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx
index bf63dfe68fe9e..24e5785c6e808 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_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
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Fragment, FC, useEffect } from 'react';
+import React, { Fragment, FC, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiCallOut,
@@ -12,17 +12,15 @@ import {
EuiFlexItem,
EuiFormRow,
EuiPanel,
- EuiProgress,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
-import {
- BASIC_NUMERICAL_TYPES,
- EXTENDED_NUMERICAL_TYPES,
- sortRegressionResultsFields,
-} from '../../../../common/fields';
+
+import { DataGrid } from '../../../../../components/data_grid';
+import { SavedSearchQuery } from '../../../../../contexts/ml';
+import { getToastNotifications } from '../../../../../util/dependency_cache';
import {
DataFrameAnalyticsConfig,
@@ -33,20 +31,20 @@ import {
} 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 } from './use_explore_data'; // TableItem
-import { ExplorationTitle } from './classification_exploration';
-import { ClassificationExplorationDataGrid } from './classification_exploration_data_grid';
+import { ExplorationTitle } from '../exploration_title';
import { ExplorationQueryBar } from '../exploration_query_bar';
+import { useExplorationResults } from './use_exploration_results';
+
const showingDocs = i18n.translate(
- 'xpack.ml.dataframe.analytics.classificationExploration.documentsShownHelpText',
+ 'xpack.ml.dataframe.analytics.explorationResults.documentsShownHelpText',
{
defaultMessage: 'Showing documents for which predictions exist',
}
);
const showingFirstDocs = i18n.translate(
- 'xpack.ml.dataframe.analytics.classificationExploration.firstDocumentsShownHelpText',
+ 'xpack.ml.dataframe.analytics.explorationResults.firstDocumentsShownHelpText',
{
defaultMessage: 'Showing first {searchSize} documents for which predictions exist',
values: { searchSize: SEARCH_SIZE },
@@ -58,71 +56,22 @@ interface Props {
jobConfig: DataFrameAnalyticsConfig;
jobStatus?: DATA_FRAME_TASK_STATE;
setEvaluateSearchQuery: React.Dispatch>;
+ title: string;
}
-export const ResultsTable: FC = React.memo(
- ({ indexPattern, jobConfig, jobStatus, setEvaluateSearchQuery }) => {
- const needsDestIndexFields = indexPattern && indexPattern.title === jobConfig.source.index[0];
- const resultsField = jobConfig.dest.results_field;
- const {
- errorMessage,
- fieldTypes,
- pagination,
- searchQuery,
- selectedFields,
- rowCount,
- setPagination,
- setSearchQuery,
- setSelectedFields,
- setSortingColumns,
- sortingColumns,
- status,
- tableFields,
- tableItems,
- } = useExploreData(jobConfig, needsDestIndexFields);
+export const ExplorationResultsTable: FC = React.memo(
+ ({ indexPattern, jobConfig, jobStatus, setEvaluateSearchQuery, title }) => {
+ const [searchQuery, setSearchQuery] = useState(defaultSearchQuery);
useEffect(() => {
setEvaluateSearchQuery(searchQuery);
}, [JSON.stringify(searchQuery)]);
- const columns = tableFields
- .sort((a: any, b: any) => sortRegressionResultsFields(a, b, jobConfig))
- .map((field: any) => {
- // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json']
- // To fall back to the default string schema it needs to be undefined.
- let schema;
- let isSortable = true;
- const type = fieldTypes[field];
- const isNumber =
- type !== undefined &&
- (BASIC_NUMERICAL_TYPES.has(type) || EXTENDED_NUMERICAL_TYPES.has(type));
-
- if (isNumber) {
- schema = 'numeric';
- }
-
- switch (type) {
- case 'date':
- schema = 'datetime';
- break;
- case 'geo_point':
- schema = 'json';
- break;
- case 'boolean':
- schema = 'boolean';
- break;
- }
+ const classificationData = useExplorationResults(indexPattern, jobConfig, searchQuery);
+ const docFieldsCount = classificationData.columns.length;
+ const { columns, errorMessage, status, tableItems, visibleColumns } = classificationData;
- if (field === `${resultsField}.feature_importance`) {
- isSortable = false;
- }
-
- return { id: field, schema, isSortable };
- });
-
- const docFieldsCount = tableFields.length;
-
- if (jobConfig === undefined) {
+ if (jobConfig === undefined || classificationData === undefined) {
return null;
}
// if it's a searchBar syntax error leave the table visible so they can try again
@@ -131,7 +80,7 @@ export const ResultsTable: FC = React.memo(
-
+
{jobStatus !== undefined && (
@@ -156,13 +105,13 @@ export const ResultsTable: FC = React.memo(
-
+
{jobStatus !== undefined && (
@@ -177,11 +126,11 @@ export const ResultsTable: FC = React.memo(
{docFieldsCount > MAX_COLUMNS && (
{i18n.translate(
- 'xpack.ml.dataframe.analytics.classificationExploration.fieldSelection',
+ 'xpack.ml.dataframe.analytics.explorationResults.fieldSelection',
{
defaultMessage:
'{selectedFieldsLength, number} of {docFieldsCount, number} {docFieldsCount, plural, one {field} other {fields}} selected',
- values: { selectedFieldsLength: selectedFields.length, docFieldsCount },
+ values: { selectedFieldsLength: visibleColumns.length, docFieldsCount },
}
)}
@@ -190,10 +139,6 @@ export const ResultsTable: FC = React.memo(
- {status === INDEX_STATUS.LOADING && }
- {status !== INDEX_STATUS.LOADING && (
-
- )}
{(columns.length > 0 || searchQuery !== defaultSearchQuery) && (
@@ -213,18 +158,10 @@ export const ResultsTable: FC = React.memo(
-
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/index.ts
new file mode 100644
index 0000000000000..19308640c8b02
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { ExplorationResultsTable } from './exploration_results_table';
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
new file mode 100644
index 0000000000000..6f9dc694d8172
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts
@@ -0,0 +1,72 @@
+/*
+ * 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 { useEffect } from 'react';
+
+import { EuiDataGridColumn } from '@elastic/eui';
+
+import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
+
+import {
+ getDataGridSchemasFromFieldTypes,
+ useDataGrid,
+ useRenderCellValue,
+ UseIndexDataReturnType,
+} from '../../../../../components/data_grid';
+import { SavedSearchQuery } from '../../../../../contexts/ml';
+
+import { getIndexData, getIndexFields, DataFrameAnalyticsConfig } from '../../../../common';
+import { DEFAULT_RESULTS_FIELD, FEATURE_IMPORTANCE } from '../../../../common/constants';
+import { sortExplorationResultsFields, ML__ID_COPY } from '../../../../common/fields';
+
+export const useExplorationResults = (
+ indexPattern: IndexPattern | undefined,
+ jobConfig: DataFrameAnalyticsConfig | undefined,
+ searchQuery: SavedSearchQuery
+): UseIndexDataReturnType => {
+ const needsDestIndexFields =
+ indexPattern !== undefined && indexPattern.title === jobConfig?.source.index[0];
+
+ const columns: EuiDataGridColumn[] = [];
+
+ if (jobConfig !== undefined) {
+ const resultsField = jobConfig.dest.results_field;
+ const { fieldTypes } = getIndexFields(jobConfig, needsDestIndexFields);
+ columns.push(
+ ...getDataGridSchemasFromFieldTypes(fieldTypes, resultsField).sort((a: any, b: any) =>
+ sortExplorationResultsFields(a.id, b.id, jobConfig)
+ )
+ );
+ }
+
+ const dataGrid = useDataGrid(
+ columns,
+ 25,
+ // reduce default selected rows from 20 to 8 for performance reasons.
+ 8,
+ // by default, hide feature-importance columns and the doc id copy
+ d => !d.includes(`.${FEATURE_IMPORTANCE}.`) && d !== ML__ID_COPY
+ );
+
+ useEffect(() => {
+ getIndexData(jobConfig, dataGrid, searchQuery);
+ // custom comparison
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]);
+
+ const renderCellValue = useRenderCellValue(
+ indexPattern,
+ dataGrid.pagination,
+ dataGrid.tableItems,
+ jobConfig?.dest.results_field ?? DEFAULT_RESULTS_FIELD
+ );
+
+ return {
+ ...dataGrid,
+ columns,
+ renderCellValue,
+ };
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/exploration_title.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/exploration_title.tsx
new file mode 100644
index 0000000000000..f06c88c73df71
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/exploration_title.tsx
@@ -0,0 +1,15 @@
+/*
+ * 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, { FC } from 'react';
+
+import { EuiTitle } from '@elastic/eui';
+
+export const ExplorationTitle: FC<{ title: string }> = ({ title }) => (
+
+ {title}
+
+);
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/index.ts
similarity index 79%
rename from x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/index.ts
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/index.ts
index a13e678813a00..b34e61b3b5e76 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_title/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { SourceIndexPreview } from './source_index_preview';
+export { ExplorationTitle } from './exploration_title';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/index.ts
similarity index 78%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/index.ts
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/index.ts
index dd896ca02f7f7..a5991f4325d12 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { useExploreData, TableItem } from './use_explore_data';
+export { JobConfigErrorCallout } from './job_config_error_callout';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx
new file mode 100644
index 0000000000000..945d6654067c0
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/job_config_error_callout/job_config_error_callout.tsx
@@ -0,0 +1,47 @@
+/*
+ * 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, { FC } from 'react';
+
+import { EuiCallOut, EuiPanel, EuiSpacer } from '@elastic/eui';
+
+import { i18n } from '@kbn/i18n';
+
+import { ExplorationTitle } from '../exploration_title';
+
+const jobConfigErrorTitle = i18n.translate('xpack.ml.dataframe.analytics.jobConfig.errorTitle', {
+ defaultMessage: 'Unable to fetch results. An error occurred loading the job configuration data.',
+});
+
+const jobCapsErrorTitle = i18n.translate('xpack.ml.dataframe.analytics.jobCaps.errorTitle', {
+ defaultMessage: "Unable to fetch results. An error occurred loading the index's field data.",
+});
+
+interface Props {
+ jobCapsServiceErrorMessage: string | undefined;
+ jobConfigErrorMessage: string | undefined;
+ title: string;
+}
+
+export const JobConfigErrorCallout: FC = ({
+ jobCapsServiceErrorMessage,
+ jobConfigErrorMessage,
+ title,
+}) => {
+ return (
+
+
+
+
+ {jobConfigErrorMessage ? jobConfigErrorMessage : jobCapsServiceErrorMessage}
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/common.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.test.ts
similarity index 100%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/common.test.ts
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.test.ts
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts
similarity index 51%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/common.ts
rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts
index 48db8e34c934e..bfd3dd33995aa 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/common.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/common.ts
@@ -4,9 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { DataFrameAnalyticsConfig } from '../../../../common';
+import { DataGridItem } from '../../../../../components/data_grid';
-const OUTLIER_SCORE = 'outlier_score';
+import { DataFrameAnalyticsConfig } from '../../../../common';
+import { FEATURE_INFLUENCE, OUTLIER_SCORE } from '../../../../common/constants';
export const getOutlierScoreFieldName = (jobConfig: DataFrameAnalyticsConfig) =>
`${jobConfig.dest.results_field}.${OUTLIER_SCORE}`;
+
+export const getFeatureCount = (resultsField: string, tableItems: DataGridItem[] = []) => {
+ if (tableItems.length === 0) {
+ return 0;
+ }
+
+ return Object.keys(tableItems[0]).filter(key =>
+ key.includes(`${resultsField}.${FEATURE_INFLUENCE}.`)
+ ).length;
+};
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 fdcb7d9d237e3..0154f92576c4a 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
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { FC } from 'react';
+import React, { useState, FC } from 'react';
import { i18n } from '@kbn/i18n';
@@ -15,127 +15,51 @@ import {
EuiHorizontalRule,
EuiPanel,
EuiSpacer,
- EuiTitle,
} from '@elastic/eui';
import {
useColorRange,
- ColorRangeLegend,
COLOR_RANGE,
COLOR_RANGE_SCALE,
} from '../../../../../components/color_range_legend';
+import { ColorRangeLegend } from '../../../../../components/color_range_legend';
+import { DataGrid } from '../../../../../components/data_grid';
+import { SavedSearchQuery } from '../../../../../contexts/ml';
+import { getToastNotifications } from '../../../../../util/dependency_cache';
-import { sortColumns, INDEX_STATUS, defaultSearchQuery } from '../../../../common';
+import { defaultSearchQuery, useResultsViewConfig, INDEX_STATUS } from '../../../../common';
import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns';
-import { useExploreData, TableItem } from '../../hooks/use_explore_data';
-
-import { ExplorationDataGrid } from '../exploration_data_grid';
import { ExplorationQueryBar } from '../exploration_query_bar';
+import { ExplorationTitle } from '../exploration_title';
-const FEATURE_INFLUENCE = 'feature_influence';
+import { getFeatureCount } from './common';
+import { useOutlierData } from './use_outlier_data';
-const ExplorationTitle: FC<{ jobId: string }> = ({ jobId }) => (
-
-
- {i18n.translate('xpack.ml.dataframe.analytics.exploration.jobIdTitle', {
- defaultMessage: 'Outlier detection job ID {jobId}',
- values: { jobId },
- })}
-
-
-);
+export type TableItem = Record;
interface ExplorationProps {
jobId: string;
}
-const getFeatureCount = (resultsField: string, tableItems: TableItem[] = []) => {
- if (tableItems.length === 0) {
- return 0;
- }
-
- return Object.keys(tableItems[0]).filter(key =>
- key.includes(`${resultsField}.${FEATURE_INFLUENCE}.`)
- ).length;
-};
-
export const OutlierExploration: FC = React.memo(({ jobId }) => {
- const {
- errorMessage,
- indexPattern,
- jobConfig,
- jobStatus,
- pagination,
- searchQuery,
- selectedFields,
- setPagination,
- setSearchQuery,
- setSelectedFields,
- setSortingColumns,
- sortingColumns,
- rowCount,
- status,
- tableFields,
- tableItems,
- } = useExploreData(jobId);
-
- const columns = [];
-
- if (
- jobConfig !== undefined &&
- indexPattern !== undefined &&
- selectedFields.length > 0 &&
- tableItems.length > 0
- ) {
- const resultsField = jobConfig.dest.results_field;
- const removePrefix = new RegExp(`^${resultsField}\.${FEATURE_INFLUENCE}\.`, 'g');
- columns.push(
- ...tableFields.sort(sortColumns(tableItems[0], resultsField)).map(id => {
- const idWithoutPrefix = id.replace(removePrefix, '');
- const field = indexPattern.fields.getByName(idWithoutPrefix);
-
- // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json']
- // To fall back to the default string schema it needs to be undefined.
- let schema;
-
- switch (field?.type) {
- case 'date':
- schema = 'datetime';
- break;
- case 'geo_point':
- schema = 'json';
- break;
- case 'number':
- schema = 'numeric';
- break;
- }
-
- if (id === `${resultsField}.outlier_score`) {
- schema = 'numeric';
- }
-
- return { id, schema };
- })
- );
- }
+ const explorationTitle = i18n.translate('xpack.ml.dataframe.analytics.exploration.jobIdTitle', {
+ defaultMessage: 'Outlier detection job ID {jobId}',
+ values: { jobId },
+ });
- const colorRange = useColorRange(
- COLOR_RANGE.BLUE,
- COLOR_RANGE_SCALE.INFLUENCER,
- jobConfig !== undefined ? getFeatureCount(jobConfig.dest.results_field, tableItems) : 1
- );
+ const { indexPattern, jobConfig, jobStatus } = useResultsViewConfig(jobId);
+ const [searchQuery, setSearchQuery] = useState(defaultSearchQuery);
+ const outlierData = useOutlierData(indexPattern, jobConfig, searchQuery);
- if (jobConfig === undefined || indexPattern === undefined) {
- return null;
- }
+ const { columns, 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')) {
return (
-
+
= React.memo(({ jobId }) =
);
}
- let tableError =
- status === INDEX_STATUS.ERROR && errorMessage.includes('parsing_exception')
- ? errorMessage
- : undefined;
-
- 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.',
- });
- }
+ const colorRange = useColorRange(
+ COLOR_RANGE.BLUE,
+ COLOR_RANGE_SCALE.INFLUENCER,
+ jobConfig !== undefined ? getFeatureCount(jobConfig.dest.results_field, tableItems) : 1
+ );
return (
@@ -170,7 +88,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) =
gutterSize="s"
>
-
+
{jobStatus !== undefined && (
@@ -179,7 +97,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) =
)}
- {(columns.length > 0 || searchQuery !== defaultSearchQuery) && (
+ {(columns.length > 0 || searchQuery !== defaultSearchQuery) && indexPattern !== undefined && (
<>
@@ -200,19 +118,10 @@ export const OutlierExploration: FC = React.memo(({ jobId }) =
{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.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.test.ts
new file mode 100644
index 0000000000000..91a5ba2db6908
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.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/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
new file mode 100644
index 0000000000000..0d06bc0d43307
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts
@@ -0,0 +1,117 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { useEffect } from 'react';
+
+import { EuiDataGridColumn } from '@elastic/eui';
+
+import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
+
+import {
+ useColorRange,
+ COLOR_RANGE,
+ COLOR_RANGE_SCALE,
+} from '../../../../../components/color_range_legend';
+import {
+ getDataGridSchemasFromFieldTypes,
+ useDataGrid,
+ useRenderCellValue,
+ UseIndexDataReturnType,
+} from '../../../../../components/data_grid';
+import { SavedSearchQuery } from '../../../../../contexts/ml';
+
+import { getIndexData, getIndexFields, DataFrameAnalyticsConfig } from '../../../../common';
+import { DEFAULT_RESULTS_FIELD, FEATURE_INFLUENCE } from '../../../../common/constants';
+import { sortExplorationResultsFields, ML__ID_COPY } from '../../../../common/fields';
+
+import { getFeatureCount, getOutlierScoreFieldName } from './common';
+
+export const useOutlierData = (
+ indexPattern: IndexPattern | undefined,
+ jobConfig: DataFrameAnalyticsConfig | undefined,
+ searchQuery: SavedSearchQuery
+): UseIndexDataReturnType => {
+ const needsDestIndexFields =
+ indexPattern !== undefined && indexPattern.title === jobConfig?.source.index[0];
+
+ const columns: EuiDataGridColumn[] = [];
+
+ if (jobConfig !== undefined && indexPattern !== undefined) {
+ const resultsField = jobConfig.dest.results_field;
+ const { fieldTypes } = getIndexFields(jobConfig, needsDestIndexFields);
+ columns.push(
+ ...getDataGridSchemasFromFieldTypes(fieldTypes, resultsField).sort((a: any, b: any) =>
+ sortExplorationResultsFields(a.id, b.id, jobConfig)
+ )
+ );
+ }
+
+ const dataGrid = useDataGrid(
+ columns,
+ 25,
+ // reduce default selected rows from 20 to 8 for performance reasons.
+ 8,
+ // by default, hide feature-influence columns and the doc id copy
+ d => !d.includes(`.${FEATURE_INFLUENCE}.`) && d !== ML__ID_COPY
+ );
+
+ // initialize sorting: reverse sort on outlier score column
+ useEffect(() => {
+ if (jobConfig !== undefined) {
+ dataGrid.setSortingColumns([{ id: getOutlierScoreFieldName(jobConfig), direction: 'desc' }]);
+ }
+ }, [jobConfig && jobConfig.id]);
+
+ useEffect(() => {
+ getIndexData(jobConfig, dataGrid, searchQuery);
+ // custom comparison
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]);
+
+ const colorRange = useColorRange(
+ COLOR_RANGE.BLUE,
+ COLOR_RANGE_SCALE.INFLUENCER,
+ jobConfig !== undefined ? getFeatureCount(jobConfig.dest.results_field, dataGrid.tableItems) : 1
+ );
+
+ const renderCellValue = useRenderCellValue(
+ indexPattern,
+ dataGrid.pagination,
+ dataGrid.tableItems,
+ jobConfig?.dest.results_field ?? DEFAULT_RESULTS_FIELD,
+ (columnId, cellValue, fullItem, setCellProps) => {
+ const resultsField = jobConfig?.dest.results_field ?? '';
+
+ const split = columnId.split('.');
+ let backgroundColor;
+
+ // column with feature values get color coded by its corresponding influencer value
+ if (
+ fullItem[resultsField] !== undefined &&
+ fullItem[resultsField][`${FEATURE_INFLUENCE}.${columnId}`] !== undefined
+ ) {
+ backgroundColor = colorRange(fullItem[resultsField][`${FEATURE_INFLUENCE}.${columnId}`]);
+ }
+
+ // column with influencer values get color coded by its own value
+ if (split.length > 2 && split[0] === resultsField && split[1] === FEATURE_INFLUENCE) {
+ backgroundColor = colorRange(cellValue);
+ }
+
+ if (backgroundColor !== undefined) {
+ setCellProps({
+ style: { backgroundColor },
+ });
+ }
+ }
+ );
+
+ return {
+ ...dataGrid,
+ columns,
+ renderCellValue,
+ };
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx
index 6ef6666be5ec6..f6e8e0047671f 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx
@@ -235,7 +235,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery })
href={`${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-regression-evaluation`}
>
{i18n.translate(
- 'xpack.ml.dataframe.analytics.classificationExploration.regressionDocsLink',
+ 'xpack.ml.dataframe.analytics.regressionExploration.regressionDocsLink',
{
defaultMessage: 'Regression evaluation docs ',
}
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx
index bfeca76a2b1c7..36d91f6f41d44 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration.tsx
@@ -4,174 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { FC, Fragment, useState, useEffect } from 'react';
-import { EuiCallOut, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { ml } from '../../../../../services/ml_api_service';
-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 { ResultsSearchQuery, defaultSearchQuery } from '../../../../common/analytics';
-import { LoadingPanel } from '../loading_panel';
-import { getIndexPatternIdFromName } from '../../../../../util/index_utils';
-import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/common/index_patterns';
-import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
-import { useMlContext } from '../../../../../contexts/ml';
-import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics';
+import React, { FC } from 'react';
-export const ExplorationTitle: React.FC<{ jobId: string }> = ({ jobId }) => (
-
-
- {i18n.translate('xpack.ml.dataframe.analytics.regressionExploration.tableJobIdTitle', {
- defaultMessage: 'Destination index for regression job ID {jobId}',
- values: { jobId },
- })}
-
-
-);
+import { i18n } from '@kbn/i18n';
-const jobConfigErrorTitle = i18n.translate(
- 'xpack.ml.dataframe.analytics.regressionExploration.jobConfigurationFetchError',
- {
- defaultMessage:
- 'Unable to fetch results. An error occurred loading the job configuration data.',
- }
-);
+import { ExplorationPageWrapper } from '../exploration_page_wrapper';
-const jobCapsErrorTitle = i18n.translate(
- 'xpack.ml.dataframe.analytics.regressionExploration.jobCapsFetchError',
- {
- defaultMessage: "Unable to fetch results. An error occurred loading the index's field data.",
- }
-);
+import { EvaluatePanel } from './evaluate_panel';
interface Props {
jobId: string;
}
export const RegressionExploration: FC = ({ jobId }) => {
- const [jobConfig, setJobConfig] = useState(undefined);
- const [jobStatus, setJobStatus] = useState(undefined);
- const [indexPattern, setIndexPattern] = useState(undefined);
- const [isLoadingJobConfig, setIsLoadingJobConfig] = useState(false);
- const [isInitialized, setIsInitialized] = useState(false);
- const [jobConfigErrorMessage, setJobConfigErrorMessage] = useState(undefined);
- const [jobCapsServiceErrorMessage, setJobCapsServiceErrorMessage] = useState(
- undefined
- );
- const [searchQuery, setSearchQuery] = useState(defaultSearchQuery);
- const mlContext = useMlContext();
-
- const loadJobConfig = async () => {
- setIsLoadingJobConfig(true);
- try {
- const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId);
- const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId);
- const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
- ? analyticsStats.data_frame_analytics[0]
- : undefined;
-
- if (stats !== undefined && stats.state) {
- setJobStatus(stats.state);
- }
-
- if (
- Array.isArray(analyticsConfigs.data_frame_analytics) &&
- analyticsConfigs.data_frame_analytics.length > 0
- ) {
- setJobConfig(analyticsConfigs.data_frame_analytics[0]);
- setIsLoadingJobConfig(false);
- }
- } catch (e) {
- if (e.message !== undefined) {
- setJobConfigErrorMessage(e.message);
- } else {
- setJobConfigErrorMessage(JSON.stringify(e));
- }
- setIsLoadingJobConfig(false);
- }
- };
-
- useEffect(() => {
- loadJobConfig();
- }, []);
-
- const initializeJobCapsService = async () => {
- if (jobConfig !== undefined) {
- try {
- const destIndex = Array.isArray(jobConfig.dest.index)
- ? jobConfig.dest.index[0]
- : jobConfig.dest.index;
- const destIndexPatternId = getIndexPatternIdFromName(destIndex) || destIndex;
- let indexP: IIndexPattern | undefined;
-
- try {
- indexP = await mlContext.indexPatterns.get(destIndexPatternId);
- } catch (e) {
- indexP = undefined;
- }
-
- if (indexP === undefined) {
- const sourceIndex = jobConfig.source.index[0];
- const sourceIndexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex;
- indexP = await mlContext.indexPatterns.get(sourceIndexPatternId);
- }
-
- if (indexP !== undefined) {
- setIndexPattern(indexP);
- await newJobCapsService.initializeFromIndexPattern(indexP, false, false);
- }
- setIsInitialized(true);
- } catch (e) {
- if (e.message !== undefined) {
- setJobCapsServiceErrorMessage(e.message);
- } else {
- setJobCapsServiceErrorMessage(JSON.stringify(e));
- }
- }
- }
- };
-
- useEffect(() => {
- initializeJobCapsService();
- }, [jobConfig && jobConfig.id]);
-
- if (jobConfigErrorMessage !== undefined || jobCapsServiceErrorMessage !== undefined) {
- return (
-
-
-
-
- {jobConfigErrorMessage ? jobConfigErrorMessage : jobCapsServiceErrorMessage}
-
-
- );
- }
-
return (
-
- {isLoadingJobConfig === true && jobConfig === undefined && }
- {isLoadingJobConfig === false && jobConfig !== undefined && isInitialized === true && (
-
- )}
-
- {isLoadingJobConfig === true && jobConfig === undefined && }
- {isLoadingJobConfig === false &&
- jobConfig !== undefined &&
- indexPattern !== undefined &&
- isInitialized === true && (
-
- )}
-
+
);
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration_data_grid.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration_data_grid.tsx
deleted file mode 100644
index 0fcb1ed600719..0000000000000
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/regression_exploration_data_grid.tsx
+++ /dev/null
@@ -1,135 +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, { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react';
-
-import { i18n } from '@kbn/i18n';
-
-import { EuiDataGrid, EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui';
-
-import { euiDataGridStyle, euiDataGridToolbarSettings } from '../../../../common';
-
-import { mlFieldFormatService } from '../../../../../services/field_format_service';
-
-import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
-
-const PAGE_SIZE_OPTIONS = [5, 10, 25, 50];
-
-type Pagination = Pick;
-type TableItem = Record;
-
-interface ExplorationDataGridProps {
- colorRange?: (d: number) => string;
- columns: any[];
- indexPattern: IndexPattern;
- pagination: Pagination;
- resultsField: string;
- rowCount: number;
- selectedFields: string[];
- setPagination: Dispatch>;
- setSelectedFields: Dispatch>;
- setSortingColumns: Dispatch>;
- sortingColumns: EuiDataGridSorting['columns'];
- tableItems: TableItem[];
-}
-
-export const RegressionExplorationDataGrid: FC = ({
- columns,
- indexPattern,
- pagination,
- resultsField,
- rowCount,
- selectedFields,
- setPagination,
- setSelectedFields,
- setSortingColumns,
- sortingColumns,
- tableItems,
-}) => {
- const renderCellValue = useMemo(() => {
- return ({ rowIndex, columnId }: { rowIndex: number; columnId: string; setCellProps: any }) => {
- const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize;
-
- const fullItem = tableItems[adjustedRowIndex];
-
- if (fullItem === undefined) {
- return null;
- }
-
- let format: any;
-
- if (indexPattern !== undefined) {
- format = mlFieldFormatService.getFieldFormatFromIndexPattern(indexPattern, columnId, '');
- }
-
- const cellValue =
- fullItem.hasOwnProperty(columnId) && fullItem[columnId] !== undefined
- ? fullItem[columnId]
- : null;
-
- if (format !== undefined) {
- return format.convert(cellValue, 'text');
- }
-
- if (typeof cellValue === 'string' || cellValue === null) {
- return cellValue;
- }
-
- if (typeof cellValue === 'boolean') {
- return cellValue ? 'true' : 'false';
- }
-
- if (typeof cellValue === 'object' && cellValue !== null) {
- return JSON.stringify(cellValue);
- }
-
- return cellValue;
- };
- }, [resultsField, rowCount, tableItems, pagination.pageIndex, pagination.pageSize]);
-
- const onChangeItemsPerPage = useCallback(
- pageSize => {
- setPagination(p => {
- const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize);
- return { pageIndex, pageSize };
- });
- },
- [setPagination]
- );
-
- const onChangePage = useCallback(pageIndex => setPagination(p => ({ ...p, pageIndex })), [
- setPagination,
- ]);
-
- const onSort = useCallback(sc => setSortingColumns(sc), [setSortingColumns]);
-
- return (
-
- );
-};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx
deleted file mode 100644
index 43fa50b2e4df5..0000000000000
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx
+++ /dev/null
@@ -1,233 +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, { Fragment, FC, useEffect } from 'react';
-
-import { i18n } from '@kbn/i18n';
-import {
- EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiFormRow,
- EuiPanel,
- EuiProgress,
- EuiSpacer,
- EuiText,
-} from '@elastic/eui';
-
-import {
- BASIC_NUMERICAL_TYPES,
- EXTENDED_NUMERICAL_TYPES,
- sortRegressionResultsFields,
-} from '../../../../common/fields';
-
-import {
- DataFrameAnalyticsConfig,
- MAX_COLUMNS,
- 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 { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
-
-import { useExploreData } from './use_explore_data';
-import { ExplorationTitle } from './regression_exploration';
-import { RegressionExplorationDataGrid } from './regression_exploration_data_grid';
-import { ExplorationQueryBar } from '../exploration_query_bar';
-
-const showingDocs = i18n.translate(
- 'xpack.ml.dataframe.analytics.regressionExploration.documentsShownHelpText',
- {
- defaultMessage: 'Showing documents for which predictions exist',
- }
-);
-
-const showingFirstDocs = i18n.translate(
- 'xpack.ml.dataframe.analytics.regressionExploration.firstDocumentsShownHelpText',
- {
- defaultMessage: 'Showing first {searchSize} documents for which predictions exist',
- values: { searchSize: SEARCH_SIZE },
- }
-);
-
-interface Props {
- indexPattern: IndexPattern;
- jobConfig: DataFrameAnalyticsConfig;
- jobStatus?: DATA_FRAME_TASK_STATE;
- setEvaluateSearchQuery: React.Dispatch>;
-}
-
-export const ResultsTable: FC = React.memo(
- ({ indexPattern, jobConfig, jobStatus, setEvaluateSearchQuery }) => {
- const needsDestIndexFields = indexPattern && indexPattern.title === jobConfig.source.index[0];
- const resultsField = jobConfig.dest.results_field;
- const {
- errorMessage,
- fieldTypes,
- pagination,
- searchQuery,
- selectedFields,
- rowCount,
- setPagination,
- setSearchQuery,
- setSelectedFields,
- setSortingColumns,
- sortingColumns,
- status,
- tableFields,
- tableItems,
- } = useExploreData(jobConfig, needsDestIndexFields);
-
- useEffect(() => {
- setEvaluateSearchQuery(searchQuery);
- }, [JSON.stringify(searchQuery)]);
-
- const columns = tableFields
- .sort((a: any, b: any) => sortRegressionResultsFields(a, b, jobConfig))
- .map((field: any) => {
- // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json']
- // To fall back to the default string schema it needs to be undefined.
- let schema;
- let isSortable = true;
- const type = fieldTypes[field];
- const isNumber =
- type !== undefined &&
- (BASIC_NUMERICAL_TYPES.has(type) || EXTENDED_NUMERICAL_TYPES.has(type));
-
- if (isNumber) {
- schema = 'numeric';
- }
-
- switch (type) {
- case 'date':
- schema = 'datetime';
- break;
- case 'geo_point':
- schema = 'json';
- break;
- case 'boolean':
- schema = 'boolean';
- break;
- }
-
- if (field === `${resultsField}.feature_importance`) {
- isSortable = false;
- }
-
- return { id: field, schema, isSortable };
- });
-
- const docFieldsCount = tableFields.length;
-
- if (jobConfig === undefined) {
- return null;
- }
- // 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 (
-
-
-
-
-
- {jobStatus !== undefined && (
-
- {getTaskStateBadge(jobStatus)}
-
- )}
-
-
- {errorMessage}
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
- {jobStatus !== undefined && (
-
- {getTaskStateBadge(jobStatus)}
-
- )}
-
-
-
-
-
- {docFieldsCount > MAX_COLUMNS && (
-
- {i18n.translate(
- 'xpack.ml.dataframe.analytics.regressionExploration.fieldSelection',
- {
- defaultMessage:
- '{selectedFieldsLength, number} of {docFieldsCount, number} {docFieldsCount, plural, one {field} other {fields}} selected',
- values: { selectedFieldsLength: selectedFields.length, docFieldsCount },
- }
- )}
-
- )}
-
-
-
-
- {status === INDEX_STATUS.LOADING && }
- {status !== INDEX_STATUS.LOADING && (
-
- )}
- {(columns.length > 0 || searchQuery !== defaultSearchQuery) && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
- );
- }
-);
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts
deleted file mode 100644
index 978aafd10de11..0000000000000
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/use_explore_data.ts
+++ /dev/null
@@ -1,291 +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 { useEffect, useState, Dispatch, SetStateAction } from 'react';
-import { EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui';
-
-import { SearchResponse } from 'elasticsearch';
-import { cloneDeep } from 'lodash';
-
-import { ml } from '../../../../../services/ml_api_service';
-import { getNestedProperty } from '../../../../../util/object_utils';
-import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
-import { SORT_DIRECTION } from '../../../../../components/ml_in_memory_table';
-import { SavedSearchQuery } from '../../../../../contexts/ml';
-
-import {
- getDefaultFieldsFromJobCaps,
- getDependentVar,
- getFlattenedFields,
- getPredictedFieldName,
- DataFrameAnalyticsConfig,
- EsFieldName,
- INDEX_STATUS,
-} from '../../../../common';
-import { Dictionary } from '../../../../../../../common/types/common';
-import { isKeywordAndTextType } from '../../../../common/fields';
-import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public';
-import {
- LoadExploreDataArg,
- defaultSearchQuery,
- ResultsSearchQuery,
- isResultsSearchBoolQuery,
-} from '../../../../common/analytics';
-
-export type TableItem = Record;
-type Pagination = Pick;
-
-export interface UseExploreDataReturnType {
- errorMessage: string;
- fieldTypes: { [key: string]: ES_FIELD_TYPES };
- pagination: Pagination;
- rowCount: number;
- searchQuery: SavedSearchQuery;
- selectedFields: EsFieldName[];
- setFilterByIsTraining: Dispatch>;
- setPagination: Dispatch>;
- setSearchQuery: Dispatch>;
- setSelectedFields: Dispatch>;
- setSortingColumns: Dispatch>;
- sortingColumns: EuiDataGridSorting['columns'];
- status: INDEX_STATUS;
- tableFields: string[];
- tableItems: TableItem[];
-}
-
-type EsSorting = Dictionary<{
- order: 'asc' | 'desc';
-}>;
-
-// The types specified in `@types/elasticsearch` are out of date and still have `total: number`.
-interface SearchResponse7 extends SearchResponse {
- hits: SearchResponse['hits'] & {
- total: {
- value: number;
- relation: string;
- };
- };
-}
-
-export const useExploreData = (
- jobConfig: DataFrameAnalyticsConfig,
- needsDestIndexFields: boolean
-): UseExploreDataReturnType => {
- const [errorMessage, setErrorMessage] = useState('');
- const [status, setStatus] = useState(INDEX_STATUS.UNUSED);
-
- const [selectedFields, setSelectedFields] = useState([] as EsFieldName[]);
- const [tableFields, setTableFields] = useState([]);
- const [tableItems, setTableItems] = useState([]);
- const [fieldTypes, setFieldTypes] = useState<{ [key: string]: ES_FIELD_TYPES }>({});
- const [rowCount, setRowCount] = useState(0);
-
- const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 });
- const [searchQuery, setSearchQuery] = useState(defaultSearchQuery);
- const [filterByIsTraining, setFilterByIsTraining] = useState(undefined);
- const [sortingColumns, setSortingColumns] = useState([]);
-
- const predictedFieldName = getPredictedFieldName(
- jobConfig.dest.results_field,
- jobConfig.analysis
- );
- const dependentVariable = getDependentVar(jobConfig.analysis);
-
- const getDefaultSelectedFields = () => {
- const { fields } = newJobCapsService;
- if (selectedFields.length === 0 && jobConfig !== undefined) {
- const { selectedFields: defaultSelected, docFields } = getDefaultFieldsFromJobCaps(
- fields,
- jobConfig,
- needsDestIndexFields
- );
-
- const types: { [key: string]: ES_FIELD_TYPES } = {};
- const allFields: string[] = [];
-
- docFields.forEach(field => {
- types[field.id] = field.type;
- allFields.push(field.id);
- });
-
- setFieldTypes(types);
- setSelectedFields(defaultSelected.map(field => field.id));
- setTableFields(allFields);
- }
- };
-
- const loadExploreData = async ({
- filterByIsTraining: isTraining,
- searchQuery: incomingQuery,
- }: LoadExploreDataArg) => {
- if (jobConfig !== undefined) {
- setErrorMessage('');
- setStatus(INDEX_STATUS.LOADING);
-
- try {
- const resultsField = jobConfig.dest.results_field;
- const searchQueryClone: ResultsSearchQuery = cloneDeep(incomingQuery);
- let query: ResultsSearchQuery;
- const { pageIndex, pageSize } = pagination;
- // If filterByIsTraining is defined - add that in to the final query
- const trainingQuery =
- isTraining !== undefined
- ? {
- term: { [`${resultsField}.is_training`]: { value: isTraining } },
- }
- : undefined;
-
- if (JSON.stringify(incomingQuery) === JSON.stringify(defaultSearchQuery)) {
- const existsQuery = {
- exists: {
- field: resultsField,
- },
- };
-
- query = {
- bool: {
- must: [existsQuery],
- },
- };
-
- if (trainingQuery !== undefined && isResultsSearchBoolQuery(query)) {
- query.bool.must.push(trainingQuery);
- }
- } else if (isResultsSearchBoolQuery(searchQueryClone)) {
- if (searchQueryClone.bool.must === undefined) {
- searchQueryClone.bool.must = [];
- }
-
- searchQueryClone.bool.must.push({
- exists: {
- field: resultsField,
- },
- });
-
- if (trainingQuery !== undefined) {
- searchQueryClone.bool.must.push(trainingQuery);
- }
-
- query = searchQueryClone;
- } else {
- query = searchQueryClone;
- }
-
- const sort: EsSorting = sortingColumns
- .map(column => {
- const { id } = column;
- column.id = isKeywordAndTextType(id) ? `${id}.keyword` : id;
- return column;
- })
- .reduce((s, column) => {
- s[column.id] = { order: column.direction };
- return s;
- }, {} as EsSorting);
-
- const resp: SearchResponse7 = await ml.esSearch({
- index: jobConfig.dest.index,
- body: {
- query,
- from: pageIndex * pageSize,
- size: pageSize,
- ...(Object.keys(sort).length > 0 ? { sort } : {}),
- },
- });
-
- setRowCount(resp.hits.total.value);
-
- const docs = resp.hits.hits;
-
- if (docs.length === 0) {
- setTableItems([]);
- setStatus(INDEX_STATUS.LOADED);
- return;
- }
-
- // 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 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(() => {
- getDefaultSelectedFields();
- }, [jobConfig && jobConfig.id]);
-
- // By default set sorting to descending on the prediction field (`_prediction`).
- useEffect(() => {
- const sortByField = isKeywordAndTextType(dependentVariable)
- ? `${predictedFieldName}.keyword`
- : predictedFieldName;
- const direction = SORT_DIRECTION.DESC;
-
- setSortingColumns([{ id: sortByField, direction }]);
- }, [jobConfig && jobConfig.id]);
-
- useEffect(() => {
- loadExploreData({ filterByIsTraining, searchQuery });
- }, [
- filterByIsTraining,
- jobConfig && jobConfig.id,
- pagination,
- searchQuery,
- selectedFields,
- sortingColumns,
- ]);
-
- return {
- errorMessage,
- fieldTypes,
- pagination,
- searchQuery,
- selectedFields,
- rowCount,
- setFilterByIsTraining,
- setPagination,
- setSelectedFields,
- setSortingColumns,
- setSearchQuery,
- sortingColumns,
- status,
- tableItems,
- tableFields,
- };
-};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts
deleted file mode 100644
index a0a9eb8312499..0000000000000
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_explore_data/use_explore_data.ts
+++ /dev/null
@@ -1,267 +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 { useEffect, useState, Dispatch, SetStateAction } from 'react';
-import { SearchResponse } from 'elasticsearch';
-
-import { EuiDataGridPaginationProps, EuiDataGridSorting } from '@elastic/eui';
-
-import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
-
-import { Dictionary } from '../../../../../../../common/types/common';
-
-import { SavedSearchQuery } from '../../../../../contexts/ml';
-import { ml } from '../../../../../services/ml_api_service';
-import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
-import { getIndexPatternIdFromName } from '../../../../../util/index_utils';
-import { getNestedProperty } from '../../../../../util/object_utils';
-import { useMlContext } from '../../../../../contexts/ml';
-import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics';
-
-import {
- getDefaultSelectableFields,
- getFlattenedFields,
- DataFrameAnalyticsConfig,
- EsFieldName,
- INDEX_STATUS,
- MAX_COLUMNS,
- defaultSearchQuery,
-} from '../../../../common';
-import { isKeywordAndTextType } from '../../../../common/fields';
-
-import { getOutlierScoreFieldName } from './common';
-import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
-
-export type TableItem = Record;
-
-type Pagination = Pick