[ML] Data Frame Analytics: Expandable sections for classification and regression (#79414)

Applies the expandable section based layout to the results pages of classification and regression analytics jobs.
This commit is contained in:
Walter Rafelsberger 2020-10-05 19:23:13 +02:00 committed by GitHub
parent 4fdf2f1566
commit c7caac61a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 942 additions and 880 deletions

View file

@ -23,7 +23,7 @@
.mlDataFrameAnalyticsClassification__actualLabel {
float: left;
width: 80px;
padding-top: $euiSize * 4 + $euiSizeXS;
padding-top: $euiSize * 4;
}
/*

View file

@ -17,17 +17,19 @@ interface Props {
}
export const ClassificationExploration: FC<Props> = ({ jobId, defaultIsTraining }) => (
<ExplorationPageWrapper
jobId={jobId}
title={i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.tableJobIdTitle',
{
defaultMessage: 'Destination index for classification job ID {jobId}',
values: { jobId },
}
)}
EvaluatePanel={EvaluatePanel}
FeatureImportanceSummaryPanel={FeatureImportanceSummaryPanel}
defaultIsTraining={defaultIsTraining}
/>
<div className="mlDataFrameAnalyticsClassification">
<ExplorationPageWrapper
jobId={jobId}
title={i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.tableJobIdTitle',
{
defaultMessage: 'Destination index for classification job ID {jobId}',
values: { jobId },
}
)}
EvaluatePanel={EvaluatePanel}
FeatureImportanceSummaryPanel={FeatureImportanceSummaryPanel}
defaultIsTraining={defaultIsTraining}
/>
</div>
);

View file

@ -6,7 +6,7 @@
import './_classification_exploration.scss';
import React, { FC, useState, useEffect, Fragment } from 'react';
import React, { FC, useState, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
@ -15,7 +15,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
@ -30,7 +29,6 @@ import {
DataFrameAnalyticsConfig,
} from '../../../../common';
import { isKeywordAndTextType } from '../../../../common/fields';
import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/use_columns';
import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
import {
isResultsSearchBoolQuery,
@ -39,7 +37,9 @@ import {
ResultsSearchQuery,
ANALYSIS_CONFIG_TYPE,
} from '../../../../common/analytics';
import { LoadingPanel } from '../loading_panel';
import { ExpandableSection, HEADER_ITEMS_LOADING } from '../expandable_section';
import {
getColumnData,
ACTUAL_CLASS_ID,
@ -47,7 +47,7 @@ import {
getTrailingControlColumns,
} from './column_data';
interface Props {
export interface EvaluatePanelProps {
jobConfig: DataFrameAnalyticsConfig;
jobStatus?: DATA_FRAME_TASK_STATE;
searchQuery: ResultsSearchQuery;
@ -90,7 +90,7 @@ function getHelpText(dataSubsetTitle: string) {
return helpText;
}
export const EvaluatePanel: FC<Props> = ({ jobConfig, jobStatus, searchQuery }) => {
export const EvaluatePanel: FC<EvaluatePanelProps> = ({ jobConfig, jobStatus, searchQuery }) => {
const {
services: { docLinks },
} = useMlKibana();
@ -272,10 +272,6 @@ export const EvaluatePanel: FC<Props> = ({ jobConfig, jobStatus, searchQuery })
return <span>{columnId === ACTUAL_CLASS_ID ? cellValue : accuracy}</span>;
};
if (isLoading === true) {
return <LoadingPanel />;
}
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks;
const showTrailingColumns = columnsData.length > MAX_COLUMNS;
@ -288,137 +284,159 @@ export const EvaluatePanel: FC<Props> = ({ jobConfig, jobStatus, searchQuery })
showTrailingColumns === true && showFullColumns === false ? MAX_COLUMNS : columnsData.length;
return (
<EuiPanel
data-test-subj="mlDFAnalyticsClassificationExplorationEvaluatePanel"
className="mlDataFrameAnalyticsClassification"
>
<div>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<span>
{i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.evaluateJobIdTitle',
{
defaultMessage: 'Evaluation of classification job ID {jobId}',
values: { jobId: jobConfig.id },
}
)}
</span>
</EuiTitle>
</EuiFlexItem>
{jobStatus !== undefined && (
<EuiFlexItem grow={false}>
<span>{getTaskStateBadge(jobStatus)}</span>
</EuiFlexItem>
)}
<EuiFlexItem />
<EuiFlexItem grow={false}>
<EuiButtonEmpty
target="_blank"
iconType="help"
iconSide="left"
color="primary"
href={`${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-classification`}
>
{i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink',
{
defaultMessage: 'Classification evaluation docs ',
}
)}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</div>
{error !== null && <ErrorCallout error={error} />}
{error === null && (
<Fragment>
<div>
<EuiFlexGroup gutterSize="xs">
<EuiTitle size="xxs">
<span>{getHelpText(dataSubsetTitle)}</span>
</EuiTitle>
<EuiFlexItem grow={false}>
<EuiIconTip
anchorClassName="mlDataFrameAnalyticsClassificationInfoTooltip"
content={i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTooltip',
{
defaultMessage:
'The multi-class confusion matrix contains the number of occurrences where the analysis classified data points correctly with their actual class as well as the number of occurrences where it misclassified them with another class',
}
)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</div>
{docsCount !== null && (
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analytics.classificationExploration.generalizationDocsCount"
defaultMessage="{docsCount, plural, one {# doc} other {# docs}} evaluated"
values={{ docsCount }}
/>
</EuiText>
)}
{/* BEGIN TABLE ELEMENTS */}
<EuiSpacer size="m" />
<div className="mlDataFrameAnalyticsClassification__confusionMatrix">
<div className="mlDataFrameAnalyticsClassification__actualLabel">
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixActualLabel"
defaultMessage="Actual label"
/>
</EuiText>
</div>
<div className="mlDataFrameAnalyticsClassification__dataGridMinWidth">
{columns.length > 0 && columnsData.length > 0 && (
<>
<div>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixPredictedLabel"
defaultMessage="Predicted label"
/>
</EuiText>
</div>
<EuiSpacer size="s" />
<EuiDataGrid
data-test-subj="mlDFAnalyticsClassificationExplorationConfusionMatrix"
aria-label={i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixLabel',
<>
<ExpandableSection
dataTestId="ClassificationEvaluation"
title={
<FormattedMessage
id="xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionTitle"
defaultMessage="Model evaluation"
/>
}
docsLink={
<EuiButtonEmpty
target="_blank"
iconType="help"
iconSide="left"
color="primary"
href={`${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-classification`}
>
{i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink',
{
defaultMessage: 'Classification evaluation docs ',
}
)}
</EuiButtonEmpty>
}
headerItems={
!isLoading
? [
...(jobStatus !== undefined
? [
{
defaultMessage: 'Classification confusion matrix',
}
)}
columns={shownColumns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
rowCount={rowCount}
renderCellValue={renderCellValue}
inMemory={{ level: 'sorting' }}
toolbarVisibility={{
showColumnSelector: true,
showStyleSelector: false,
showFullScreenSelector: false,
showSortSelector: false,
}}
popoverContents={popoverContents}
gridStyle={{ rowHover: 'none' }}
trailingControlColumns={
showTrailingColumns === true && showFullColumns === false
? getTrailingControlColumns(extraColumns, setShowFullColumns)
: undefined
}
/>
id: 'jobStatus',
label: i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.evaluateJobStatusLabel',
{
defaultMessage: 'Job status',
}
),
value: jobStatus,
},
]
: []),
...(docsCount !== null
? [
{
id: 'docsEvaluated',
label: i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.generalizationDocsCount',
{
defaultMessage: '{docsCount, plural, one {doc} other {docs}} evaluated',
values: { docsCount },
}
),
value: docsCount,
},
]
: []),
]
: HEADER_ITEMS_LOADING
}
contentPadding={true}
content={
!isLoading ? (
<>
{error !== null && <ErrorCallout error={error} />}
{error === null && (
<>
<EuiFlexGroup gutterSize="none">
<EuiTitle size="xxs">
<span>{getHelpText(dataSubsetTitle)}</span>
</EuiTitle>
<EuiFlexItem grow={false}>
<EuiIconTip
anchorClassName="mlDataFrameAnalyticsClassificationInfoTooltip"
content={i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTooltip',
{
defaultMessage:
'The multi-class confusion matrix contains the number of occurrences where the analysis classified data points correctly with their actual class as well as the number of occurrences where it misclassified them with another class',
}
)}
/>
</EuiFlexItem>
</EuiFlexGroup>
{/* BEGIN TABLE ELEMENTS */}
<EuiSpacer size="m" />
<div className="mlDataFrameAnalyticsClassification__confusionMatrix">
<div className="mlDataFrameAnalyticsClassification__actualLabel">
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixActualLabel"
defaultMessage="Actual label"
/>
</EuiText>
</div>
<div className="mlDataFrameAnalyticsClassification__dataGridMinWidth">
{columns.length > 0 && columnsData.length > 0 && (
<>
<div>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixPredictedLabel"
defaultMessage="Predicted label"
/>
</EuiText>
</div>
<EuiSpacer size="s" />
<EuiDataGrid
data-test-subj="mlDFAnalyticsClassificationExplorationConfusionMatrix"
aria-label={i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixLabel',
{
defaultMessage: 'Classification confusion matrix',
}
)}
columns={shownColumns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
rowCount={rowCount}
renderCellValue={renderCellValue}
inMemory={{ level: 'sorting' }}
toolbarVisibility={{
showColumnSelector: true,
showStyleSelector: false,
showFullScreenSelector: false,
showSortSelector: false,
}}
popoverContents={popoverContents}
gridStyle={{
border: 'all',
fontSize: 's',
cellPadding: 's',
stripes: false,
rowHover: 'none',
header: 'shade',
}}
trailingControlColumns={
showTrailingColumns === true && showFullColumns === false
? getTrailingControlColumns(extraColumns, setShowFullColumns)
: undefined
}
/>
</>
)}
</div>
</div>
</>
)}
</div>
</div>
</Fragment>
)}
{/* END TABLE ELEMENTS */}
</EuiPanel>
{/* END TABLE ELEMENTS */}
</>
) : null
}
/>
<EuiSpacer size="m" />
</>
);
};

