Skip to content

Commit

Permalink
[ML] Anomaly Explorer / Single Metric Viewer: Fix error reporting for…
Browse files Browse the repository at this point in the history
… annotations. (elastic#74953)

Fixes error reporting when annotations fail to load for Anomaly Explorer and Single Metric Viewer.
Previously, Anomaly Explorer ended up with a completely empty page when annotations failed to load. Single Metric Viewer would not fail to load, but it would make no difference for the user if existing annotations failed to load of if there were simply no existing annotations. Only in dev console an error message would be visible.
Now a callout is shown when annotations fail to load.
# Conflicts:
#	x-pack/test/functional/services/ml/navigation.ts
  • Loading branch information
walterra committed Aug 19, 2020
1 parent de7c2fb commit 79a6fb0
Show file tree
Hide file tree
Showing 16 changed files with 377 additions and 32 deletions.
10 changes: 8 additions & 2 deletions x-pack/plugins/ml/common/types/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ export function isAnnotation(arg: any): arg is Annotation {
);
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Annotations extends Array<Annotation> {}
export type Annotations = Annotation[];

export function isAnnotations(arg: any): arg is Annotations {
if (Array.isArray(arg) === false) {
Expand Down Expand Up @@ -134,5 +133,12 @@ export type EsAggregationResult = Record<string, TermAggregationResult>;
export interface GetAnnotationsResponse {
aggregations?: EsAggregationResult;
annotations: Record<string, Annotations>;
error?: string;
success: boolean;
}

export interface AnnotationsTable {
annotationsData: Annotations;
aggregations: EsAggregationResult;
error?: string;
}
32 changes: 30 additions & 2 deletions x-pack/plugins/ml/public/application/explorer/explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { FormattedMessage } from '@kbn/i18n/react';

import {
htmlIdGenerator,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
Expand Down Expand Up @@ -221,7 +222,7 @@ export class Explorer extends React.Component {
selectedJobs,
tableData,
} = this.props.explorerState;
const { annotationsData, aggregations } = annotations;
const { annotationsData, aggregations, error: annotationsError } = annotations;

const jobSelectorProps = {
dateFormatTz: getDateFormatTz(),
Expand Down Expand Up @@ -302,9 +303,36 @@ export class Explorer extends React.Component {
setSelectedCells={this.props.setSelectedCells}
/>
<EuiSpacer size="m" />
{annotationsData.length > 0 && (
{annotationsError !== undefined && (
<>
<EuiTitle
className="panel-title"
data-test-subj="mlAnomalyExplorerAnnotationsPanel error"
>
<h2>
<FormattedMessage
id="xpack.ml.explorer.annotationsErrorTitle"
defaultMessage="Annotations"
/>
</h2>
</EuiTitle>
<EuiPanel>
<EuiCallOut
title={i18n.translate('xpack.ml.explorer.annotationsErrorCallOutTitle', {
defaultMessage: 'An error occurred loading annotations:',
})}
color="danger"
iconType="alert"
>
<p>{annotationsError}</p>
</EuiCallOut>
</EuiPanel>
<EuiSpacer size="m" />
</>
)}
{annotationsData.length > 0 && (
<>
<EuiPanel data-test-subj="mlAnomalyExplorerAnnotationsPanel loaded">
<EuiAccordion
id={this.htmlIdGen()}
buttonContent={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { Moment } from 'moment';

import { AnnotationsTable } from '../../../common/types/annotations';
import { CombinedJob } from '../../../common/types/anomaly_detection_jobs';
import { SwimlaneType } from './explorer_constants';

Expand Down Expand Up @@ -111,7 +112,7 @@ export declare const loadAnnotationsTableData: (
selectedJobs: ExplorerJob[],
interval: number,
bounds: TimeRangeBounds
) => Promise<any[]>;
) => Promise<AnnotationsTable>;

export declare interface AnomaliesTableData {
anomalies: any[];
Expand Down
17 changes: 13 additions & 4 deletions x-pack/plugins/ml/public/application/explorer/explorer_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
ANOMALIES_TABLE_DEFAULT_QUERY_SIZE,
} from '../../../common/constants/search';
import { getEntityFieldList } from '../../../common/util/anomaly_utils';
import { extractErrorMessage } from '../../../common/util/errors';
import {
isSourceDataChartableForDetector,
isModelPlotChartableForDetector,
Expand Down Expand Up @@ -406,7 +407,12 @@ export function loadAnnotationsTableData(selectedCells, selectedJobs, interval,
.toPromise()
.then((resp) => {
if (resp.error !== undefined || resp.annotations === undefined) {
return resolve([]);
const errorMessage = extractErrorMessage(resp.error);
return resolve({
annotationsData: [],
aggregations: {},
error: errorMessage !== '' ? errorMessage : undefined,
});
}

const annotationsData = [];
Expand All @@ -430,9 +436,12 @@ export function loadAnnotationsTableData(selectedCells, selectedJobs, interval,
});
})
.catch((resp) => {
console.log('Error loading list of annotations for jobs list:', resp);
// Silently fail and just return an empty array for annotations to not break the UI.
return resolve([]);
const errorMessage = extractErrorMessage(resp);
return resolve({
annotationsData: [],
aggregations: {},
error: errorMessage !== '' ? errorMessage : undefined,
});
});
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,11 @@ import {
SwimlaneData,
ViewBySwimLaneData,
} from '../../explorer_utils';
import { Annotations, EsAggregationResult } from '../../../../../common/types/annotations';
import { AnnotationsTable } from '../../../../../common/types/annotations';
import { SWIM_LANE_DEFAULT_PAGE_SIZE } from '../../explorer_constants';

export interface ExplorerState {
annotations: {
annotationsData: Annotations;
aggregations: EsAggregationResult;
};
annotations: AnnotationsTable;
bounds: TimeRangeBounds | undefined;
chartsData: ExplorerChartsData;
fieldFormatsLoading: boolean;
Expand Down Expand Up @@ -67,6 +64,7 @@ function getDefaultIndexPattern() {
export function getExplorerDefaultState(): ExplorerState {
return {
annotations: {
error: undefined,
annotationsData: [],
aggregations: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ class TimeseriesChartIntl extends Component {
renderFocusChart() {
const {
focusAggregationInterval,
focusAnnotationData,
focusAnnotationData: focusAnnotationDataOriginalPropValue,
focusChartData,
focusForecastData,
modelPlotEnabled,
Expand All @@ -567,6 +567,10 @@ class TimeseriesChartIntl extends Component {
zoomToFocusLoaded,
} = this.props;

const focusAnnotationData = Array.isArray(focusAnnotationDataOriginalPropValue)
? focusAnnotationDataOriginalPropValue
: [];

if (focusChartData === undefined) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
EuiFormRow,
EuiSelect,
EuiSpacer,
EuiPanel,
EuiTitle,
EuiAccordion,
EuiBadge,
Expand Down Expand Up @@ -1028,6 +1029,7 @@ export class TimeSeriesExplorer extends React.Component {
dataNotChartable,
entityValues,
focusAggregationInterval,
focusAnnotationError,
focusAnnotationData,
focusAggregations,
focusChartData,
Expand Down Expand Up @@ -1316,6 +1318,36 @@ export class TimeSeriesExplorer extends React.Component {
)}
</MlTooltipComponent>
</div>
{focusAnnotationError !== undefined && (
<>
<EuiTitle
className="panel-title"
data-test-subj="mlAnomalyExplorerAnnotations error"
>
<h2>
<FormattedMessage
id="xpack.ml.timeSeriesExplorer.annotationsErrorTitle"
defaultMessage="Annotations"
/>
</h2>
</EuiTitle>
<EuiPanel>
<EuiCallOut
title={i18n.translate(
'xpack.ml.timeSeriesExplorer.annotationsErrorCallOutTitle',
{
defaultMessage: 'An error occurred loading annotations:',
}
)}
color="danger"
iconType="alert"
>
<p>{focusAnnotationError}</p>
</EuiCallOut>
</EuiPanel>
<EuiSpacer size="m" />
</>
)}
{focusAnnotationData && focusAnnotationData.length > 0 && (
<EuiAccordion
id={'EuiAccordion-blah'}
Expand All @@ -1340,6 +1372,7 @@ export class TimeSeriesExplorer extends React.Component {
</h2>
</EuiTitle>
}
data-test-subj="mlAnomalyExplorerAnnotations loaded"
>
<AnnotationsTable
chartDetails={chartDetails}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE,
ANOMALIES_TABLE_DEFAULT_QUERY_SIZE,
} from '../../../../common/constants/search';
import { extractErrorMessage } from '../../../../common/util/errors';
import { mlTimeSeriesSearchService } from '../timeseries_search_service';
import { mlResultsService, CriteriaField } from '../../services/results_service';
import { Job } from '../../../../common/types/anomaly_detection_jobs';
Expand All @@ -23,7 +24,7 @@ import {
} from './timeseriesexplorer_utils';
import { mlForecastService } from '../../services/forecast_service';
import { mlFunctionToESAggregation } from '../../../../common/util/job_utils';
import { Annotation } from '../../../../common/types/annotations';
import { GetAnnotationsResponse } from '../../../../common/types/annotations';
import { ANNOTATION_EVENT_USER } from '../../../../common/constants/annotations';

export interface Interval {
Expand All @@ -36,7 +37,8 @@ export interface FocusData {
anomalyRecords: any;
scheduledEvents: any;
showForecastCheckbox?: any;
focusAnnotationData?: any;
focusAnnotationError?: string;
focusAnnotationData?: any[];
focusForecastData?: any;
focusAggregations?: any;
}
Expand Down Expand Up @@ -96,14 +98,14 @@ export function getFocusData(
entities: nonBlankEntities,
})
.pipe(
catchError(() => {
// silent fail
return of({
annotations: {} as Record<string, Annotation[]>,
catchError((resp) =>
of({
annotations: {},
aggregations: {},
error: extractErrorMessage(resp),
success: false,
});
})
} as GetAnnotationsResponse)
)
),
// Plus query for forecast data if there is a forecastId stored in the appState.
forecastId !== undefined
Expand Down Expand Up @@ -152,16 +154,22 @@ export function getFocusData(
};

if (annotations) {
refreshFocusData.focusAnnotationData = (annotations.annotations[selectedJob.job_id] ?? [])
.sort((a, b) => {
return a.timestamp - b.timestamp;
})
.map((d, i: number) => {
d.key = (i + 1).toString();
return d;
});
if (annotations.error !== undefined) {
refreshFocusData.focusAnnotationError = annotations.error;
refreshFocusData.focusAnnotationData = [];
refreshFocusData.focusAggregations = {};
} else {
refreshFocusData.focusAnnotationData = (annotations.annotations[selectedJob.job_id] ?? [])
.sort((a, b) => {
return a.timestamp - b.timestamp;
})
.map((d, i: number) => {
d.key = (i + 1).toString();
return d;
});

refreshFocusData.focusAggregations = annotations.aggregations;
refreshFocusData.focusAggregations = annotations.aggregations;
}
}

if (forecastData) {
Expand Down
Loading

0 comments on commit 79a6fb0

Please sign in to comment.