Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Anomaly Explorer / Single Metric Viewer: Fix error reporting for annotations. #74953

Merged
merged 12 commits into from
Aug 19, 2020
Merged
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({
qn895 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -552,7 +552,7 @@ class TimeseriesChartIntl extends Component {
renderFocusChart() {
const {
focusAggregationInterval,
focusAnnotationData,
focusAnnotationData: focusAnnotationDataOriginalPropValue,
focusChartData,
focusForecastData,
modelPlotEnabled,
Expand All @@ -565,6 +565,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