diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client/actions_client.test.ts index 02c647f17664da..b52ce10ab8c4ac 100644 --- a/x-pack/plugins/actions/server/actions_client/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client/actions_client.test.ts @@ -51,6 +51,7 @@ import { getOAuthClientCredentialsAccessToken } from '../lib/get_oauth_client_cr import { OAuthParams } from '../routes/get_oauth_access_token'; import { eventLogClientMock } from '@kbn/event-log-plugin/server/event_log_client.mock'; import { GetGlobalExecutionKPIParams, GetGlobalExecutionLogParams } from '../../common'; +import { estypes } from '@elastic/elasticsearch'; jest.mock('@kbn/core-saved-objects-utils-server', () => { const actual = jest.requireActual('@kbn/core-saved-objects-utils-server'); @@ -3419,6 +3420,10 @@ describe('getGlobalExecutionLogWithAuth()', () => { executionUuidCardinality: { doc_count: 5, executionUuidCardinality: { value: 5 } }, }, }, + hits: { + total: { value: 5, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; describe('authorization', () => { test('ensures user is authorised to access logs', async () => { @@ -3474,6 +3479,10 @@ describe('getGlobalExecutionKpiWithAuth()', () => { }, }, }, + hits: { + total: { value: 5, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; describe('authorization', () => { test('ensures user is authorised to access kpi', async () => { diff --git a/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.test.ts b/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.test.ts index f85d51b5ae2c38..0d889e53ca8c2e 100644 --- a/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.test.ts +++ b/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { fromKueryExpression } from '@kbn/es-query'; import { getExecutionLogAggregation, @@ -485,7 +486,15 @@ describe('getExecutionLogAggregation', () => { describe('formatExecutionLogResult', () => { test('should return empty results if aggregations are undefined', () => { - expect(formatExecutionLogResult({ aggregations: undefined })).toEqual({ + expect( + formatExecutionLogResult({ + aggregations: undefined, + hits: { + total: { value: 0, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, + }) + ).toEqual({ total: 0, data: [], }); @@ -494,6 +503,10 @@ describe('formatExecutionLogResult', () => { expect( formatExecutionLogResult({ aggregations: { executionLogAgg: undefined as unknown as ExecutionUuidAggResult }, + hits: { + total: { value: 5, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }) ).toEqual({ total: 0, @@ -554,6 +567,10 @@ describe('formatExecutionLogResult', () => { executionUuidCardinality: { doc_count: 1, executionUuidCardinality: { value: 1 } }, }, }, + hits: { + total: { value: 5, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; expect(formatExecutionLogResult(results)).toEqual({ data: [ @@ -675,6 +692,10 @@ describe('formatExecutionLogResult', () => { executionUuidCardinality: { doc_count: 2, executionUuidCardinality: { value: 2 } }, }, }, + hits: { + total: { value: 10, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; expect(formatExecutionLogResult(results)).toEqual({ data: [ @@ -918,6 +939,10 @@ describe('formatExecutionKPIAggBuckets', () => { expect( formatExecutionKPIResult({ aggregations: undefined, + hits: { + total: { value: 0, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }) ).toEqual({ failure: 0, success: 0, unknown: 0, warning: 0 }); }); @@ -951,6 +976,10 @@ describe('formatExecutionKPIAggBuckets', () => { }, }, }, + hits: { + total: { value: 21, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; expect(formatExecutionKPIResult(results)).toEqual({ diff --git a/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.test.ts b/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.test.ts index bb38fb7a98bfa5..88263ff495b9ac 100644 --- a/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.test.ts +++ b/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; import { fromKueryExpression } from '@kbn/es-query'; import { getNumExecutions, @@ -73,7 +74,7 @@ describe('getNumExecutions', () => { new Date('2020-12-02T00:00:00.000Z'), '1s' ) - ).toEqual(1000); + ).toEqual(10000); }); }); @@ -146,38 +147,48 @@ describe('getExecutionLogAggregation', () => { }, aggs: { executionUuidCardinality: { - aggs: { - executionUuidCardinality: { - cardinality: { field: 'kibana.alert.rule.execution.uuid' }, - }, + sum_bucket: { + buckets_path: 'executionUuidCardinalityBuckets>ruleExecution._count', }, - filter: { - bool: { - must: [ - { - bool: { - must: [ - { - match: { - 'event.action': 'execute', - }, - }, - { - match: { - 'event.provider': 'alerting', - }, + }, + executionUuidCardinalityBuckets: { + terms: { + field: 'kibana.alert.rule.execution.uuid', + size: 10000, + order: [{ 'ruleExecution>executeStartTime': 'desc' }], + }, + aggs: { + ruleExecution: { + filter: { + bool: { + must: [ + { + bool: { + must: [ + { + match: { + 'event.action': 'execute', + }, + }, + { + match: { + 'event.provider': 'alerting', + }, + }, + ], }, - ], - }, + }, + ], }, - ], + }, + aggs: { executeStartTime: { min: { field: 'event.start' } } }, }, }, }, executionUuid: { terms: { field: 'kibana.alert.rule.execution.uuid', - size: 1000, + size: 10000, order: [ { 'ruleExecution>executeStartTime': 'asc' }, { 'ruleExecution>executionDuration': 'desc' }, @@ -330,50 +341,60 @@ describe('getExecutionLogAggregation', () => { }, aggs: { executionUuidCardinality: { - aggs: { - executionUuidCardinality: { - cardinality: { field: 'kibana.alert.rule.execution.uuid' }, - }, + sum_bucket: { + buckets_path: 'executionUuidCardinalityBuckets>ruleExecution._count', + }, + }, + executionUuidCardinalityBuckets: { + terms: { + field: 'kibana.alert.rule.execution.uuid', + size: 10000, + order: [{ 'ruleExecution>executeStartTime': 'desc' }], }, - filter: { - bool: { + aggs: { + ruleExecution: { filter: { bool: { - minimum_should_match: 1, - should: [ + filter: { + bool: { + minimum_should_match: 1, + should: [ + { + match: { + test: 'test', + }, + }, + ], + }, + }, + must: [ { - match: { - test: 'test', + bool: { + must: [ + { + match: { + 'event.action': 'execute', + }, + }, + { + match: { + 'event.provider': 'alerting', + }, + }, + ], }, }, ], }, }, - must: [ - { - bool: { - must: [ - { - match: { - 'event.action': 'execute', - }, - }, - { - match: { - 'event.provider': 'alerting', - }, - }, - ], - }, - }, - ], + aggs: { executeStartTime: { min: { field: 'event.start' } } }, }, }, }, executionUuid: { terms: { field: 'kibana.alert.rule.execution.uuid', - size: 1000, + size: 10000, order: [ { 'ruleExecution>executeStartTime': 'asc' }, { 'ruleExecution>executionDuration': 'desc' }, @@ -538,50 +559,60 @@ describe('getExecutionLogAggregation', () => { }, aggs: { executionUuidCardinality: { - aggs: { - executionUuidCardinality: { - cardinality: { field: 'kibana.alert.rule.execution.uuid' }, - }, + sum_bucket: { + buckets_path: 'executionUuidCardinalityBuckets>ruleExecution._count', + }, + }, + executionUuidCardinalityBuckets: { + terms: { + field: 'kibana.alert.rule.execution.uuid', + size: 10000, + order: [{ 'ruleExecution>executeStartTime': 'desc' }], }, - filter: { - bool: { + aggs: { + ruleExecution: { filter: { bool: { - minimum_should_match: 1, - should: [ + filter: { + bool: { + minimum_should_match: 1, + should: [ + { + match: { + test: 'test', + }, + }, + ], + }, + }, + must: [ { - match: { - test: 'test', + bool: { + must: [ + { + match: { + 'event.action': 'execute', + }, + }, + { + match: { + 'event.provider': 'alerting', + }, + }, + ], }, }, ], }, }, - must: [ - { - bool: { - must: [ - { - match: { - 'event.action': 'execute', - }, - }, - { - match: { - 'event.provider': 'alerting', - }, - }, - ], - }, - }, - ], + aggs: { executeStartTime: { min: { field: 'event.start' } } }, }, }, }, executionUuid: { terms: { field: 'kibana.alert.rule.execution.uuid', - size: 1000, + size: 10000, order: [ { 'ruleExecution>executeStartTime': 'asc' }, { 'ruleExecution>executionDuration': 'desc' }, @@ -726,7 +757,12 @@ describe('getExecutionLogAggregation', () => { describe('formatExecutionLogResult', () => { test('should return empty results if aggregations are undefined', () => { - expect(formatExecutionLogResult({ aggregations: undefined })).toEqual({ + expect( + formatExecutionLogResult({ + aggregations: undefined, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + }) + ).toEqual({ total: 0, data: [], }); @@ -735,6 +771,7 @@ describe('formatExecutionLogResult', () => { expect( formatExecutionLogResult({ aggregations: { excludeExecuteStart: undefined as unknown as ExecutionUuidAggResult }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, }) ).toEqual({ total: 0, @@ -932,12 +969,14 @@ describe('formatExecutionLogResult', () => { ], }, executionUuidCardinality: { - executionUuidCardinality: { - value: 374, - }, + value: 374, }, }, }, + hits: { + total: { value: 875, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; expect(formatExecutionLogResult(results)).toEqual({ total: 374, @@ -1188,12 +1227,14 @@ describe('formatExecutionLogResult', () => { ], }, executionUuidCardinality: { - executionUuidCardinality: { - value: 374, - }, + value: 374, }, }, }, + hits: { + total: { value: 875, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; expect(formatExecutionLogResult(results)).toEqual({ total: 374, @@ -1436,12 +1477,14 @@ describe('formatExecutionLogResult', () => { ], }, executionUuidCardinality: { - executionUuidCardinality: { - value: 374, - }, + value: 374, }, }, }, + hits: { + total: { value: 875, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; expect(formatExecutionLogResult(results)).toEqual({ total: 374, @@ -1689,12 +1732,14 @@ describe('formatExecutionLogResult', () => { ], }, executionUuidCardinality: { - executionUuidCardinality: { - value: 417, - }, + value: 417, }, }, }, + hits: { + total: { value: 875, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; expect(formatExecutionLogResult(results)).toEqual({ total: 417, @@ -1750,78 +1795,283 @@ describe('formatExecutionLogResult', () => { ], }); }); -}); -describe('getExecutionKPIAggregation', () => { - test('should correctly generate aggregation', () => { - expect(getExecutionKPIAggregation()).toEqual({ - excludeExecuteStart: { - filter: { - bool: { - must_not: [ - { - term: { - 'event.action': 'execute-start', - }, - }, - ], - }, - }, - aggs: { + test('should throw an error when document is above 10,000', () => { + const results = { + aggregations: { + excludeExecuteStart: { + meta: {}, + doc_count: 875, executionUuid: { - terms: { - field: 'kibana.alert.rule.execution.uuid', - order: [ - { - 'ruleExecution>executeStartTime': 'desc', - }, - ], - size: 10000, - }, - aggs: { - executionUuidSorted: { - bucket_sort: { - from: 0, - size: 10000, - gap_policy: 'insert_zeros', + meta: {}, + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'ecf7ac4c-1c15-4a1d-818a-cacbf57f6158', + doc_count: 32, + timeoutMessage: { + meta: {}, + doc_count: 0, }, - }, - actionExecution: { - filter: { - bool: { - must: [ - { - bool: { - must: [ - { - match: { - 'event.action': 'execute', - }, + ruleExecution: { + meta: {}, + doc_count: 1, + numTriggeredActions: { + value: 5.0, + }, + numGeneratedActions: { + value: 5.0, + }, + numActiveAlerts: { + value: 5.0, + }, + numNewAlerts: { + value: 5.0, + }, + numRecoveredAlerts: { + value: 5.0, + }, + outcomeMessageAndMaintenanceWindow: { + hits: { + total: { + value: 1, + relation: 'eq', + }, + max_score: 1.0, + hits: [ + { + _index: '.kibana-event-log-8.2.0-000001', + _id: '7xKcb38BcntAq5ycFwiu', + _score: 1.0, + _source: { + rule: { id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef', name: 'rule_name' }, + event: { + outcome: 'success', }, - { - match: { - 'event.provider': 'actions', + kibana: { + version: '8.2.0', + alerting: { + outcome: 'success', }, }, - ], + message: + "rule executed: example.always-firing:a348a740-9e2c-11ec-bd64-774ed95c43ef: 'test rule'", + }, }, - }, - ], - }, - }, - aggs: { - actionOutcomes: { - terms: { - field: 'event.outcome', - size: 2, + ], }, }, - }, - }, - ruleExecution: { - filter: { - bool: { - must: [ + scheduleDelay: { + value: 3.126e9, + }, + totalSearchDuration: { + value: 0.0, + }, + esSearchDuration: { + value: 0.0, + }, + executionDuration: { + value: 1.374e9, + }, + executeStartTime: { + value: 1.646844973039e12, + value_as_string: '2022-03-09T16:56:13.039Z', + }, + }, + actionExecution: { + meta: {}, + doc_count: 5, + actionOutcomes: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'failure', + doc_count: 5, + }, + ], + }, + }, + }, + { + key: '61bb867b-661a-471f-bf92-23471afa10b3', + doc_count: 32, + timeoutMessage: { + meta: {}, + doc_count: 0, + }, + ruleExecution: { + meta: {}, + doc_count: 1, + numTriggeredActions: { + value: 5.0, + }, + numGeneratedActions: { + value: 5.0, + }, + numActiveAlerts: { + value: 5.0, + }, + numNewAlerts: { + value: 5.0, + }, + numRecoveredAlerts: { + value: 5.0, + }, + outcomeMessageAndMaintenanceWindow: { + hits: { + total: { + value: 1, + relation: 'eq', + }, + max_score: 1.0, + hits: [ + { + _index: '.kibana-event-log-8.2.0-000001', + _id: 'zRKbb38BcntAq5ycOwgk', + _score: 1.0, + _source: { + rule: { id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef', name: 'rule_name' }, + event: { + outcome: 'success', + }, + kibana: { + version: '8.2.0', + alert: { + maintenance_window_ids: ['254699b0-dfb2-11ed-bb3d-c91b918d0260'], + }, + alerting: { + outcome: 'success', + }, + }, + message: + "rule executed: example.always-firing:a348a740-9e2c-11ec-bd64-774ed95c43ef: 'test rule'", + }, + }, + ], + }, + }, + scheduleDelay: { + value: 3.133e9, + }, + totalSearchDuration: { + value: 0.0, + }, + esSearchDuration: { + value: 0.0, + }, + executionDuration: { + value: 4.18e8, + }, + executeStartTime: { + value: 1.646844917518e12, + value_as_string: '2022-03-09T16:55:17.518Z', + }, + }, + actionExecution: { + meta: {}, + doc_count: 5, + actionOutcomes: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'success', + doc_count: 5, + }, + ], + }, + }, + }, + ], + }, + executionUuidCardinality: { + value: 417, + }, + }, + }, + hits: { + total: { value: 10000, relation: 'gte' }, + hits: [], + } as estypes.SearchHitsMetadata, + }; + expect(() => formatExecutionLogResult(results)).toThrowErrorMatchingInlineSnapshot( + `"Results are limited to 10,000 documents, refine your search to see others."` + ); + }); +}); + +describe('getExecutionKPIAggregation', () => { + test('should correctly generate aggregation', () => { + expect(getExecutionKPIAggregation()).toEqual({ + excludeExecuteStart: { + filter: { + bool: { + must_not: [ + { + term: { + 'event.action': 'execute-start', + }, + }, + ], + }, + }, + aggs: { + executionUuid: { + terms: { + field: 'kibana.alert.rule.execution.uuid', + order: [ + { + 'ruleExecution>executeStartTime': 'desc', + }, + ], + size: 10000, + }, + aggs: { + executionUuidSorted: { + bucket_sort: { + from: 0, + size: 10000, + gap_policy: 'insert_zeros', + }, + }, + actionExecution: { + filter: { + bool: { + must: [ + { + bool: { + must: [ + { + match: { + 'event.action': 'execute', + }, + }, + { + match: { + 'event.provider': 'actions', + }, + }, + ], + }, + }, + ], + }, + }, + aggs: { + actionOutcomes: { + terms: { + field: 'event.outcome', + size: 2, + }, + }, + }, + }, + ruleExecution: { + filter: { + bool: { + must: [ { bool: { must: [ @@ -2254,6 +2504,10 @@ describe('formatExecutionKPIAggBuckets', () => { expect( formatExecutionKPIResult({ aggregations: undefined, + hits: { + total: { value: 875, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }) ).toEqual({ activeAlerts: 0, @@ -2375,6 +2629,10 @@ describe('formatExecutionKPIAggBuckets', () => { }, }, }, + hits: { + total: { value: 875, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; expect(formatExecutionKPIResult(results)).toEqual({ @@ -2497,6 +2755,10 @@ describe('formatExecutionKPIAggBuckets', () => { }, }, }, + hits: { + total: { value: 875, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; expect(formatExecutionKPIResult(results)).toEqual({ @@ -2511,4 +2773,209 @@ describe('formatExecutionKPIAggBuckets', () => { triggeredActions: 10, }); }); + + test('should throw an error when document is above 10,000', () => { + const results = { + aggregations: { + excludeExecuteStart: { + meta: {}, + doc_count: 875, + executionUuid: { + meta: {}, + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'ecf7ac4c-1c15-4a1d-818a-cacbf57f6158', + doc_count: 32, + timeoutMessage: { + meta: {}, + doc_count: 0, + }, + ruleExecution: { + meta: {}, + doc_count: 1, + numTriggeredActions: { + value: 5.0, + }, + numGeneratedActions: { + value: 5.0, + }, + numActiveAlerts: { + value: 5.0, + }, + numNewAlerts: { + value: 5.0, + }, + numRecoveredAlerts: { + value: 5.0, + }, + outcomeMessageAndMaintenanceWindow: { + hits: { + total: { + value: 1, + relation: 'eq', + }, + max_score: 1.0, + hits: [ + { + _index: '.kibana-event-log-8.2.0-000001', + _id: '7xKcb38BcntAq5ycFwiu', + _score: 1.0, + _source: { + rule: { id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef', name: 'rule_name' }, + event: { + outcome: 'success', + }, + kibana: { + version: '8.2.0', + alerting: { + outcome: 'success', + }, + }, + message: + "rule executed: example.always-firing:a348a740-9e2c-11ec-bd64-774ed95c43ef: 'test rule'", + }, + }, + ], + }, + }, + scheduleDelay: { + value: 3.126e9, + }, + totalSearchDuration: { + value: 0.0, + }, + esSearchDuration: { + value: 0.0, + }, + executionDuration: { + value: 1.374e9, + }, + executeStartTime: { + value: 1.646844973039e12, + value_as_string: '2022-03-09T16:56:13.039Z', + }, + }, + actionExecution: { + meta: {}, + doc_count: 5, + actionOutcomes: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'failure', + doc_count: 5, + }, + ], + }, + }, + }, + { + key: '61bb867b-661a-471f-bf92-23471afa10b3', + doc_count: 32, + timeoutMessage: { + meta: {}, + doc_count: 0, + }, + ruleExecution: { + meta: {}, + doc_count: 1, + numTriggeredActions: { + value: 5.0, + }, + numGeneratedActions: { + value: 5.0, + }, + numActiveAlerts: { + value: 5.0, + }, + numNewAlerts: { + value: 5.0, + }, + numRecoveredAlerts: { + value: 5.0, + }, + outcomeMessageAndMaintenanceWindow: { + hits: { + total: { + value: 1, + relation: 'eq', + }, + max_score: 1.0, + hits: [ + { + _index: '.kibana-event-log-8.2.0-000001', + _id: 'zRKbb38BcntAq5ycOwgk', + _score: 1.0, + _source: { + rule: { id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef', name: 'rule_name' }, + event: { + outcome: 'success', + }, + kibana: { + version: '8.2.0', + alert: { + maintenance_window_ids: ['254699b0-dfb2-11ed-bb3d-c91b918d0260'], + }, + alerting: { + outcome: 'success', + }, + }, + message: + "rule executed: example.always-firing:a348a740-9e2c-11ec-bd64-774ed95c43ef: 'test rule'", + }, + }, + ], + }, + }, + scheduleDelay: { + value: 3.133e9, + }, + totalSearchDuration: { + value: 0.0, + }, + esSearchDuration: { + value: 0.0, + }, + executionDuration: { + value: 4.18e8, + }, + executeStartTime: { + value: 1.646844917518e12, + value_as_string: '2022-03-09T16:55:17.518Z', + }, + }, + actionExecution: { + meta: {}, + doc_count: 5, + actionOutcomes: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'success', + doc_count: 5, + }, + ], + }, + }, + }, + ], + }, + executionUuidCardinality: { + value: 417, + }, + }, + }, + hits: { + total: { value: 10000, relation: 'gte' }, + hits: [], + } as estypes.SearchHitsMetadata, + }; + expect(() => formatExecutionKPIResult(results)).toThrowErrorMatchingInlineSnapshot( + `"Results are limited to 10,000 documents, refine your search to see others."` + ); + }); }); diff --git a/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.ts b/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.ts index 551dfdce1ef428..30f495efbf0876 100644 --- a/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.ts +++ b/x-pack/plugins/alerting/server/lib/get_execution_log_aggregation.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { KueryNode } from '@kbn/es-query'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import Boom from '@hapi/boom'; @@ -14,7 +15,7 @@ import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { parseDuration } from '.'; import { IExecutionLog, IExecutionLogResult, EMPTY_EXECUTION_KPI_RESULT } from '../../common'; -const DEFAULT_MAX_BUCKETS_LIMIT = 1000; // do not retrieve more than this number of executions +const DEFAULT_MAX_BUCKETS_LIMIT = 10000; // do not retrieve more than this number of executions. UI limits 1000 to display, but we need to fetch all 10000 to accurately reflect the KPIs const DEFAULT_MAX_KPI_BUCKETS_LIMIT = 10000; const RULE_ID_FIELD = 'rule.id'; @@ -104,9 +105,7 @@ export interface ExecutionUuidKPIAggResult interface ExcludeExecuteStartAggResult extends estypes.AggregationsAggregateBase { executionUuid: ExecutionUuidAggResult; - executionUuidCardinality: { - executionUuidCardinality: estypes.AggregationsCardinalityAggregate; - }; + executionUuidCardinality: estypes.AggregationsCardinalityAggregate; // This is an accurate type even though we're actually using a sum bucket agg } interface ExcludeExecuteStartKpiAggResult extends estypes.AggregationsAggregateBase { @@ -301,21 +300,37 @@ export function getExecutionLogAggregation({ }, aggs: { // Get total number of executions - executionUuidCardinality: { - filter: { - bool: { - ...(dslFilterQuery ? { filter: dslFilterQuery } : {}), - must: [getProviderAndActionFilter('alerting', 'execute')], - }, + executionUuidCardinalityBuckets: { + terms: { + field: EXECUTION_UUID_FIELD, + size: DEFAULT_MAX_BUCKETS_LIMIT, + order: formatSortForTermSort([{ timestamp: { order: 'desc' } }]), }, aggs: { - executionUuidCardinality: { - cardinality: { - field: EXECUTION_UUID_FIELD, + ruleExecution: { + filter: { + bool: { + ...(dslFilterQuery ? { filter: dslFilterQuery } : {}), + must: [getProviderAndActionFilter('alerting', 'execute')], + }, + }, + aggs: { + executeStartTime: { + min: { + field: START_FIELD, + }, + }, }, }, }, }, + // Cardinality aggregation isn't accurate for this use case because we want to limit the cardinality + // to DEFAULT_MAX_BUCKETS_LIMIT. Instead, we sum the buckets and call it a cardinality. + executionUuidCardinality: { + sum_bucket: { + buckets_path: 'executionUuidCardinalityBuckets>ruleExecution._count', + }, + }, executionUuid: { // Bucket by execution UUID terms: { @@ -592,8 +607,31 @@ function formatExecutionKPIAggBuckets(buckets: IExecutionUuidKpiAggBucket[]) { return objToReturn; } +function validTotalHitsLimitationOnExecutionLog(esHitsTotal: estypes.SearchTotalHits) { + if ( + esHitsTotal && + esHitsTotal.relation && + esHitsTotal.value && + esHitsTotal.relation === 'gte' && + esHitsTotal.value === 10000 + ) { + throw Boom.entityTooLarge( + i18n.translate('xpack.alerting.feature.executionLogAggs.limitationQueryMsg', { + defaultMessage: + 'Results are limited to 10,000 documents, refine your search to see others.', + }), + EMPTY_EXECUTION_LOG_RESULT + ); + } +} + export function formatExecutionKPIResult(results: AggregateEventsBySavedObjectResult) { - const { aggregations } = results; + const { aggregations, hits } = results; + + if (hits && hits.total) { + validTotalHitsLimitationOnExecutionLog(hits.total as estypes.SearchTotalHits); + } + if (!aggregations || !aggregations.excludeExecuteStart) { return EMPTY_EXECUTION_KPI_RESULT; } @@ -605,7 +643,11 @@ export function formatExecutionKPIResult(results: AggregateEventsBySavedObjectRe export function formatExecutionLogResult( results: AggregateEventsBySavedObjectResult ): IExecutionLogResult { - const { aggregations } = results; + const { aggregations, hits } = results; + + if (hits && hits.total) { + validTotalHitsLimitationOnExecutionLog(hits.total as estypes.SearchTotalHits); + } if (!aggregations || !aggregations.excludeExecuteStart) { return EMPTY_EXECUTION_LOG_RESULT; @@ -613,7 +655,7 @@ export function formatExecutionLogResult( const aggs = aggregations.excludeExecuteStart as ExcludeExecuteStartAggResult; - const total = aggs.executionUuidCardinality.executionUuidCardinality.value; + const total = aggs.executionUuidCardinality.value; const buckets = aggs.executionUuid.buckets; return { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts index c4419ef8386a57..1d00d74d1ddf8f 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts @@ -339,12 +339,14 @@ const aggregateResults = { ], }, executionUuidCardinality: { - executionUuidCardinality: { - value: 374, - }, + value: 374, }, }, }, + hits: { + total: { value: 875, relation: 'eq' }, + hits: [], + } as estypes.SearchHitsMetadata, }; function getRuleSavedObject(attributes: Partial = {}): SavedObject { diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index a147d80f6639d2..3cf30df38c9615 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -673,6 +673,13 @@ describe('aggregateEventsBySavedObject', () => { ], }, }, + hits: { + hits: [], + total: { + relation: 'eq', + value: 0, + }, + }, }); }); }); @@ -771,6 +778,13 @@ describe('aggregateEventsWithAuthFilter', () => { ], }, }, + hits: { + hits: [], + total: { + relation: 'eq', + value: 0, + }, + }, }); }); @@ -919,6 +933,13 @@ describe('aggregateEventsWithAuthFilter', () => { ], }, }, + hits: { + hits: [], + total: { + relation: 'eq', + value: 0, + }, + }, }); }); }); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index 1eb1219621c370..530374c770bbc9 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -82,6 +82,7 @@ export type AggregateEventsOptionsBySavedObjectFilter = QueryOptionsEventsBySave }; export interface AggregateEventsBySavedObjectResult { + hits?: estypes.SearchHitsMetadata; aggregations: Record | undefined; } @@ -455,12 +456,13 @@ export class ClusterClientAdapter({ + const { aggregations, hits } = await esClient.search({ index, body, }); return { aggregations, + hits, }; } catch (err) { throw new Error( @@ -488,19 +490,20 @@ export class ClusterClientAdapter({ + const { aggregations, hits } = await esClient.search({ index, body, }); return { aggregations, + hits, }; } catch (err) { - throw new Error( + this.logger.debug( `querying for Event Log by for type "${type}" and auth filter failed with: ${err.message}` ); + throw err; } } } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_event_logs.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_event_logs.ts index 627b1b2f9a6a6e..409be866b730b6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_event_logs.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_event_logs.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import datemath from '@kbn/datemath'; import { useKibana } from '../../common/lib/kibana'; @@ -37,7 +37,6 @@ const isGlobal = (props: UseLoadRuleEventLogsProps): props is LoadGlobalExecutio export function useLoadRuleEventLogs(props: UseLoadRuleEventLogsProps) { const { http } = useKibana().services; - const queryFn = useCallback(() => { if (isGlobal(props)) { return loadGlobalExecutionLogAggregations({ @@ -55,16 +54,21 @@ export function useLoadRuleEventLogs(props: UseLoadRuleEventLogsProps) { }); }, [props, http]); - const { data, isLoading, isFetching, refetch } = useQuery({ + const { data, error, isLoading, isFetching, refetch } = useQuery({ queryKey: ['loadRuleEventLog', props], queryFn, onError: props.onError, + retry: 0, refetchOnWindowFocus: false, }); - - return { - data, - isLoading: isLoading || isFetching, - loadEventLogs: refetch, - }; + const hasExceedLogs = useMemo(() => error && error.body.statusCode === 413, [error]); + return useMemo( + () => ({ + data, + hasExceedLogs, + isLoading: isLoading || isFetching, + loadEventLogs: refetch, + }), + [data, hasExceedLogs, isFetching, isLoading, refetch] + ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx index 718222636830a9..7b9398d37e4181 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx @@ -12,7 +12,10 @@ import { loadExecutionKPIAggregations } from '../../../lib/rule_api/load_executi import { loadGlobalExecutionKPIAggregations } from '../../../lib/rule_api/load_global_execution_kpi_aggregations'; import { RuleEventLogListKPI } from './rule_event_log_list_kpi'; import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; +import { useKibana } from '../../../../common/lib'; +import { IToasts } from '@kbn/core/public'; +const addDangerMock = jest.fn(); jest.mock('../../../../common/lib/kibana', () => ({ useKibana: jest.fn().mockReturnValue({ services: { @@ -20,6 +23,7 @@ jest.mock('../../../../common/lib/kibana', () => ({ }, }), })); +const useKibanaMock = useKibana as jest.Mocked; jest.mock('../../../lib/rule_api/load_execution_kpi_aggregations', () => ({ loadExecutionKPIAggregations: jest.fn(), @@ -53,6 +57,9 @@ const loadGlobalExecutionKPIAggregationsMock = describe('rule_event_log_list_kpi', () => { beforeEach(() => { jest.clearAllMocks(); + useKibanaMock().services.notifications.toasts = { + addDanger: addDangerMock, + } as unknown as IToasts; (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); loadExecutionKPIAggregationsMock.mockResolvedValue(mockKpiResponse); loadGlobalExecutionKPIAggregationsMock.mockResolvedValue(mockKpiResponse); @@ -226,4 +233,44 @@ describe('rule_event_log_list_kpi', () => { }) ); }); + + it('Should call addDanger function when an the API throw an error', async () => { + loadGlobalExecutionKPIAggregationsMock.mockRejectedValue({ body: { statusCode: 400 } }); + const wrapper = mountWithIntl( + + ); + // Let the load resolve + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(addDangerMock).toHaveBeenCalled(); + }); + + it('Should NOT call addDanger function when an the API throw a 413 error', async () => { + loadGlobalExecutionKPIAggregationsMock.mockRejectedValue({ body: { statusCode: 413 } }); + const wrapper = mountWithIntl( + + ); + // Let the load resolve + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(addDangerMock).not.toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx index 63ef7cb5bbc22d..d8606a525d821b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx @@ -109,6 +109,9 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => { }); setKpi(newKpi); } catch (e) { + if (e.body.statusCode === 413) { + return; + } toasts.addDanger({ title: API_FAILED_MESSAGE, text: e.body?.message ?? e, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.test.tsx index ffcb8f69ee7c41..9c18d74a53fdad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.test.tsx @@ -95,6 +95,7 @@ describe('rule_event_log_list_table', () => { useLoadRuleEventLogs.mockReturnValue({ data: mockLogResponse, isLoading: false, + hasExceedLogs: false, loadEventLogs: mockLoadEventLog, }); }); @@ -130,6 +131,7 @@ describe('rule_event_log_list_table', () => { useLoadRuleEventLogs.mockReturnValue({ data: [], isLoading: true, + hasExceedLogs: false, loadEventLogs: mockLoadEventLog, }); @@ -254,6 +256,7 @@ describe('rule_event_log_list_table', () => { total: 100, }, isLoading: true, + hasExceedLogs: false, loadEventLogs: mockLoadEventLog, }); }); @@ -306,6 +309,7 @@ describe('rule_event_log_list_table', () => { total: 0, }, isLoading: false, + hasExceedLogs: false, loadEventLogs: mockLoadEventLog, }); render(); @@ -324,6 +328,7 @@ describe('rule_event_log_list_table', () => { total: 1, }, isLoading: false, + hasExceedLogs: false, loadEventLogs: mockLoadEventLog, }); @@ -343,6 +348,7 @@ describe('rule_event_log_list_table', () => { total: 85, }, isLoading: false, + hasExceedLogs: false, loadEventLogs: mockLoadEventLog, }); @@ -368,6 +374,7 @@ describe('rule_event_log_list_table', () => { total: 85, }, isLoading: false, + hasExceedLogs: false, loadEventLogs: mockLoadEventLog, }); @@ -397,7 +404,7 @@ describe('rule_event_log_list_table', () => { outcomeFilter: [], page: 0, perPage: 10, - dateStart: 'now-24h', + dateStart: 'now-15m', dateEnd: 'now', }) ); @@ -460,6 +467,7 @@ describe('rule_event_log_list_table', () => { total: 1100, }, isLoading: false, + hasExceedLogs: false, loadEventLogs: mockLoadEventLog, }); }); @@ -485,6 +493,36 @@ describe('rule_event_log_list_table', () => { }); }); + describe('Show exceed document prompt', () => { + beforeEach(() => { + useLoadRuleEventLogs.mockReturnValue({ + data: { + data: [], + total: 11000, + }, + isLoading: false, + hasExceedLogs: true, + loadEventLogs: mockLoadEventLog, + }); + }); + + it('should show the exceed limit logs prompt normally', async () => { + render(); + + await waitFor(() => { + expect(screen.queryByTestId('exceedLimitLogsCallout')).toBeInTheDocument(); + }); + }); + + it('should hide the logs table', async () => { + render(); + + await waitFor(() => { + expect(screen.queryByTestId('eventLogList')).not.toBeInTheDocument(); + }); + }); + }); + it('renders errored action badges in message rows', async () => { useLoadRuleEventLogs.mockReturnValue({ data: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx index 73eb9f2c5c5369..a7ddc9ff04fb38 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx @@ -19,6 +19,7 @@ import { OnTimeChangeProps, EuiSwitch, EuiDataGridColumn, + EuiCallOut, } from '@elastic/eui'; import { IExecutionLog } from '@kbn/alerting-plugin/common'; import { useKibana } from '../../../../common/lib/kibana'; @@ -150,7 +151,7 @@ export const RuleEventLogListTable = ( }); // Date related states - const [dateStart, setDateStart] = useState('now-24h'); + const [dateStart, setDateStart] = useState('now-15m'); const [dateEnd, setDateEnd] = useState('now'); const [dateFormat] = useState(() => uiSettings?.get('dateFormat')); const [commonlyUsedRanges] = useState(() => { @@ -197,6 +198,9 @@ export const RuleEventLogListTable = ( const onError = useCallback( (e) => { + if (e.body.statusCode === 413) { + return; + } notifications.toasts.addDanger({ title: API_FAILED_MESSAGE, text: e.body?.message ?? e, @@ -205,7 +209,7 @@ export const RuleEventLogListTable = ( [notifications] ); - const { data, isLoading, loadEventLogs } = useLoadRuleEventLogs({ + const { data, isLoading, hasExceedLogs, loadEventLogs } = useLoadRuleEventLogs({ id: ruleId, sort: formattedSort as LoadExecutionLogAggregationsProps['sort'], outcomeFilter: filter, @@ -724,7 +728,19 @@ export const RuleEventLogListTable = ( - {renderList()} + {hasExceedLogs && ( + + } + data-test-subj="exceedLimitLogsCallout" + size="m" + /> + )} + {!hasExceedLogs && renderList()} {isOnLastPage && (