diff --git a/x-pack/test/alerting_api_integration/observability/helpers/data_view.ts b/x-pack/test/alerting_api_integration/observability/helpers/data_view.ts index e8a03df2d0713..0b0e85b104962 100644 --- a/x-pack/test/alerting_api_integration/observability/helpers/data_view.ts +++ b/x-pack/test/alerting_api_integration/observability/helpers/data_view.ts @@ -47,7 +47,7 @@ export const deleteDataView = async ({ id: string; }) => { const { body } = await supertest - .delete(`/api/content_management/rpc/create`) + .post(`/api/content_management/rpc/delete`) .set('kbn-xsrf', 'foo') .send({ contentTypeId: 'index-pattern', diff --git a/x-pack/test/alerting_api_integration/observability/index.ts b/x-pack/test/alerting_api_integration/observability/index.ts index c3fb33202ce9e..7bc5a5caab0b2 100644 --- a/x-pack/test/alerting_api_integration/observability/index.ts +++ b/x-pack/test/alerting_api_integration/observability/index.ts @@ -8,12 +8,16 @@ // eslint-disable-next-line import/no-default-export export default function ({ loadTestFile }: any) { describe('Observability Rules', () => { - describe('MetricsUI Endpoints', () => { + describe('Rules Endpoints', () => { loadTestFile(require.resolve('./metric_threshold_rule')); - loadTestFile(require.resolve('./threshold_rule')); + loadTestFile(require.resolve('./threshold_rule/avg_pct_fired')); + loadTestFile(require.resolve('./threshold_rule/avg_pct_no_data')); + loadTestFile(require.resolve('./threshold_rule/avg_us_fired')); + loadTestFile(require.resolve('./threshold_rule/custom_eq_avg_bytes_fired')); + loadTestFile(require.resolve('./threshold_rule/documents_count_fired')); + loadTestFile(require.resolve('./threshold_rule/group_by_fired')); loadTestFile(require.resolve('./threshold_rule_data_view')); }); - describe('Synthetics', () => { loadTestFile(require.resolve('./synthetics_rule')); }); diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_fired.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_fired.ts new file mode 100644 index 0000000000000..5c48eb3571ec2 --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_fired.ts @@ -0,0 +1,182 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { cleanup, generate } from '@kbn/infra-forge'; +import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + + describe('Threshold rule - AVG - PCT - FIRED', () => { + const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await createDataView({ + supertest, + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + customMetrics: [ + { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Threshold (Technical Preview)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>', + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + customMetrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_no_data.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_no_data.ts new file mode 100644 index 0000000000000..400d5ad55e730 --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_no_data.ts @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; + +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + + describe('Threshold rule - AVG - PCT - NoData', () => { + const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id-no-data'; + let actionId: string; + let ruleId: string; + + before(async () => { + await createDataView({ + supertest, + name: 'no-data-pattern', + id: DATA_VIEW_ID, + title: 'no-data-pattern', + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + customMetrics: [ + { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Threshold (Technical Preview)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.nodata'); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>', + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + customMetrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + index: 'data-view-id-no-data', + query: { query: '', language: 'kuery' }, + }, + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_us_fired.ts similarity index 95% rename from x-pack/test/alerting_api_integration/observability/threshold_rule.ts rename to x-pack/test/alerting_api_integration/observability/threshold_rule/avg_us_fired.ts index d91d30cae7f73..65db9101c04e8 100644 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule.ts +++ b/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_us_fired.ts @@ -12,15 +12,15 @@ import { Aggregators, Comparator } from '@kbn/observability-plugin/common/thresh import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; -import { FtrProviderContext } from '../common/ftr_provider_context'; -import { createIndexConnector, createRule } from './helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from './helpers/data_view'; -import { getSyntraceClient, generateData } from './helpers/syntrace'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { getSyntraceClient, generateData } from '../helpers/syntrace'; import { waitForAlertInIndex, waitForDocumentInIndex, waitForRuleStatus, -} from './helpers/alerting_wait_for_helpers'; +} from '../helpers/alerting_wait_for_helpers'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { @@ -33,7 +33,7 @@ export default function ({ getService }: FtrProviderContext) { const kibanaUrl = format(kibanaServerConfig); const supertest = getService('supertest'); - describe('Threshold rule', () => { + describe('Threshold rule - AVG - US - FIRED', () => { const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/custom_eq_avg_bytes_fired.ts new file mode 100644 index 0000000000000..b05d60ca7f750 --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/threshold_rule/custom_eq_avg_bytes_fired.ts @@ -0,0 +1,194 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { cleanup, generate } from '@kbn/infra-forge'; +import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + + describe('Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { + const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await createDataView({ + supertest, + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.9], + timeSize: 1, + timeUnit: 'm', + customMetrics: [ + { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, + { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, + ], + equation: 'A / B ', + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Threshold (Technical Preview)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [0.9], + timeSize: 1, + timeUnit: 'm', + customMetrics: [ + { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, + { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, + ], + equation: 'A / B ', + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/documents_count_fired.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/documents_count_fired.ts new file mode 100644 index 0000000000000..0ccff07f20828 --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/threshold_rule/documents_count_fired.ts @@ -0,0 +1,180 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { cleanup, generate } from '@kbn/infra-forge'; +import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + + describe('Threshold rule - DOCUMENTS_COUNT - FIRED', () => { + const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await createDataView({ + supertest, + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + threshold: [2], + timeSize: 1, + timeUnit: 'm', + customMetrics: [{ name: 'A', filter: '', aggType: Aggregators.COUNT }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Threshold (Technical Preview)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>', + threshold: [2], + timeSize: 1, + timeUnit: 'm', + customMetrics: [{ name: 'A', filter: '', aggType: 'count' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/group_by_fired.ts b/x-pack/test/alerting_api_integration/observability/threshold_rule/group_by_fired.ts new file mode 100644 index 0000000000000..8b2db8b13ca05 --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/threshold_rule/group_by_fired.ts @@ -0,0 +1,237 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { cleanup, generate } from '@kbn/infra-forge'; +import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { + waitForAlertInIndex, + waitForDocumentInIndex, + waitForRuleStatus, +} from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + let alertId: string; + let startedAt: string; + + describe('Threshold rule - GROUP_BY - FIRED', () => { + const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await createDataView({ + supertest, + name: 'metrics-fake_hosts', + id: DATA_VIEW_ID, + title: 'metrics-fake_hosts', + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'alerts', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT_OR_EQ, + threshold: [0.2], + timeSize: 1, + timeUnit: 'm', + customMetrics: [ + { name: 'A', field: 'system.cpu.total.norm.pct', aggType: Aggregators.AVERAGE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + groupBy: ['host.name'], + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + alertDetailsUrl: '{{context.alertDetailsUrl}}', + reason: '{{context.reason}}', + value: '{{context.value}}', + host: '{{context.host}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; + startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start']; + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Threshold (Technical Preview)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', 'host-0'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source).property('host.name', 'host-0'); + expect(resp.hits.hits[0]._source) + .property('host.mac') + .eql(['00-00-5E-00-53-23', '00-00-5E-00-53-24']); + expect(resp.hits.hits[0]._source).property('container.id', 'container-0'); + expect(resp.hits.hits[0]._source).property('container.name', 'container-name'); + expect(resp.hits.hits[0]._source).not.property('container.cpu'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + aggType: 'custom', + comparator: '>=', + threshold: [0.2], + timeSize: 1, + timeUnit: 'm', + customMetrics: [{ name: 'A', field: 'system.cpu.total.norm.pct', aggType: 'avg' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + groupBy: ['host.name'], + }); + }); + + it('should set correct action variables', async () => { + const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString(); + const resp = await waitForDocumentInIndex<{ + ruleType: string; + alertDetailsUrl: string; + reason: string; + value: string; + host: string; + }>({ + esClient, + indexName: ALERT_ACTION_INDEX, + }); + + expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.threshold'); + expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( + `https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` + ); + expect(resp.hits.hits[0]._source?.reason).eql( + 'Custom equation is 0.8 in the last 1 min for host-0. Alert when >= 0.2.' + ); + expect(resp.hits.hits[0]._source?.value).eql('0.8'); + expect(resp.hits.hits[0]._source?.host).eql( + '{"name":"host-0","mac":["00-00-5E-00-53-23","00-00-5E-00-53-24"]}' + ); + }); + }); + }); +}