diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts index 4c0fafc95a57..a242c1e0eb29 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts @@ -175,7 +175,19 @@ export function getAlertType( // console.log(`index_threshold: response: ${JSON.stringify(groupResults, null, 4)}`); for (const groupResult of groupResults) { const instanceId = groupResult.group; - const value = groupResult.metrics[0][1]; + const metric = + groupResult.metrics && groupResult.metrics.length > 0 ? groupResult.metrics[0] : null; + const value = metric && metric.length === 2 ? metric[1] : null; + + if (!value) { + logger.debug( + `alert ${ID}:${alertId} "${name}": no metrics found for group ${instanceId}} from groupResult ${JSON.stringify( + groupResult + )}` + ); + continue; + } + const met = compareFn(value, params.threshold); if (!met) continue; diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts index 86d18d98fa0e..37f6219cf30a 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts @@ -7,8 +7,14 @@ // test error conditions of calling timeSeriesQuery - postive results tested in FT +import type { estypes } from '@elastic/elasticsearch'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; -import { TimeSeriesQueryParameters, TimeSeriesQuery, timeSeriesQuery } from './time_series_query'; +import { + TimeSeriesQueryParameters, + TimeSeriesQuery, + timeSeriesQuery, + getResultFromEs, +} from './time_series_query'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '../../../../../../src/core/server/elasticsearch/client/mocks'; @@ -53,3 +59,135 @@ describe('timeSeriesQuery', () => { ); }); }); + +describe('getResultFromEs', () => { + it('correctly parses time series results for count aggregation', () => { + expect( + getResultFromEs(true, false, { + took: 0, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + aggregations: { + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:14:31.075Z-2021-04-22T15:19:31.075Z', + from: 1619104471075, + from_as_string: '2021-04-22T15:14:31.075Z', + to: 1619104771075, + to_as_string: '2021-04-22T15:19:31.075Z', + doc_count: 0, + }, + ], + }, + }, + } as estypes.SearchResponse) + ).toEqual({ + results: [ + { + group: 'all documents', + metrics: [['2021-04-22T15:19:31.075Z', 0]], + }, + ], + }); + }); + + it('correctly parses time series results with no aggregation data for count aggregation', () => { + // this could happen with cross cluster searches when cluster permissions are incorrect + // the query completes but doesn't return any aggregations + expect( + getResultFromEs(true, false, { + took: 0, + timed_out: false, + _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, + _clusters: { total: 1, successful: 1, skipped: 0 }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + } as estypes.SearchResponse) + ).toEqual({ + results: [], + }); + }); + + it('correctly parses time series results for group aggregation', () => { + expect( + getResultFromEs(false, true, { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 298, relation: 'eq' }, hits: [] }, + aggregations: { + groupAgg: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'host-2', + doc_count: 149, + sortValueAgg: { value: 0.5000000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 149, + metricAgg: { value: 0.5000000018251423 }, + }, + ], + }, + }, + { + key: 'host-1', + doc_count: 149, + sortValueAgg: { value: 0.5000000011000857 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 149, + metricAgg: { value: 0.5000000011000857 }, + }, + ], + }, + }, + ], + }, + }, + } as estypes.SearchResponse) + ).toEqual({ + results: [ + { + group: 'host-2', + metrics: [['2021-04-22T15:23:43.191Z', 0.5000000018251423]], + }, + { + group: 'host-1', + metrics: [['2021-04-22T15:23:43.191Z', 0.5000000011000857]], + }, + ], + }); + }); + + it('correctly parses time series results with no aggregation data for group aggregation', () => { + // this could happen with cross cluster searches when cluster permissions are incorrect + // the query completes but doesn't return any aggregations + expect( + getResultFromEs(false, true, { + took: 0, + timed_out: false, + _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, + _clusters: { total: 1, successful: 1, skipped: 0 }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + } as estypes.SearchResponse) + ).toEqual({ + results: [], + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts index ad044f4570ea..a2ba8d43c9c6 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts @@ -147,7 +147,7 @@ export async function timeSeriesQuery( return getResultFromEs(isCountAgg, isGroupAgg, esResult); } -function getResultFromEs( +export function getResultFromEs( isCountAgg: boolean, isGroupAgg: boolean, esResult: estypes.SearchResponse @@ -155,8 +155,8 @@ function getResultFromEs( const aggregations = esResult?.aggregations || {}; // add a fake 'all documents' group aggregation, if a group aggregation wasn't used - if (!isGroupAgg) { - const dateAgg = aggregations.dateAgg || {}; + if (!isGroupAgg && aggregations.dateAgg) { + const dateAgg = aggregations.dateAgg; aggregations.groupAgg = { buckets: [{ key: 'all documents', dateAgg }],