[ML] DF Analytics Regression results: Ensure error handling and table sort works correctly (#49929)
* add error handling for regression jobConfig fetch * fix sorting change causing blank table by removing table render timeout * Add label to table for number of docs obtained * parameterize searchSize in documents fetched text
This commit is contained in:
parent
128948c2a7
commit
568b8d3992
|
@ -1,5 +1,4 @@
|
|||
@import 'pages/analytics_exploration/components/exploration/index';
|
||||
@import 'pages/analytics_exploration/components/regression_exploration/index';
|
||||
@import 'pages/analytics_management/components/analytics_list/index';
|
||||
@import 'pages/analytics_management/components/create_analytics_form/index';
|
||||
@import 'pages/analytics_management/components/create_analytics_flyout/index';
|
||||
|
|
|
@ -211,14 +211,7 @@ export const getDefaultRegressionFields = (
|
|||
return false;
|
||||
}
|
||||
|
||||
let value = false;
|
||||
docs.forEach(row => {
|
||||
const source = row._source;
|
||||
if (source[k] !== null) {
|
||||
value = true;
|
||||
}
|
||||
});
|
||||
return value;
|
||||
return docs.some(row => row._source[k] !== null);
|
||||
})
|
||||
.sort((a, b) => sortRegressionResultsFields(a, b, jobConfig))
|
||||
.slice(0, DEFAULT_REGRESSION_COLUMNS);
|
||||
|
@ -239,14 +232,7 @@ export const getDefaultSelectableFields = (docs: EsDoc[], resultsField: string):
|
|||
return false;
|
||||
}
|
||||
|
||||
let value = false;
|
||||
docs.forEach(row => {
|
||||
const source = row._source;
|
||||
if (source[k] !== null) {
|
||||
value = true;
|
||||
}
|
||||
});
|
||||
return value;
|
||||
return docs.some(row => row._source[k] !== null);
|
||||
})
|
||||
.slice(0, MAX_COLUMNS);
|
||||
};
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import 'regression_exploration';
|
|
@ -1,3 +0,0 @@
|
|||
.mlRegressionExploration__evaluateLoadingSpinner {
|
||||
display: inline-block;
|
||||
}
|
|
@ -7,7 +7,6 @@
|
|||
import React, { FC, Fragment, useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiStat, EuiTitle } from '@elastic/eui';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { ErrorCallout } from './error_callout';
|
||||
import {
|
||||
getValuesFromResponse,
|
||||
|
@ -45,7 +44,7 @@ export const EvaluatePanel: FC<Props> = ({ jobConfig, jobStatus }) => {
|
|||
const [isLoadingTraining, setIsLoadingTraining] = useState<boolean>(false);
|
||||
const [isLoadingGeneralization, setIsLoadingGeneralization] = useState<boolean>(false);
|
||||
|
||||
const index = idx(jobConfig, _ => _.dest.index) as string;
|
||||
const index = jobConfig.dest.index;
|
||||
const dependentVariable = getDependentVar(jobConfig.analysis);
|
||||
const predictionFieldName = getPredictionFieldName(jobConfig.analysis);
|
||||
// default is 'ml'
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
*/
|
||||
|
||||
import React, { FC, Fragment, useState, useEffect } from 'react';
|
||||
import { EuiSpacer, EuiLoadingSpinner, EuiPanel } from '@elastic/eui';
|
||||
import { EuiCallOut, EuiLoadingSpinner, 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';
|
||||
|
@ -18,11 +19,22 @@ interface GetDataFrameAnalyticsResponse {
|
|||
}
|
||||
|
||||
const LoadingPanel: FC = () => (
|
||||
<EuiPanel>
|
||||
<EuiLoadingSpinner className="mlRegressionExploration__evaluateLoadingSpinner" size="xl" />
|
||||
<EuiPanel className="eui-textCenter">
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiPanel>
|
||||
);
|
||||
|
||||
export const ExplorationTitle: React.SFC<{ jobId: string }> = ({ jobId }) => (
|
||||
<EuiTitle size="xs">
|
||||
<span>
|
||||
{i18n.translate('xpack.ml.dataframe.analytics.regressionExploration.jobIdTitle', {
|
||||
defaultMessage: 'Regression job ID {jobId}',
|
||||
values: { jobId },
|
||||
})}
|
||||
</span>
|
||||
</EuiTitle>
|
||||
);
|
||||
|
||||
interface Props {
|
||||
jobId: string;
|
||||
jobStatus: DATA_FRAME_TASK_STATE;
|
||||
|
@ -31,10 +43,11 @@ interface Props {
|
|||
export const RegressionExploration: FC<Props> = ({ jobId, jobStatus }) => {
|
||||
const [jobConfig, setJobConfig] = useState<DataFrameAnalyticsConfig | undefined>(undefined);
|
||||
const [isLoadingJobConfig, setIsLoadingJobConfig] = useState<boolean>(false);
|
||||
const [jobConfigErrorMessage, setJobConfigErrorMessage] = useState<undefined | string>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
(async function() {
|
||||
setIsLoadingJobConfig(true);
|
||||
const loadJobConfig = async () => {
|
||||
setIsLoadingJobConfig(true);
|
||||
try {
|
||||
const analyticsConfigs: GetDataFrameAnalyticsResponse = await ml.dataFrameAnalytics.getDataFrameAnalytics(
|
||||
jobId
|
||||
);
|
||||
|
@ -45,9 +58,42 @@ export const RegressionExploration: FC<Props> = ({ jobId, jobStatus }) => {
|
|||
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();
|
||||
}, []);
|
||||
|
||||
if (jobConfigErrorMessage !== undefined) {
|
||||
return (
|
||||
<EuiPanel grow={false}>
|
||||
<ExplorationTitle jobId={jobId} />
|
||||
<EuiSpacer />
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.ml.dataframe.analytics.regressionExploration.jobConfigurationFetchError',
|
||||
{
|
||||
defaultMessage:
|
||||
'Unable to fetch results. An error occurred loading the job configuration data.',
|
||||
}
|
||||
)}
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
>
|
||||
<p>{jobConfigErrorMessage}</p>
|
||||
</EuiCallOut>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{isLoadingJobConfig === true && jobConfig === undefined && <LoadingPanel />}
|
||||
|
|
|
@ -15,13 +15,13 @@ import {
|
|||
EuiCheckbox,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiPanel,
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiProgress,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiToolTip,
|
||||
Query,
|
||||
} from '@elastic/eui';
|
||||
|
@ -49,25 +49,16 @@ import {
|
|||
MAX_COLUMNS,
|
||||
getPredictedFieldName,
|
||||
INDEX_STATUS,
|
||||
SEARCH_SIZE,
|
||||
} from '../../../../common';
|
||||
import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns';
|
||||
import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
|
||||
|
||||
import { useExploreData, defaultSearchQuery } from './use_explore_data';
|
||||
import { ExplorationTitle } from './regression_exploration';
|
||||
|
||||
const PAGE_SIZE_OPTIONS = [5, 10, 25, 50];
|
||||
|
||||
const ExplorationTitle: React.SFC<{ jobId: string }> = ({ jobId }) => (
|
||||
<EuiTitle size="xs">
|
||||
<span>
|
||||
{i18n.translate('xpack.ml.dataframe.analytics.regressionExploration.jobIdTitle', {
|
||||
defaultMessage: 'Regression job ID {jobId}',
|
||||
values: { jobId },
|
||||
})}
|
||||
</span>
|
||||
</EuiTitle>
|
||||
);
|
||||
|
||||
interface Props {
|
||||
jobConfig: DataFrameAnalyticsConfig;
|
||||
jobStatus: DATA_FRAME_TASK_STATE;
|
||||
|
@ -76,25 +67,12 @@ interface Props {
|
|||
export const ResultsTable: FC<Props> = React.memo(({ jobConfig, jobStatus }) => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(25);
|
||||
const [clearTable, setClearTable] = useState(false);
|
||||
const [selectedFields, setSelectedFields] = useState([] as EsFieldName[]);
|
||||
const [isColumnsPopoverVisible, setColumnsPopoverVisible] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState<SavedSearchQuery>(defaultSearchQuery);
|
||||
const [searchError, setSearchError] = useState<any>(undefined);
|
||||
const [searchString, setSearchString] = useState<string | undefined>(undefined);
|
||||
|
||||
// EuiInMemoryTable has an issue with dynamic sortable columns
|
||||
// and will trigger a full page Kibana error in such a case.
|
||||
// The following is a workaround until this is solved upstream:
|
||||
// - If the sortable/columns config changes,
|
||||
// the table will be unmounted/not rendered.
|
||||
// This is what setClearTable(true) in toggleColumn() does.
|
||||
// - After that on next render it gets re-enabled. To make sure React
|
||||
// doesn't consolidate the state updates, setTimeout is used.
|
||||
if (clearTable) {
|
||||
setTimeout(() => setClearTable(false), 0);
|
||||
}
|
||||
|
||||
function toggleColumnsPopover() {
|
||||
setColumnsPopoverVisible(!isColumnsPopoverVisible);
|
||||
}
|
||||
|
@ -105,7 +83,6 @@ export const ResultsTable: FC<Props> = React.memo(({ jobConfig, jobStatus }) =>
|
|||
|
||||
function toggleColumn(column: EsFieldName) {
|
||||
if (tableItems.length > 0 && jobConfig !== undefined) {
|
||||
setClearTable(true);
|
||||
// spread to a new array otherwise the component wouldn't re-render
|
||||
setSelectedFields([...toggleSelectedField(selectedFields, column)]);
|
||||
}
|
||||
|
@ -240,7 +217,6 @@ export const ResultsTable: FC<Props> = React.memo(({ jobConfig, jobStatus }) =>
|
|||
const field = predictedFieldSelected ? predictedFieldName : selectedFields[0];
|
||||
const direction = predictedFieldSelected ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC;
|
||||
loadExploreData({ field, direction, searchQuery });
|
||||
return;
|
||||
}
|
||||
}, [JSON.stringify(searchQuery)]);
|
||||
|
||||
|
@ -258,7 +234,6 @@ export const ResultsTable: FC<Props> = React.memo(({ jobConfig, jobStatus }) =>
|
|||
const field = predictedFieldSelected ? predictedFieldName : selectedFields[0];
|
||||
const direction = predictedFieldSelected ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC;
|
||||
loadExploreData({ field, direction, searchQuery });
|
||||
return;
|
||||
}
|
||||
}, [jobConfig, columns.length, sortField, sortDirection, tableItems.length]);
|
||||
|
||||
|
@ -282,7 +257,6 @@ export const ResultsTable: FC<Props> = React.memo(({ jobConfig, jobStatus }) =>
|
|||
setPageSize(size);
|
||||
|
||||
if (sort.field !== sortField || sort.direction !== sortDirection) {
|
||||
setClearTable(true);
|
||||
loadExploreData({ ...sort, searchQuery });
|
||||
}
|
||||
};
|
||||
|
@ -458,8 +432,19 @@ export const ResultsTable: FC<Props> = React.memo(({ jobConfig, jobStatus }) =>
|
|||
{status !== INDEX_STATUS.LOADING && (
|
||||
<EuiProgress size="xs" color="accent" max={1} value={0} />
|
||||
)}
|
||||
{clearTable === false && (columns.length > 0 || searchQuery !== defaultSearchQuery) && (
|
||||
{(columns.length > 0 || searchQuery !== defaultSearchQuery) && (
|
||||
<Fragment>
|
||||
<EuiFormRow
|
||||
helpText={i18n.translate(
|
||||
'xpack.ml.dataframe.analytics.regressionExploration.documentsShownHelpText',
|
||||
{
|
||||
defaultMessage: 'Showing first {searchSize} documents',
|
||||
values: { searchSize: SEARCH_SIZE },
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Fragment />
|
||||
</EuiFormRow>
|
||||
<EuiSpacer />
|
||||
<MlInMemoryTableBasic
|
||||
allowNeutralSort={false}
|
||||
|
|
Loading…
Reference in a new issue