diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts index 5d0ecf96fb6b5..cbf2acd152476 100644 --- a/x-pack/plugins/ml/common/types/data_frame_analytics.ts +++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts @@ -19,7 +19,9 @@ export type DataFrameAnalyticsId = string; export interface OutlierAnalysis { [key: string]: {}; - outlier_detection: {}; + outlier_detection: { + compute_feature_influence?: boolean; + }; } interface Regression { diff --git a/x-pack/plugins/ml/common/util/analytics_utils.ts b/x-pack/plugins/ml/common/util/analytics_utils.ts index d231ed4344389..94797efdfcfad 100644 --- a/x-pack/plugins/ml/common/util/analytics_utils.ts +++ b/x-pack/plugins/ml/common/util/analytics_utils.ts @@ -13,16 +13,19 @@ import { import { ANALYSIS_CONFIG_TYPE } from '../../common/constants/data_frame_analytics'; export const isOutlierAnalysis = (arg: any): arg is OutlierAnalysis => { + if (typeof arg !== 'object' || arg === null) return false; const keys = Object.keys(arg); return keys.length === 1 && keys[0] === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION; }; export const isRegressionAnalysis = (arg: any): arg is RegressionAnalysis => { + if (typeof arg !== 'object' || arg === null) return false; const keys = Object.keys(arg); return keys.length === 1 && keys[0] === ANALYSIS_CONFIG_TYPE.REGRESSION; }; export const isClassificationAnalysis = (arg: any): arg is ClassificationAnalysis => { + if (typeof arg !== 'object' || arg === null) return false; const keys = Object.keys(arg); return keys.length === 1 && keys[0] === ANALYSIS_CONFIG_TYPE.CLASSIFICATION; }; 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 fa711316df003..734b5170d26a1 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 @@ -6,7 +6,9 @@ import React, { useState, FC } from 'react'; -import { EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; import { useColorRange, @@ -15,7 +17,8 @@ import { } from '../../../../../components/color_range_legend'; import { SavedSearchQuery } from '../../../../../contexts/ml'; -import { defaultSearchQuery, useResultsViewConfig } from '../../../../common'; +import { defaultSearchQuery, isOutlierAnalysis, useResultsViewConfig } from '../../../../common'; +import { FEATURE_INFLUENCE } from '../../../../common/constants'; import { ExpandableSectionAnalytics, ExpandableSectionResults } from '../expandable_section'; import { ExplorationQueryBar } from '../exploration_query_bar'; @@ -39,6 +42,24 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = const featureCount = getFeatureCount(jobConfig?.dest?.results_field || '', tableItems); const colorRange = useColorRange(COLOR_RANGE.BLUE, COLOR_RANGE_SCALE.INFLUENCER, featureCount); + // Show the color range only if feature influence is enabled and there's more than 0 features. + const showColorRange = + featureCount > 0 && + isOutlierAnalysis(jobConfig?.analysis) && + jobConfig?.analysis.outlier_detection.compute_feature_influence === true; + + const resultsField = jobConfig?.dest.results_field ?? ''; + + // Identify if the results index has a legacy feature influence format. + // If feature influence was enabled for the legacy job we'll show a callout + // with some additional information for a workaround. + const showLegacyFeatureInfluenceFormatCallout = + isOutlierAnalysis(jobConfig?.analysis) && + jobConfig?.analysis.outlier_detection.compute_feature_influence === true && + columnsWithCharts.findIndex( + (d) => d.id === `${resultsField}.${FEATURE_INFLUENCE}.feature_name` + ) === -1; + return ( <> {typeof jobConfig?.description !== 'undefined' && ( @@ -55,8 +76,26 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = )} {typeof jobConfig?.id === 'string' && } + {showLegacyFeatureInfluenceFormatCallout && ( + <> + + + + )} 0 ? colorRange : undefined} + colorRange={ + showColorRange && !showLegacyFeatureInfluenceFormatCallout ? colorRange : undefined + } indexData={outlierData} indexPattern={indexPattern} jobConfig={jobConfig}