View file

@ -1,3 +1,7 @@
.mlExpandableSection {
padding: 0 $euiSizeS $euiSizeS $euiSizeS;
}
.mlExpandableSection-contentPadding {
padding: $euiSizeS;
}

View file

@ -29,9 +29,13 @@ const isHeaderItems = (arg: any): arg is HeaderItem[] => {
return Array.isArray(arg);
};
export const HEADER_ITEMS_LOADING = 'header_items_loading';
export interface ExpandableSectionProps {
content: ReactNode;
headerItems?: HeaderItem[] | 'loading';
contentPadding?: boolean;
docsLink?: ReactNode;
headerItems?: HeaderItem[] | typeof HEADER_ITEMS_LOADING;
isExpanded?: boolean;
dataTestId: string;
title: ReactNode;
@ -45,8 +49,10 @@ export const ExpandableSection: FC<ExpandableSectionProps> = ({
// callback.
isExpanded: isExpandedDefault = true,
content,
contentPadding = false,
dataTestId,
title,
docsLink,
}) => {
const [isExpanded, setIsExpanded] = useState(isExpandedDefault);
const toggleExpanded = () => {
@ -56,16 +62,21 @@ export const ExpandableSection: FC<ExpandableSectionProps> = ({
return (
<EuiPanel paddingSize="none" data-test-subj={`mlDFExpandableSection-${dataTestId}`}>
<div className="mlExpandableSection">
<EuiButtonEmpty
onClick={toggleExpanded}
iconType={isExpanded ? 'arrowUp' : 'arrowDown'}
size="l"
iconSide="right"
flush="left"
>
{title}
</EuiButtonEmpty>
{headerItems === 'loading' && <EuiLoadingContent lines={1} />}
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="none">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={toggleExpanded}
iconType={isExpanded ? 'arrowUp' : 'arrowDown'}
size="l"
iconSide="right"
flush="left"
>
{title}
</EuiButtonEmpty>
</EuiFlexItem>
{docsLink !== undefined && <EuiFlexItem grow={false}>{docsLink}</EuiFlexItem>}
</EuiFlexGroup>
{headerItems === HEADER_ITEMS_LOADING && <EuiLoadingContent lines={1} />}
{isHeaderItems(headerItems) && (
<EuiFlexGroup>
{headerItems.map(({ label, value, id }) => (
@ -82,13 +93,19 @@ export const ExpandableSection: FC<ExpandableSectionProps> = ({
<EuiBadge>{value}</EuiBadge>
</>
)}
{label === undefined && value}
{label === undefined && (
<EuiText size="xs" color="subdued">
{value}
</EuiText>
)}
</EuiFlexItem>
))}
</EuiFlexGroup>
)}
</div>
{isExpanded && content}
{isExpanded && (
<div className={contentPadding ? 'mlExpandableSection-contentPadding' : ''}>{content}</div>
)}
</EuiPanel>
);
};

View file

@ -0,0 +1,146 @@
/*
* 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, { useEffect, useState, FC } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiHorizontalRule, EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui';
import type { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
import { ml } from '../../../../../services/ml_api_service';
import { getAnalysisType } from '../../../../common';
import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics';
import {
DataFrameAnalyticsListRow,
DATA_FRAME_MODE,
} from '../../../analytics_management/components/analytics_list/common';
import { ExpandedRow } from '../../../analytics_management/components/analytics_list/expanded_row';
import {
ExpandableSection,
ExpandableSectionProps,
HEADER_ITEMS_LOADING,
} from './expandable_section';
const getAnalyticsSectionHeaderItems = (
expandedRowItem: DataFrameAnalyticsListRow | undefined
): ExpandableSectionProps['headerItems'] => {
if (expandedRowItem === undefined) {
return HEADER_ITEMS_LOADING;
}
const sourceIndex = Array.isArray(expandedRowItem.config.source.index)
? expandedRowItem.config.source.index.join()
: expandedRowItem.config.source.index;
return [
{
id: 'analysisTypeLabel',
label: (
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.analysisTypeLabel"
defaultMessage="Type"
/>
),
value: expandedRowItem.job_type,
},
{
id: 'analysisSourceIndexLabel',
label: (
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.analysisSourceIndexLabel"
defaultMessage="Source index"
/>
),
value: sourceIndex,
},
{
id: 'analysisDestinationIndexLabel',
label: (
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.analysisDestinationIndexLabel"
defaultMessage="Destination index"
/>
),
value: expandedRowItem.config.dest.index,
},
];
};
interface ExpandableSectionAnalyticsProps {
jobId: string;
}
export const ExpandableSectionAnalytics: FC<ExpandableSectionAnalyticsProps> = ({ jobId }) => {
const [expandedRowItem, setExpandedRowItem] = useState<DataFrameAnalyticsListRow | undefined>();
const fetchStats = async () => {
const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId);
const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId);
const config = analyticsConfigs.data_frame_analytics[0];
const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
? analyticsStats.data_frame_analytics[0]
: undefined;
if (stats === undefined) {
return;
}
const newExpandedRowItem: DataFrameAnalyticsListRow = {
checkpointing: {},
config,
id: config.id,
job_type: getAnalysisType(config.analysis) as DataFrameAnalysisConfigType,
mode: DATA_FRAME_MODE.BATCH,
state: stats.state,
stats,
};
setExpandedRowItem(newExpandedRowItem);
};
useEffect(() => {
fetchStats();
}, [jobId]);
const analyticsSectionHeaderItems = getAnalyticsSectionHeaderItems(expandedRowItem);
const analyticsSectionContent = (
<>
<EuiHorizontalRule size="full" margin="none" />
{expandedRowItem === undefined && (
<EuiText textAlign="center">
<EuiSpacer size="l" />
<EuiLoadingSpinner size="l" />
<EuiSpacer size="l" />
</EuiText>
)}
{expandedRowItem !== undefined && <ExpandedRow item={expandedRowItem} />}
</>
);
return (
<>
<ExpandableSection
dataTestId="analysis"
content={analyticsSectionContent}
headerItems={analyticsSectionHeaderItems}
isExpanded={false}
title={
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.analysisSectionTitle"
defaultMessage="Analysis"
/>
}
/>
<EuiSpacer size="m" />
</>
);
};

View file

@ -0,0 +1,161 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiDataGridColumn, EuiSpacer, EuiText } from '@elastic/eui';
import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
import {
isClassificationAnalysis,
isRegressionAnalysis,
} from '../../../../../../../common/util/analytics_utils';
import { getToastNotifications } from '../../../../../util/dependency_cache';
import { useColorRange, ColorRangeLegend } from '../../../../../components/color_range_legend';
import { DataGrid, UseIndexDataReturnType } from '../../../../../components/data_grid';
import { SavedSearchQuery } from '../../../../../contexts/ml';
import { defaultSearchQuery, DataFrameAnalyticsConfig, SEARCH_SIZE } from '../../../../common';
import {
ExpandableSection,
ExpandableSectionProps,
HEADER_ITEMS_LOADING,
} from '../expandable_section';
import { IndexPatternPrompt } from '../index_pattern_prompt';
const showingDocs = i18n.translate(
'xpack.ml.dataframe.analytics.explorationResults.documentsShownHelpText',
{
defaultMessage: 'Showing documents for which predictions exist',
}
);
const showingFirstDocs = i18n.translate(
'xpack.ml.dataframe.analytics.explorationResults.firstDocumentsShownHelpText',
{
defaultMessage: 'Showing first {searchSize} documents for which predictions exist',
values: { searchSize: SEARCH_SIZE },
}
);
const getResultsSectionHeaderItems = (
columnsWithCharts: EuiDataGridColumn[],
tableItems: Array<Record<string, any>>,
rowCount: number,
colorRange?: ReturnType<typeof useColorRange>
): ExpandableSectionProps['headerItems'] => {
return columnsWithCharts.length > 0 && tableItems.length > 0
? [
{
id: 'explorationTableTotalDocs',
label: (
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.explorationTableTotalDocsLabel"
defaultMessage="Total docs"
/>
),
value: rowCount,
},
...(colorRange !== undefined
? [
{
id: 'colorRangeLegend',
value: (
<ColorRangeLegend
colorRange={colorRange}
title={i18n.translate(
'xpack.ml.dataframe.analytics.exploration.colorRangeLegendTitle',
{
defaultMessage: 'Feature influence score',
}
)}
/>
),
},
]
: []),
]
: HEADER_ITEMS_LOADING;
};
interface ExpandableSectionResultsProps {
colorRange?: ReturnType<typeof useColorRange>;
indexData: UseIndexDataReturnType;
indexPattern?: IndexPattern;
jobConfig?: DataFrameAnalyticsConfig;
needsDestIndexPattern: boolean;
searchQuery: SavedSearchQuery;
}
export const ExpandableSectionResults: FC<ExpandableSectionResultsProps> = ({
colorRange,
indexData,
indexPattern,
jobConfig,
needsDestIndexPattern,
searchQuery,
}) => {
const { columnsWithCharts, tableItems } = indexData;
// Results section header items and content
const resultsSectionHeaderItems = getResultsSectionHeaderItems(
columnsWithCharts,
tableItems,
indexData.rowCount,
colorRange
);
const resultsSectionContent = (
<>
{jobConfig !== undefined && needsDestIndexPattern && (
<div className="mlExpandableSection-contentPadding">
<IndexPatternPrompt destIndex={jobConfig.dest.index} />
</div>
)}
{jobConfig !== undefined &&
(isRegressionAnalysis(jobConfig.analysis) ||
isClassificationAnalysis(jobConfig.analysis)) && (
<EuiText size="xs" color="subdued" className="mlExpandableSection-contentPadding">
{tableItems.length === SEARCH_SIZE ? showingFirstDocs : showingDocs}
</EuiText>
)}
{(columnsWithCharts.length > 0 || searchQuery !== defaultSearchQuery) &&
indexPattern !== undefined && (
<>
{columnsWithCharts.length > 0 && tableItems.length > 0 && (
<DataGrid
{...indexData}
dataTestSubj="mlExplorationDataGrid"
toastNotifications={getToastNotifications()}
/>
)}
</>
)}
</>
);
return (
<>
<ExpandableSection
dataTestId="results"
content={resultsSectionContent}
headerItems={resultsSectionHeaderItems}
title={
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.explorationTableTitle"
defaultMessage="Results"
/>
}
/>
<EuiSpacer size="m" />
</>
);
};

View file

@ -4,4 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { ExpandableSection, ExpandableSectionProps } from './expandable_section';
export {
ExpandableSection,
ExpandableSectionProps,
HEADER_ITEMS_LOADING,
} from './expandable_section';
export { ExpandableSectionAnalytics } from './expandable_section_analytics';
export { ExpandableSectionResults } from './expandable_section_results';

View file

@ -4,20 +4,49 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FC, useState } from 'react';
import React, { FC, useEffect, useState } from 'react';
import { EuiSpacer } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useResultsViewConfig, DataFrameAnalyticsConfig } from '../../../../common';
import { ResultsSearchQuery, defaultSearchQuery } from '../../../../common/analytics';
import { useUrlState } from '../../../../../util/url_state';
import {
defaultSearchQuery,
getDefaultTrainingFilterQuery,
useResultsViewConfig,
DataFrameAnalyticsConfig,
} from '../../../../common';
import { ResultsSearchQuery } from '../../../../common/analytics';
import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
import { ExpandableSectionAnalytics } from '../expandable_section';
import { ExplorationResultsTable } from '../exploration_results_table';
import { ExplorationQueryBar } from '../exploration_query_bar';
import { JobConfigErrorCallout } from '../job_config_error_callout';
import { LoadingPanel } from '../loading_panel';
import { FeatureImportanceSummaryPanelProps } from '../total_feature_importance_summary/feature_importance_summary';
const filters = {
options: [
{
id: 'training',
label: i18n.translate('xpack.ml.dataframe.analytics.explorationResults.trainingSubsetLabel', {
defaultMessage: 'Training',
}),
},
{
id: 'testing',
label: i18n.translate('xpack.ml.dataframe.analytics.explorationResults.testingSubsetLabel', {
defaultMessage: 'Testing',
}),
},
],
columnId: 'ml.is_training',
key: { training: true, testing: false },
};
export interface EvaluatePanelProps {
jobConfig: DataFrameAnalyticsConfig;
jobStatus?: DATA_FRAME_TASK_STATE;
@ -50,7 +79,25 @@ export const ExplorationPageWrapper: FC<Props> = ({
needsDestIndexPattern,
totalFeatureImportance,
} = useResultsViewConfig(jobId);
const [searchQuery, setSearchQuery] = useState<ResultsSearchQuery>(defaultSearchQuery);
const [globalState, setGlobalState] = useUrlState('_g');
const [defaultQueryString, setDefaultQueryString] = useState<string | undefined>();
useEffect(() => {
if (defaultIsTraining !== undefined && jobConfig !== undefined) {
// Apply defaultIsTraining filter
setSearchQuery(
getDefaultTrainingFilterQuery(jobConfig.dest.results_field, defaultIsTraining)
);
setDefaultQueryString(`${jobConfig.dest.results_field}.is_training : ${defaultIsTraining}`);
// Clear defaultIsTraining from url
setGlobalState('ml', {
analysisType: globalState.ml.analysisType,
jobId: globalState.ml.jobId,
});
}
}, [jobConfig?.dest.results_field]);
if (jobConfigErrorMessage !== undefined || jobCapsServiceErrorMessage !== undefined) {
return (
@ -61,21 +108,54 @@ export const ExplorationPageWrapper: FC<Props> = ({
/>
);
}
return (
<>
{typeof jobConfig?.description !== 'undefined' && (
<>
<EuiText>{jobConfig?.description}</EuiText>
<EuiSpacer size="m" />
</>
)}
{indexPattern !== undefined && (
<>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<EuiSpacer size="s" />
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem>
<ExplorationQueryBar
indexPattern={indexPattern}
setSearchQuery={setSearchQuery}
defaultQueryString={defaultQueryString}
filters={filters}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
</>
)}
{isLoadingJobConfig === true && jobConfig === undefined && <LoadingPanel />}
{isLoadingJobConfig === false && jobConfig !== undefined && isInitialized === true && (
<EvaluatePanel jobConfig={jobConfig} jobStatus={jobStatus} searchQuery={searchQuery} />
<ExpandableSectionAnalytics jobId={jobConfig.id} />
)}
{isLoadingJobConfig === true && totalFeatureImportance === undefined && <LoadingPanel />}
{isLoadingJobConfig === false && totalFeatureImportance !== undefined && (
<>
<EuiSpacer />
<FeatureImportanceSummaryPanel totalFeatureImportance={totalFeatureImportance} />
</>
)}
<EuiSpacer />
{isLoadingJobConfig === true && jobConfig === undefined && <LoadingPanel />}
{isLoadingJobConfig === false && jobConfig !== undefined && isInitialized === true && (
<EvaluatePanel jobConfig={jobConfig} jobStatus={jobStatus} searchQuery={searchQuery} />
)}
{isLoadingJobConfig === true && jobConfig === undefined && <LoadingPanel />}
{isLoadingJobConfig === false &&
jobConfig !== undefined &&
@ -86,9 +166,7 @@ export const ExplorationPageWrapper: FC<Props> = ({
jobConfig={jobConfig}
jobStatus={jobStatus}
needsDestIndexPattern={needsDestIndexPattern}
setEvaluateSearchQuery={setSearchQuery}
title={title}
defaultIsTraining={defaultIsTraining}
searchQuery={searchQuery}
/>
)}
</>

View file

@ -4,118 +4,37 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment, FC, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
import React, { FC } from 'react';
import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
import { DataGrid } from '../../../../../components/data_grid';
import { SavedSearchQuery } from '../../../../../contexts/ml';
import { getToastNotifications } from '../../../../../util/dependency_cache';
import { useMlKibana } from '../../../../../contexts/kibana';
import { DataFrameAnalyticsConfig } from '../../../../common';
import { ResultsSearchQuery } from '../../../../common/analytics';
import {
DataFrameAnalyticsConfig,
MAX_COLUMNS,
SEARCH_SIZE,
defaultSearchQuery,
getAnalysisType,
getDefaultTrainingFilterQuery,
} from '../../../../common';
import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/use_columns';
import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
import { ExplorationTitle } from '../exploration_title';
import { ExplorationQueryBar } from '../exploration_query_bar';
import { IndexPatternPrompt } from '../index_pattern_prompt';
import { ExpandableSectionResults } from '../expandable_section';
import { useExplorationResults } from './use_exploration_results';
import { useMlKibana } from '../../../../../contexts/kibana';
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
import { useUrlState } from '../../../../../util/url_state';
const showingDocs = i18n.translate(
'xpack.ml.dataframe.analytics.explorationResults.documentsShownHelpText',
{
defaultMessage: 'Showing documents for which predictions exist',
}
);
const showingFirstDocs = i18n.translate(
'xpack.ml.dataframe.analytics.explorationResults.firstDocumentsShownHelpText',
{
defaultMessage: 'Showing first {searchSize} documents for which predictions exist',
values: { searchSize: SEARCH_SIZE },
}
);
const filters = {
options: [
{
id: 'training',
label: i18n.translate('xpack.ml.dataframe.analytics.explorationResults.trainingSubsetLabel', {
defaultMessage: 'Training',
}),
},
{
id: 'testing',
label: i18n.translate('xpack.ml.dataframe.analytics.explorationResults.testingSubsetLabel', {
defaultMessage: 'Testing',
}),
},
],
columnId: 'ml.is_training',
key: { training: true, testing: false },
};
interface Props {
indexPattern: IndexPattern;
jobConfig: DataFrameAnalyticsConfig;
jobStatus?: DATA_FRAME_TASK_STATE;
needsDestIndexPattern: boolean;
setEvaluateSearchQuery: React.Dispatch<React.SetStateAction<object>>;
title: string;
defaultIsTraining?: boolean;
searchQuery: ResultsSearchQuery;
}
export const ExplorationResultsTable: FC<Props> = React.memo(
({
indexPattern,
jobConfig,
jobStatus,
needsDestIndexPattern,
setEvaluateSearchQuery,
title,
defaultIsTraining,
}) => {
({ indexPattern, jobConfig, jobStatus, needsDestIndexPattern, searchQuery }) => {
const {
services: {
mlServices: { mlApiServices },
},
} = useMlKibana();
const [globalState, setGlobalState] = useUrlState('_g');
const [searchQuery, setSearchQuery] = useState<SavedSearchQuery>(defaultSearchQuery);
const [defaultQueryString, setDefaultQueryString] = useState<string | undefined>();
useEffect(() => {
setEvaluateSearchQuery(searchQuery);
}, [JSON.stringify(searchQuery)]);
useEffect(() => {
if (defaultIsTraining !== undefined) {
// Apply defaultIsTraining filter
setSearchQuery(
getDefaultTrainingFilterQuery(jobConfig.dest.results_field, defaultIsTraining)
);
setDefaultQueryString(`${jobConfig.dest.results_field}.is_training : ${defaultIsTraining}`);
// Clear defaultIsTraining from url
setGlobalState('ml', {
analysisType: globalState.ml.analysisType,
jobId: globalState.ml.jobId,
});
}
}, []);
const analysisType = getAnalysisType(jobConfig.analysis);
const classificationData = useExplorationResults(
indexPattern,
@ -125,83 +44,20 @@ export const ExplorationResultsTable: FC<Props> = React.memo(
mlApiServices
);
const docFieldsCount = classificationData.columnsWithCharts.length;
const { columnsWithCharts, tableItems, visibleColumns } = classificationData;
if (jobConfig === undefined || classificationData === undefined) {
return null;
}
return (
<EuiPanel
grow={false}
id="mlDataFrameAnalyticsTableResultsPanel"
data-test-subj="mlDFAnalyticsExplorationTablePanel"
>
{needsDestIndexPattern && <IndexPatternPrompt destIndex={jobConfig.dest.index} />}
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" responsive={false}>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<ExplorationTitle title={title} />
</EuiFlexItem>
{jobStatus !== undefined && (
<EuiFlexItem grow={false}>
<span>{getTaskStateBadge(jobStatus)}</span>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
<EuiFlexItem style={{ textAlign: 'right' }}>
{docFieldsCount > MAX_COLUMNS && (
<EuiText size="s">
{i18n.translate(
'xpack.ml.dataframe.analytics.explorationResults.fieldSelection',
{
defaultMessage:
'{selectedFieldsLength, number} of {docFieldsCount, number} {docFieldsCount, plural, one {field} other {fields}} selected',
values: { selectedFieldsLength: visibleColumns.length, docFieldsCount },
}
)}
</EuiText>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
{(columnsWithCharts.length > 0 || searchQuery !== defaultSearchQuery) && (
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<EuiSpacer size="s" />
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem>
<ExplorationQueryBar
indexPattern={indexPattern}
setSearchQuery={setSearchQuery}
defaultQueryString={defaultQueryString}
filters={filters}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFormRow
helpText={tableItems.length === SEARCH_SIZE ? showingFirstDocs : showingDocs}
>
<Fragment />
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<DataGrid
{...classificationData}
dataTestSubj="mlExplorationDataGrid"
toastNotifications={getToastNotifications()}
analysisType={(analysisType as unknown) as DataFrameAnalysisConfigType}
/>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiPanel>
<div data-test-subj="mlDFAnalyticsExplorationTablePanel">
<ExpandableSectionResults
indexData={classificationData}
indexPattern={indexPattern}
jobConfig={jobConfig}
needsDestIndexPattern={needsDestIndexPattern}
searchQuery={searchQuery}
/>
</div>
);
}
);

View file

@ -1,15 +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, { FC } from 'react';
import { EuiTitle } from '@elastic/eui';
export const ExplorationTitle: FC<{ title: string }> = ({ title }) => (
<EuiTitle size="xs">
<span>{title}</span>
</EuiTitle>
);

View file

@ -1,7 +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.
*/
export { ExplorationTitle } from './exploration_title';

View file

@ -6,7 +6,7 @@
import React, { FC } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
import { EuiLink, EuiText } from '@elastic/eui';
import { useMlKibana } from '../../../../../contexts/kibana';
interface Props {
@ -42,7 +42,6 @@ export const IndexPatternPrompt: FC<Props> = ({ destIndex }) => {
}}
/>
</EuiText>
<EuiSpacer size="m" />
</>
);
};

View file

@ -10,7 +10,6 @@ import { EuiCallOut, EuiLink, EuiPanel, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ExplorationTitle } from '../exploration_title';
import { useMlKibana } from '../../../../../contexts/kibana';
const jobConfigErrorTitle = i18n.translate('xpack.ml.dataframe.analytics.jobConfig.errorTitle', {
@ -63,7 +62,6 @@ export const JobConfigErrorCallout: FC<Props> = ({
return (
<EuiPanel grow={false}>
<ExplorationTitle title={title} />
<EuiSpacer />
<EuiCallOut
title={jobConfigErrorMessage ? jobConfigErrorTitle : jobCapsErrorTitle}

View file

@ -5,10 +5,13 @@
*/
import React, { FC } from 'react';
import { EuiLoadingSpinner, EuiPanel } from '@elastic/eui';
import { EuiLoadingSpinner, EuiPanel, EuiSpacer } from '@elastic/eui';
export const LoadingPanel: FC = () => (
<EuiPanel className="eui-textCenter">
<EuiLoadingSpinner size="xl" />
</EuiPanel>
<>
<EuiPanel className="eui-textCenter">
<EuiLoadingSpinner size="xl" />
</EuiPanel>
<EuiSpacer size="m" />
</>
);

View file

@ -4,124 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useEffect, useState, FC } from 'react';
import React, { useState, FC } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiDataGridColumn,
EuiHorizontalRule,
EuiLoadingSpinner,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import type { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
import { EuiSpacer, EuiText } from '@elastic/eui';
import {
useColorRange,
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 { ml } from '../../../../../services/ml_api_service';
import { getAnalysisType, defaultSearchQuery, useResultsViewConfig } from '../../../../common';
import { defaultSearchQuery, useResultsViewConfig } from '../../../../common';
import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics';
import {
DataFrameAnalyticsListRow,
DATA_FRAME_MODE,
} from '../../../analytics_management/components/analytics_list/common';
import { ExpandedRow } from '../../../analytics_management/components/analytics_list/expanded_row';
import { ExpandableSection, ExpandableSectionProps } from '../expandable_section';
import { ExpandableSectionAnalytics, ExpandableSectionResults } from '../expandable_section';
import { ExplorationQueryBar } from '../exploration_query_bar';
import { IndexPatternPrompt } from '../index_pattern_prompt';
import { getFeatureCount } from './common';
import { useOutlierData } from './use_outlier_data';
const getAnalyticsSectionHeaderItems = (
expandedRowItem: DataFrameAnalyticsListRow | undefined
): ExpandableSectionProps['headerItems'] => {
return expandedRowItem !== undefined
? [
{
id: 'analysisTypeLabel',
label: (
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.analysisTypeLabel"
defaultMessage="Type"
/>
),
value: expandedRowItem.job_type,
},
{
id: 'analysisSourceIndexLabel',
label: (
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.analysisSourceIndexLabel"
defaultMessage="Source index"
/>
),
value: expandedRowItem.config.source.index,
},
{
id: 'analysisDestinationIndexLabel',
label: (
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.analysisDestinationIndexLabel"
defaultMessage="Destination index"
/>
),
value: expandedRowItem.config.dest.index,
},
]
: 'loading';
};
const getResultsSectionHeaderItems = (
columnsWithCharts: EuiDataGridColumn[],
tableItems: Array<Record<string, any>>,
rowCount: number,
colorRange: ReturnType<typeof useColorRange>
): ExpandableSectionProps['headerItems'] => {
return columnsWithCharts.length > 0 && tableItems.length > 0
? [
{
id: 'explorationTableTotalDocs',
label: (
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.explorationTableTotalDocsLabel"
defaultMessage="Total docs"
/>
),
value: rowCount,
},
{
id: 'colorRangeLegend',
value: (
<ColorRangeLegend
colorRange={colorRange}
title={i18n.translate(
'xpack.ml.dataframe.analytics.exploration.colorRangeLegendTitle',
{
defaultMessage: 'Feature influence score',
}
)}
/>
),
},
]
: 'loading';
};
export type TableItem = Record<string, any>;
interface ExplorationProps {
@ -141,89 +42,14 @@ export const OutlierExploration: FC<ExplorationProps> = React.memo(({ jobId }) =
jobConfig !== undefined ? getFeatureCount(jobConfig.dest.results_field, tableItems) : 1
);
const [expandedRowItem, setExpandedRowItem] = useState<DataFrameAnalyticsListRow | undefined>();
const fetchStats = async () => {
const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId);
const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId);
const config = analyticsConfigs.data_frame_analytics[0];
const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
? analyticsStats.data_frame_analytics[0]
: undefined;
if (stats === undefined) {
return;
}
const newExpandedRowItem: DataFrameAnalyticsListRow = {
checkpointing: {},
config,
id: config.id,
job_type: getAnalysisType(config.analysis) as DataFrameAnalysisConfigType,
mode: DATA_FRAME_MODE.BATCH,
state: stats.state,
stats,
};
setExpandedRowItem(newExpandedRowItem);
};
useEffect(() => {
fetchStats();
}, [jobConfig?.id]);
// Analytics section header items and content
const analyticsSectionHeaderItems = getAnalyticsSectionHeaderItems(expandedRowItem);
const analyticsSectionContent = (
<>
<EuiHorizontalRule size="full" margin="none" />
{expandedRowItem === undefined && (
<EuiText textAlign="center">
<EuiSpacer size="l" />
<EuiLoadingSpinner size="l" />
<EuiSpacer size="l" />
</EuiText>
)}
{(columnsWithCharts.length > 0 || searchQuery !== defaultSearchQuery) &&
indexPattern !== undefined &&
jobConfig !== undefined &&
columnsWithCharts.length > 0 &&
tableItems.length > 0 &&
expandedRowItem !== undefined && <ExpandedRow item={expandedRowItem} />}
</>
);
// Results section header items and content
const resultsSectionHeaderItems = getResultsSectionHeaderItems(
columnsWithCharts,
tableItems,
outlierData.rowCount,
colorRange
);
const resultsSectionContent = (
<>
{jobConfig !== undefined && needsDestIndexPattern && (
<IndexPatternPrompt destIndex={jobConfig.dest.index} />
)}
{(columnsWithCharts.length > 0 || searchQuery !== defaultSearchQuery) &&
indexPattern !== undefined && (
<>
<EuiSpacer size="s" />
{columnsWithCharts.length > 0 && tableItems.length > 0 && (
<DataGrid
{...outlierData}
dataTestSubj="mlExplorationDataGrid"
toastNotifications={getToastNotifications()}
/>
)}
</>
)}
</>
);
return (
<>
{typeof jobConfig?.description !== 'undefined' && (
<>
<EuiText>{jobConfig?.description}</EuiText>
<EuiSpacer size="m" />
</>
)}
{(columnsWithCharts.length > 0 || searchQuery !== defaultSearchQuery) &&
indexPattern !== undefined && (
<>
@ -231,34 +57,15 @@ export const OutlierExploration: FC<ExplorationProps> = React.memo(({ jobId }) =
<EuiSpacer size="m" />
</>
)}
<ExpandableSection
dataTestId="analysis"
content={analyticsSectionContent}
headerItems={analyticsSectionHeaderItems}
isExpanded={false}
title={
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.analysisSectionTitle"
defaultMessage="Analysis"
/>
}
{typeof jobConfig?.id === 'string' && <ExpandableSectionAnalytics jobId={jobConfig?.id} />}
<ExpandableSectionResults
colorRange={colorRange}
indexData={outlierData}
indexPattern={indexPattern}
jobConfig={jobConfig}
needsDestIndexPattern={needsDestIndexPattern}
searchQuery={searchQuery}
/>
<EuiSpacer size="m" />
<ExpandableSection
dataTestId="results"
content={resultsSectionContent}
headerItems={resultsSectionHeaderItems}
title={
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.explorationTableTitle"
defaultMessage="Results"
/>
}
/>
<EuiSpacer size="m" />
</>
);
});

View file

@ -11,7 +11,6 @@ import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
@ -27,9 +26,7 @@ import {
Eval,
DataFrameAnalyticsConfig,
} from '../../../../common';
import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/use_columns';
import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
import { EvaluateStat } from './evaluate_stat';
import {
isResultsSearchBoolQuery,
isRegressionEvaluateResponse,
@ -38,6 +35,10 @@ import {
EMPTY_STAT,
} from '../../../../common/analytics';
import { ExpandableSection } from '../expandable_section';
import { EvaluateStat } from './evaluate_stat';
interface Props {
jobConfig: DataFrameAnalyticsConfig;
jobStatus?: DATA_FRAME_TASK_STATE;
@ -219,30 +220,16 @@ export const EvaluatePanel: FC<Props> = ({ jobConfig, jobStatus, searchQuery })
}, [JSON.stringify(searchQuery)]);
return (
<EuiPanel data-test-subj="mlDFAnalyticsRegressionExplorationEvaluatePanel">
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<span>
{i18n.translate(
'xpack.ml.dataframe.analytics.regressionExploration.evaluateJobIdTitle',
{
defaultMessage: 'Evaluation of regression job ID {jobId}',
values: { jobId: jobConfig.id },
}
)}
</span>
</EuiTitle>
</EuiFlexItem>
{jobStatus !== undefined && (
<EuiFlexItem grow={false}>
<span>{getTaskStateBadge(jobStatus)}</span>
</EuiFlexItem>
)}
<EuiFlexItem>
<EuiSpacer />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<>
<ExpandableSection
dataTestId="RegressionEvaluation"
title={
<FormattedMessage
id="xpack.ml.dataframe.analytics.regressionExploration.evaluateSectionTitle"
defaultMessage="Model evaluation"
/>
}
docsLink={
<EuiButtonEmpty
target="_blank"
iconType="help"
@ -257,195 +244,214 @@ export const EvaluatePanel: FC<Props> = ({ jobConfig, jobStatus, searchQuery })
}
)}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
}
headerItems={
jobStatus !== undefined
? [
{
id: 'jobStatus',
label: i18n.translate(
'xpack.ml.dataframe.analytics.classificationExploration.evaluateJobStatusLabel',
{
defaultMessage: 'Job status',
}
),
value: jobStatus,
},
]
: []
}
contentPadding={true}
content={
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem>
<EuiTitle size="xxs">
<span>
{i18n.translate(
'xpack.ml.dataframe.analytics.regressionExploration.generalizationErrorTitle',
{
defaultMessage: 'Generalization error',
}
)}
</span>
</EuiTitle>
{generalizationDocsCount !== null && (
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analytics.regressionExploration.generalizationDocsCount"
defaultMessage="{docsCount, plural, one {# doc} other {# docs}} evaluated"
values={{ docsCount: generalizationDocsCount }}
/>
{isTrainingFilter === true && generalizationDocsCount === 0 && (
<FormattedMessage
id="xpack.ml.dataframe.analytics.regressionExploration.generalizationFilterText"
defaultMessage=". Filtering for training data."
/>
)}
</EuiText>
)}
<EuiSpacer />
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem>
<EuiFlexGroup direction="column" gutterSize="s">
{/* First row stats */}
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionGenMSEstat'}
isLoading={isLoadingGeneralization}
title={generalizationEval.mse}
statType={REGRESSION_STATS.MSE}
/>
</EuiFlexItem>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionGenRSquaredStat'}
isLoading={isLoadingGeneralization}
title={generalizationEval.rSquared}
statType={REGRESSION_STATS.R_SQUARED}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{/* Second row stats */}
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionGenMsleStat'}
isLoading={isLoadingGeneralization}
title={generalizationEval.msle}
statType={REGRESSION_STATS.MSLE}
/>
</EuiFlexItem>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionGenHuberStat'}
isLoading={isLoadingGeneralization}
title={generalizationEval.huber}
statType={REGRESSION_STATS.HUBER}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{generalizationEval.error !== null && (
<EuiFlexItem grow={false}>
<EuiText size="xs" color="danger">
{isTrainingFilter === true &&
generalizationDocsCount === 0 &&
generalizationEval.error.includes('No documents found')
? i18n.translate(
'xpack.ml.dataframe.analytics.regressionExploration.evaluateNoTestingDocsError',
{
defaultMessage: 'No testing documents found',
}
)
: generalizationEval.error}
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xxs">
<span>
{i18n.translate(
'xpack.ml.dataframe.analytics.regressionExploration.trainingErrorTitle',
{
defaultMessage: 'Training error',
}
)}
</span>
</EuiTitle>
{trainingDocsCount !== null && (
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analytics.regressionExploration.trainingDocsCount"
defaultMessage="{docsCount, plural, one {# doc} other {# docs}} evaluated"
values={{ docsCount: trainingDocsCount }}
/>
{isTrainingFilter === false && trainingDocsCount === 0 && (
<FormattedMessage
id="xpack.ml.dataframe.analytics.regressionExploration.trainingFilterText"
defaultMessage=". Filtering for testing data."
/>
)}
</EuiText>
)}
<EuiSpacer />
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem>
<EuiFlexGroup direction="column" gutterSize="s">
{/* First row stats */}
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionTrainingMSEstat'}
isLoading={isLoadingTraining}
title={trainingEval.mse}
statType={REGRESSION_STATS.MSE}
/>
</EuiFlexItem>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionTrainingRSquaredStat'}
isLoading={isLoadingTraining}
title={trainingEval.rSquared}
statType={REGRESSION_STATS.R_SQUARED}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{/* Second row stats */}
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionTrainingMsleStat'}
isLoading={isLoadingTraining}
title={trainingEval.msle}
statType={REGRESSION_STATS.MSLE}
/>
</EuiFlexItem>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionTrainingHuberStat'}
isLoading={isLoadingTraining}
title={trainingEval.huber}
statType={REGRESSION_STATS.HUBER}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{trainingEval.error !== null && (
<EuiFlexItem grow={false}>
<EuiText size="xs" color="danger">
{isTrainingFilter === false &&
trainingDocsCount === 0 &&
trainingEval.error.includes('No documents found')
? i18n.translate(
'xpack.ml.dataframe.analytics.regressionExploration.evaluateNoTrainingDocsError',
{
defaultMessage: 'No training documents found',
}
)
: trainingEval.error}
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
}
/>
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem>
<EuiTitle size="xxs">
<span>
{i18n.translate(
'xpack.ml.dataframe.analytics.regressionExploration.generalizationErrorTitle',
{
defaultMessage: 'Generalization error',
}
)}
</span>
</EuiTitle>
{generalizationDocsCount !== null && (
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analytics.regressionExploration.generalizationDocsCount"
defaultMessage="{docsCount, plural, one {# doc} other {# docs}} evaluated"
values={{ docsCount: generalizationDocsCount }}
/>
{isTrainingFilter === true && generalizationDocsCount === 0 && (
<FormattedMessage
id="xpack.ml.dataframe.analytics.regressionExploration.generalizationFilterText"
defaultMessage=". Filtering for training data."
/>
)}
</EuiText>
)}
<EuiSpacer />
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem>
<EuiFlexGroup direction="column" gutterSize="s">
{/* First row stats */}
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionGenMSEstat'}
isLoading={isLoadingGeneralization}
title={generalizationEval.mse}
statType={REGRESSION_STATS.MSE}
/>
</EuiFlexItem>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionGenRSquaredStat'}
isLoading={isLoadingGeneralization}
title={generalizationEval.rSquared}
statType={REGRESSION_STATS.R_SQUARED}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{/* Second row stats */}
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionGenMsleStat'}
isLoading={isLoadingGeneralization}
title={generalizationEval.msle}
statType={REGRESSION_STATS.MSLE}
/>
</EuiFlexItem>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionGenHuberStat'}
isLoading={isLoadingGeneralization}
title={generalizationEval.huber}
statType={REGRESSION_STATS.HUBER}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{generalizationEval.error !== null && (
<EuiFlexItem grow={false}>
<EuiText size="xs" color="danger">
{isTrainingFilter === true &&
generalizationDocsCount === 0 &&
generalizationEval.error.includes('No documents found')
? i18n.translate(
'xpack.ml.dataframe.analytics.regressionExploration.evaluateNoTestingDocsError',
{
defaultMessage: 'No testing documents found',
}
)
: generalizationEval.error}
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xxs">
<span>
{i18n.translate(
'xpack.ml.dataframe.analytics.regressionExploration.trainingErrorTitle',
{
defaultMessage: 'Training error',
}
)}
</span>
</EuiTitle>
{trainingDocsCount !== null && (
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analytics.regressionExploration.trainingDocsCount"
defaultMessage="{docsCount, plural, one {# doc} other {# docs}} evaluated"
values={{ docsCount: trainingDocsCount }}
/>
{isTrainingFilter === false && trainingDocsCount === 0 && (
<FormattedMessage
id="xpack.ml.dataframe.analytics.regressionExploration.trainingFilterText"
defaultMessage=". Filtering for testing data."
/>
)}
</EuiText>
)}
<EuiSpacer />
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem>
<EuiFlexGroup direction="column" gutterSize="s">
{/* First row stats */}
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionTrainingMSEstat'}
isLoading={isLoadingTraining}
title={trainingEval.mse}
statType={REGRESSION_STATS.MSE}
/>
</EuiFlexItem>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionTrainingRSquaredStat'}
isLoading={isLoadingTraining}
title={trainingEval.rSquared}
statType={REGRESSION_STATS.R_SQUARED}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{/* Second row stats */}
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionTrainingMsleStat'}
isLoading={isLoadingTraining}
title={trainingEval.msle}
statType={REGRESSION_STATS.MSLE}
/>
</EuiFlexItem>
<EuiFlexItem>
<EvaluateStat
dataTestSubj={'mlDFAnalyticsRegressionTrainingHuberStat'}
isLoading={isLoadingTraining}
title={trainingEval.huber}
statType={REGRESSION_STATS.HUBER}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{trainingEval.error !== null && (
<EuiFlexItem grow={false}>
<EuiText size="xs" color="danger">
{isTrainingFilter === false &&
trainingDocsCount === 0 &&
trainingEval.error.includes('No documents found')
? i18n.translate(
'xpack.ml.dataframe.analytics.regressionExploration.evaluateNoTrainingDocsError',
{
defaultMessage: 'No training documents found',
}
)
: trainingEval.error}
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</>
);
};

View file

@ -5,15 +5,7 @@
*/
import React, { FC, useCallback, useMemo } from 'react';
import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiPanel,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { EuiButtonEmpty, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import {
Chart,
@ -38,6 +30,9 @@ import {
} from '../../../../../../../common/types/feature_importance';
import { useMlKibana } from '../../../../../contexts/kibana';
import { ExpandableSection } from '../expandable_section';
const { euiColorMediumShade } = euiVars;
const axisColor = euiColorMediumShade;
@ -194,71 +189,67 @@ export const FeatureImportanceSummaryPanel: FC<FeatureImportanceSummaryPanelProp
const tickFormatter = useCallback((d) => Number(d.toPrecision(3)).toString(), []);
return (
<EuiPanel>
<div>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem>
<EuiFlexGroup gutterSize="xs">
<EuiTitle size="xs">
<span>
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.featureImportanceSummaryTitle"
defaultMessage="Total feature importance"
/>
</span>
</EuiTitle>
<EuiFlexItem grow={false}>
<EuiIconTip content={tooltipContent} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiSpacer />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
target="_blank"
iconType="help"
iconSide="left"
color="primary"
href={`${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-feature-importance.html`}
>
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.featureImportanceDocsLink"
defaultMessage="Feature importance docs"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</div>
<Chart
size={{
width: '100%',
height: chartHeight,
}}
>
<Settings rotation={90} theme={theme} showLegend={showLegend} />
<>
<ExpandableSection
dataTestId="FeatureImportanceSummary"
title={
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.featureImportanceSummaryTitle"
defaultMessage="Total feature importance"
/>
}
docsLink={
<EuiButtonEmpty
target="_blank"
iconType="help"
iconSide="left"
color="primary"
href={`${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-feature-importance.html`}
>
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.featureImportanceDocsLink"
defaultMessage="Feature importance docs"
/>
</EuiButtonEmpty>
}
headerItems={[
{
id: 'FeatureImportanceSummary',
value: tooltipContent,
},
]}
content={
<Chart
size={{
width: '100%',
height: chartHeight,
}}
>
<Settings rotation={90} theme={theme} showLegend={showLegend} />
<Axis
id="x-axis"
title={i18n.translate(
'xpack.ml.dataframe.analytics.exploration.featureImportanceXAxisTitle',
{
defaultMessage: 'Feature importance average magnitude',
}
)}
position={Position.Bottom}
tickFormat={tickFormatter}
/>
<Axis id="y-axis" title="" position={Position.Left} />
<BarSeries
id="magnitude"
xScaleType={ScaleType.Ordinal}
yScaleType={ScaleType.Linear}
data={plotData}
{...barSeriesSpec}
/>
</Chart>
</EuiPanel>
<Axis
id="x-axis"
title={i18n.translate(
'xpack.ml.dataframe.analytics.exploration.featureImportanceXAxisTitle',
{
defaultMessage: 'Feature importance average magnitude',
}
)}
position={Position.Bottom}
tickFormat={tickFormatter}
/>
<Axis id="y-axis" title="" position={Position.Left} />
<BarSeries
id="magnitude"
xScaleType={ScaleType.Ordinal}
yScaleType={ScaleType.Linear}
data={plotData}
{...barSeriesSpec}
/>
</Chart>
}
/>
<EuiSpacer size="m" />
</>
);
};

View file

@ -12,7 +12,6 @@ import {
EuiPageContentBody,
EuiPageContentHeader,
EuiPageContentHeaderSection,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
@ -42,7 +41,6 @@ export const Page: FC<{
</EuiPageContentHeaderSection>
</EuiPageContentHeader>
<EuiPageContentBody style={{ maxWidth: 'calc(100% - 0px)' }}>
<EuiSpacer size="m" />
{analysisType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION && (
<OutlierExploration jobId={jobId} />
)}

View file

@ -10550,7 +10550,6 @@
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTestingHelpText": "データセットをテストするための正規化された混同行列",
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTooltip": "マルチクラス混同行列には、分析が実際のクラスで正しくデータポイントを分類した発生数と、別のクラスで誤分類した発生数が含まれます。",
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTrainingHelpText": "データセットを学習するための正規化された混同行列",
"xpack.ml.dataframe.analytics.classificationExploration.evaluateJobIdTitle": "分類ジョブID {jobId}の評価",
"xpack.ml.dataframe.analytics.classificationExploration.generalizationDocsCount": "{docsCount, plural, one {# doc} other {# docs}}が評価されました",
"xpack.ml.dataframe.analytics.classificationExploration.showActions": "アクションを表示",
"xpack.ml.dataframe.analytics.classificationExploration.showAllColumns": "すべての列を表示",
@ -10753,13 +10752,11 @@
"xpack.ml.dataframe.analytics.errorCallout.queryParsingErrorTitle": "クエリをパースできません。",
"xpack.ml.dataframe.analytics.exploration.colorRangeLegendTitle": "機能影響スコア",
"xpack.ml.dataframe.analytics.explorationResults.documentsShownHelpText": "予測があるドキュメントを示す",
"xpack.ml.dataframe.analytics.explorationResults.fieldSelection": "{docFieldsCount, number}件中 showing {selectedFieldsLength, number}件の{docFieldsCount, plural, one {フィールド} other {フィールド}}",
"xpack.ml.dataframe.analytics.explorationResults.firstDocumentsShownHelpText": "予測がある最初の{searchSize}のドキュメントを示す",
"xpack.ml.dataframe.analytics.indexPatternPromptLinkText": "インデックスパターンを作成します",
"xpack.ml.dataframe.analytics.indexPatternPromptMessage": "{destIndex}のインデックス{destIndex}. {linkToIndexPatternManagement}にはインデックスパターンが存在しません。",
"xpack.ml.dataframe.analytics.jobCaps.errorTitle": "結果を取得できません。インデックスのフィールドデータの読み込み中にエラーが発生しました。",
"xpack.ml.dataframe.analytics.jobConfig.errorTitle": "結果を取得できません。ジョブ構成データの読み込み中にエラーが発生しました。",
"xpack.ml.dataframe.analytics.regressionExploration.evaluateJobIdTitle": "回帰ジョブID {jobId}の評価",
"xpack.ml.dataframe.analytics.regressionExploration.generalizationDocsCount": "{docsCount, plural, one {# doc} other {# docs}}が評価されました",
"xpack.ml.dataframe.analytics.regressionExploration.generalizationErrorTitle": "一般化エラー",
"xpack.ml.dataframe.analytics.regressionExploration.generalizationFilterText": ".学習データをフィルタリングしています。",

View file

@ -10556,7 +10556,6 @@
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTestingHelpText": "用于测试数据集的标准化混淆矩阵",
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTooltip": "多类混淆矩阵包含分析使用数据点的实际类正确分类数据点的次数以及分析使用其他类错误分类这些数据点的次数",
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixTrainingHelpText": "用于训练数据集的标准化混淆矩阵",
"xpack.ml.dataframe.analytics.classificationExploration.evaluateJobIdTitle": "分类作业 ID {jobId} 的评估",
"xpack.ml.dataframe.analytics.classificationExploration.generalizationDocsCount": "{docsCount, plural, one {# 个文档} other {# 个文档}}已评估",
"xpack.ml.dataframe.analytics.classificationExploration.showActions": "显示操作",
"xpack.ml.dataframe.analytics.classificationExploration.showAllColumns": "显示所有列",
@ -10759,13 +10758,11 @@
"xpack.ml.dataframe.analytics.errorCallout.queryParsingErrorTitle": "无法解析查询。",
"xpack.ml.dataframe.analytics.exploration.colorRangeLegendTitle": "功能影响分数",
"xpack.ml.dataframe.analytics.explorationResults.documentsShownHelpText": "正在显示有相关预测存在的文档",
"xpack.ml.dataframe.analytics.explorationResults.fieldSelection": "已选择 {docFieldsCount, number} 个{docFieldsCount, plural, one {字段} other {字段}}中的 {selectedFieldsLength, number} 个",
"xpack.ml.dataframe.analytics.explorationResults.firstDocumentsShownHelpText": "正在显示有相关预测存在的前 {searchSize} 个文档",
"xpack.ml.dataframe.analytics.indexPatternPromptLinkText": "创建索引模式",
"xpack.ml.dataframe.analytics.indexPatternPromptMessage": "不存在索引 {destIndex} 的索引模式。{destIndex} 的{linkToIndexPatternManagement}。",
"xpack.ml.dataframe.analytics.jobCaps.errorTitle": "无法提取结果。加载索引的字段数据时发生错误。",
"xpack.ml.dataframe.analytics.jobConfig.errorTitle": "无法提取结果。加载作业配置数据时发生错误。",
"xpack.ml.dataframe.analytics.regressionExploration.evaluateJobIdTitle": "回归作业 ID {jobId} 的评估",
"xpack.ml.dataframe.analytics.regressionExploration.generalizationDocsCount": "{docsCount, plural, one {# 个文档} other {# 个文档}}已评估",
"xpack.ml.dataframe.analytics.regressionExploration.generalizationErrorTitle": "泛化误差",
"xpack.ml.dataframe.analytics.regressionExploration.generalizationFilterText": ".筛留训练数据。",

View file

@ -15,7 +15,7 @@ export function MachineLearningDataFrameAnalyticsResultsProvider({
return {
async assertRegressionEvaluatePanelElementsExists() {
await testSubjects.existOrFail('mlDFAnalyticsRegressionExplorationEvaluatePanel');
await testSubjects.existOrFail('mlDFExpandableSection-RegressionEvaluation');
await testSubjects.existOrFail('mlDFAnalyticsRegressionGenMSEstat');
await testSubjects.existOrFail('mlDFAnalyticsRegressionGenRSquaredStat');
await testSubjects.existOrFail('mlDFAnalyticsRegressionTrainingMSEstat');
@ -27,7 +27,7 @@ export function MachineLearningDataFrameAnalyticsResultsProvider({
},
async assertClassificationEvaluatePanelElementsExists() {
await testSubjects.existOrFail('mlDFAnalyticsClassificationExplorationEvaluatePanel');
await testSubjects.existOrFail('mlDFExpandableSection-ClassificationEvaluation');
await testSubjects.existOrFail('mlDFAnalyticsClassificationExplorationConfusionMatrix');
},