[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:
Melissa Alvarez 2019-11-01 12:12:01 -04:00 committed by GitHub
parent 128948c2a7
commit 568b8d3992
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 71 additions and 60 deletions

View file

@ -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';

View file

@ -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);
};

View file

@ -1,3 +0,0 @@
.mlRegressionExploration__evaluateLoadingSpinner {
display: inline-block;
}

View file

@ -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'

View file

@ -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 />}

View file

@ -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}