diff --git a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/field_maps/legacy_experimental_field_map.ts b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/field_maps/legacy_experimental_field_map.ts index 4b049516b458f..08d3dd1ba9d7d 100644 --- a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/field_maps/legacy_experimental_field_map.ts +++ b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/field_maps/legacy_experimental_field_map.ts @@ -12,6 +12,7 @@ import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_EVALUATION_VALUES, + ALERT_GROUPING, ALERT_GROUP, ALERT_GROUP_FIELD, ALERT_GROUP_VALUE, @@ -31,6 +32,12 @@ export const legacyExperimentalFieldMap = { required: false, array: true, }, + [ALERT_GROUPING]: { + type: 'object', + dynamic: true, + array: false, + required: false, + }, [ALERT_GROUP]: { type: 'object', array: true, diff --git a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_apm_schema.ts b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_apm_schema.ts index cc4264b4333f0..95a0f2b15107a 100644 --- a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_apm_schema.ts +++ b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_apm_schema.ts @@ -87,6 +87,7 @@ const ObservabilityApmAlertOptional = rt.partial({ value: schemaStringArray, }) ), + 'kibana.alert.grouping': schemaUnknown, labels: schemaUnknown, 'processor.event': schemaString, 'service.environment': schemaString, diff --git a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_logs_schema.ts b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_logs_schema.ts index 70f1bb12a7ade..38b70a36d5619 100644 --- a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_logs_schema.ts +++ b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_logs_schema.ts @@ -83,6 +83,7 @@ const ObservabilityLogsAlertOptional = rt.partial({ value: schemaStringArray, }) ), + 'kibana.alert.grouping': schemaUnknown, }); // prettier-ignore diff --git a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_metrics_schema.ts b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_metrics_schema.ts index 76511b4adfabb..f03f7aa629a50 100644 --- a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_metrics_schema.ts +++ b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_metrics_schema.ts @@ -83,6 +83,7 @@ const ObservabilityMetricsAlertOptional = rt.partial({ value: schemaStringArray, }) ), + 'kibana.alert.grouping': schemaUnknown, }); // prettier-ignore diff --git a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_slo_schema.ts b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_slo_schema.ts index cc0b15434dbce..54a6bd59520e0 100644 --- a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_slo_schema.ts +++ b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_slo_schema.ts @@ -83,6 +83,7 @@ const ObservabilitySloAlertOptional = rt.partial({ value: schemaStringArray, }) ), + 'kibana.alert.grouping': schemaUnknown, 'slo.id': schemaString, 'slo.instanceId': schemaString, 'slo.revision': schemaStringOrNumber, diff --git a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_threshold_schema.ts b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_threshold_schema.ts index af8f86ed57a13..10d7182c9b785 100644 --- a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_threshold_schema.ts +++ b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_threshold_schema.ts @@ -82,6 +82,7 @@ const ObservabilityThresholdAlertOptional = rt.partial({ value: schemaStringArray, }) ), + 'kibana.alert.grouping': schemaUnknown, }); // prettier-ignore diff --git a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_uptime_schema.ts b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_uptime_schema.ts index 32bf7e3621042..51b323e82d28d 100644 --- a/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_uptime_schema.ts +++ b/src/platform/packages/shared/kbn-alerts-as-data-utils/src/schemas/generated/observability_uptime_schema.ts @@ -89,6 +89,7 @@ const ObservabilityUptimeAlertOptional = rt.partial({ value: schemaStringArray, }) ), + 'kibana.alert.grouping': schemaUnknown, labels: schemaUnknown, 'location.id': schemaStringArray, 'location.name': schemaStringArray, diff --git a/src/platform/packages/shared/kbn-rule-data-utils/src/technical_field_names.ts b/src/platform/packages/shared/kbn-rule-data-utils/src/technical_field_names.ts index 318517bbc6c76..2501294afa034 100644 --- a/src/platform/packages/shared/kbn-rule-data-utils/src/technical_field_names.ts +++ b/src/platform/packages/shared/kbn-rule-data-utils/src/technical_field_names.ts @@ -90,6 +90,7 @@ const ALERT_EVALUATION_THRESHOLD = `${ALERT_NAMESPACE}.evaluation.threshold` as const ALERT_EVALUATION_VALUE = `${ALERT_NAMESPACE}.evaluation.value` as const; const ALERT_CONTEXT = `${ALERT_NAMESPACE}.context` as const; const ALERT_EVALUATION_VALUES = `${ALERT_NAMESPACE}.evaluation.values` as const; +const ALERT_GROUPING = `${ALERT_NAMESPACE}.grouping` as const; const ALERT_GROUP = `${ALERT_NAMESPACE}.group` as const; const ALERT_GROUP_FIELD = `${ALERT_GROUP}.field` as const; const ALERT_GROUP_VALUE = `${ALERT_GROUP}.value` as const; @@ -134,6 +135,7 @@ const fields = { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_EVALUATION_VALUES, + ALERT_GROUPING, ALERT_GROUP, ALERT_GROUP_FIELD, ALERT_GROUP_VALUE, @@ -209,6 +211,7 @@ export { ALERT_EVALUATION_VALUE, ALERT_CONTEXT, ALERT_EVALUATION_VALUES, + ALERT_GROUPING, ALERT_GROUP, ALERT_GROUP_FIELD, ALERT_GROUP_VALUE, diff --git a/x-pack/platform/packages/shared/alerting-rule-utils/index.ts b/x-pack/platform/packages/shared/alerting-rule-utils/index.ts index 921c04c4e03b8..19b7075db6f78 100644 --- a/x-pack/platform/packages/shared/alerting-rule-utils/index.ts +++ b/x-pack/platform/packages/shared/alerting-rule-utils/index.ts @@ -6,5 +6,10 @@ */ export { getEcsGroups } from './src/get_ecs_groups'; -export { getGroupByObject, getFormattedGroupBy } from './src/group_by_object_utils'; +export { + unflattenGrouping, + getFormattedGroups, + getFormattedGroupBy, + getGroupByObject, +} from './src/group_by_object_utils'; export type { Group, FieldsObject } from './src/types'; diff --git a/x-pack/platform/packages/shared/alerting-rule-utils/src/group_by_object_utils.test.ts b/x-pack/platform/packages/shared/alerting-rule-utils/src/group_by_object_utils.test.ts index 3735cb44183bf..c05d7088fee7b 100644 --- a/x-pack/platform/packages/shared/alerting-rule-utils/src/group_by_object_utils.test.ts +++ b/x-pack/platform/packages/shared/alerting-rule-utils/src/group_by_object_utils.test.ts @@ -5,7 +5,51 @@ * 2.0. */ -import { getFormattedGroupBy, getGroupByObject } from './group_by_object_utils'; +import { + getFormattedGroups, + unflattenGrouping, + getGroupByObject, + getFormattedGroupBy, +} from './group_by_object_utils'; + +describe('getFormattedGroups', () => { + it('should format groupBy correctly for empty input', () => { + expect(getFormattedGroups()).toBeUndefined(); + }); + + it('should format groupBy correctly for multiple groups', () => { + expect( + getFormattedGroups({ + 'host.name': 'host-0', + 'host.mac': '00-00-5E-00-53-23', + tags: 'event-0', + 'container.name': 'container-name', + }) + ).toEqual([ + { field: 'host.name', value: 'host-0' }, + { field: 'host.mac', value: '00-00-5E-00-53-23' }, + { field: 'tags', value: 'event-0' }, + { field: 'container.name', value: 'container-name' }, + ]); + }); +}); + +describe('unflattenGrouping', () => { + it('should return undefined when there is no grouping', () => { + expect(unflattenGrouping()).toBeUndefined(); + }); + + it('should return an object containing groups for one groupBy field', () => { + expect(unflattenGrouping({ 'host.name': 'host-0' })).toEqual({ host: { name: 'host-0' } }); + }); + + it('should return an object containing groups for multiple groupBy fields', () => { + expect(unflattenGrouping({ 'host.name': 'host-0', 'container.id': 'container-0' })).toEqual({ + container: { id: 'container-0' }, + host: { name: 'host-0' }, + }); + }); +}); describe('getFormattedGroupBy', () => { it('should format groupBy correctly for empty input', () => { @@ -53,10 +97,6 @@ describe('getFormattedGroupBy', () => { }); describe('getGroupByObject', () => { - it('should return empty object for undefined groupBy', () => { - expect(getFormattedGroupBy(undefined, new Set())).toEqual({}); - }); - it('should return an object containing groups for one groupBy field', () => { expect(getGroupByObject('host.name', new Set(['host-0', 'host-1']))).toEqual({ 'host-0': { host: { name: 'host-0' } }, diff --git a/x-pack/platform/packages/shared/alerting-rule-utils/src/group_by_object_utils.ts b/x-pack/platform/packages/shared/alerting-rule-utils/src/group_by_object_utils.ts index 2dbe77cbb93fc..360f766673fab 100644 --- a/x-pack/platform/packages/shared/alerting-rule-utils/src/group_by_object_utils.ts +++ b/x-pack/platform/packages/shared/alerting-rule-utils/src/group_by_object_utils.ts @@ -8,6 +8,8 @@ import { unflattenObject } from '@kbn/object-utils'; import { Group } from './types'; +// TODO: Remove after updating the group logic in the log threshold rule +// https://github.com/elastic/kibana/issues/220006 export const getGroupByObject = ( groupBy: string | string[] | undefined, groupValueSet: Set @@ -28,6 +30,16 @@ export const getGroupByObject = ( return groupKeyValueMappingsObject; }; +export const unflattenGrouping = ( + grouping?: Record | undefined +): Record | undefined => { + if (grouping) { + return unflattenObject(grouping); + } +}; + +// TODO: Remove after updating the group logic in the log threshold rule +// https://github.com/elastic/kibana/issues/220006 export const getFormattedGroupBy = ( groupBy: string | string[] | undefined, groupSet: Set @@ -46,3 +58,14 @@ export const getFormattedGroupBy = ( } return groupByKeysObjectMapping; }; + +export const getFormattedGroups = (grouping?: Record): Group[] | undefined => { + const groups: Group[] = []; + if (grouping) { + const groupKeys = Object.keys(grouping); + groupKeys.forEach((group) => { + groups.push({ field: group, value: grouping[group] }); + }); + } + return groups.length ? groups : undefined; +}; diff --git a/x-pack/platform/plugins/shared/alerting/server/integration_tests/__snapshots__/alert_as_data_fields.test.ts.snap b/x-pack/platform/plugins/shared/alerting/server/integration_tests/__snapshots__/alert_as_data_fields.test.ts.snap index 4926acc2a8219..aa7d715daae62 100644 --- a/x-pack/platform/plugins/shared/alerting/server/integration_tests/__snapshots__/alert_as_data_fields.test.ts.snap +++ b/x-pack/platform/plugins/shared/alerting/server/integration_tests/__snapshots__/alert_as_data_fields.test.ts.snap @@ -140,6 +140,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, "labels": Object { "dynamic": true, "required": false, @@ -232,6 +238,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, "labels": Object { "dynamic": true, "required": false, @@ -324,6 +336,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, "labels": Object { "dynamic": true, "required": false, @@ -416,6 +434,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, "labels": Object { "dynamic": true, "required": false, @@ -488,6 +512,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, }, } `; @@ -531,6 +561,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, }, } `; @@ -574,6 +610,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, }, } `; @@ -664,6 +706,18 @@ Object { exports[`Alert as data fields checks detect AAD fields changes for: observability.rules.custom_threshold 1`] = ` Object { + "dynamicTemplates": Array [ + Object { + "strings_as_keywords": Object { + "mapping": Object { + "ignore_above": 1024, + "type": "keyword", + }, + "match_mapping_type": "string", + "path_match": "kibana.alert.grouping.*", + }, + }, + ], "fieldMap": Object { "kibana.alert.context": Object { "array": false, @@ -701,6 +755,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, }, } `; @@ -9734,6 +9794,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, "slo.id": Object { "array": false, "required": false, @@ -10055,6 +10121,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, "labels": Object { "required": false, "type": "object", @@ -10199,6 +10271,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, "labels": Object { "required": false, "type": "object", @@ -10343,6 +10421,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, "labels": Object { "required": false, "type": "object", @@ -10487,6 +10571,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, "labels": Object { "required": false, "type": "object", @@ -10637,6 +10727,12 @@ Object { "required": false, "type": "keyword", }, + "kibana.alert.grouping": Object { + "array": false, + "dynamic": true, + "required": false, + "type": "object", + }, "labels": Object { "required": false, "type": "object", diff --git a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts index 9e1cce4745198..3bd11ad3689fd 100644 --- a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts +++ b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts @@ -9,6 +9,7 @@ import { RuleExecutorServicesMock, alertsMock } from '@kbn/alerting-plugin/serve import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; import { searchSourceCommonMock } from '@kbn/data-plugin/common/search/search_source/mocks'; import type { ISearchSource } from '@kbn/data-plugin/common'; +import { ALERT_GROUP } from '@kbn/rule-data-utils'; import { getErrorSource, TaskErrorSource, @@ -447,6 +448,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -457,6 +459,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -476,6 +479,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -486,6 +490,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -505,6 +510,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -515,6 +521,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -534,6 +541,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -544,6 +552,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -1086,6 +1095,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'host-01' }, + flattenGrouping: { 'host.name': 'host-01' }, context: { tags: ['host-01_tag1', 'host-01_tag2'], }, @@ -1099,6 +1109,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'host-02' }, + flattenGrouping: { 'host.name': 'host-02' }, context: { tags: ['host-02_tag1', 'host-02_tag2'], }, @@ -1126,6 +1137,9 @@ describe('The custom threshold alert type', () => { value: 'host-01', }, ], + 'kibana.alert.grouping': { + host: { name: 'host-01' }, + }, 'kibana.alert.reason': 'Average test.metric.1 is 1, above the threshold of 0.75. (duration: 1 min, data view: mockedDataViewName, group: host-01)', tags: ['host-01_tag1', 'host-01_tag2', 'ruleTag1', 'ruleTag2'], @@ -1147,6 +1161,9 @@ describe('The custom threshold alert type', () => { value: 'host-02', }, ], + 'kibana.alert.grouping': { + host: { name: 'host-02' }, + }, 'kibana.alert.reason': 'Average test.metric.1 is 3, above the threshold of 0.75. (duration: 1 min, data view: mockedDataViewName, group: host-02)', tags: ['host-02_tag1', 'host-02_tag2', 'ruleTag1', 'ruleTag2'], @@ -1310,6 +1327,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -1320,6 +1338,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, { @@ -1339,6 +1358,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -1356,6 +1376,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -1375,6 +1396,9 @@ describe('The custom threshold alert type', () => { value: 'a', }, ], + 'kibana.alert.grouping': { + groupByField: 'a', + }, 'kibana.alert.reason': 'Average test.metric.1 is 1, above or equal the threshold of 1; Average test.metric.2 is 3, above or equal the threshold of 3. (duration: 1 min, data view: mockedDataViewName, group: a)', tags: [], @@ -1459,7 +1483,7 @@ describe('The custom threshold alert type', () => { timestamp: new Date().toISOString(), shouldFire: true, isNoData: false, - bucketKey: { groupBy0: 'a' }, + bucketKey: { groupBy0: '*' }, }, }, ]); @@ -1475,7 +1499,7 @@ describe('The custom threshold alert type', () => { timestamp: new Date().toISOString(), shouldFire: false, isNoData: false, - bucketKey: { groupBy0: 'a' }, + bucketKey: { groupBy0: '*' }, }, }, ]); @@ -1521,6 +1545,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdCountCriterion, @@ -1531,6 +1556,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -1548,6 +1574,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdCountCriterion, @@ -1558,6 +1585,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -1604,6 +1632,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { 'host.name': 'a' }, + flattenGrouping: { 'host.name': 'a' }, context: { host: { name: 'a', @@ -1691,6 +1720,12 @@ describe('The custom threshold alert type', () => { }, hit: { 'host.name': 'host-0', + [ALERT_GROUP]: [ + { + field: 'host.name', + value: 'host-0', + }, + ], }, }, ]; @@ -2018,6 +2053,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -2028,6 +2064,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2059,6 +2096,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -2076,6 +2114,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2103,6 +2142,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -2120,6 +2160,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, c: { ...customThresholdNonCountCriterion, @@ -2137,6 +2178,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'c' }, + flattenGrouping: { groupByField: 'c' }, }, }, ]); @@ -2156,6 +2198,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -2166,6 +2209,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2262,6 +2306,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -2272,6 +2317,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2301,6 +2347,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdNonCountCriterion, @@ -2318,6 +2365,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2483,6 +2531,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2493,6 +2542,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2512,6 +2562,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2522,6 +2573,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2541,6 +2593,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2551,6 +2604,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2570,6 +2624,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2580,6 +2635,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2610,6 +2666,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2627,6 +2684,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, c: { ...customThresholdLastValueCriterion, @@ -2644,6 +2702,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'c' }, + flattenGrouping: { groupByField: 'c' }, }, }, ]); @@ -2671,6 +2730,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2681,6 +2741,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, c: { ...customThresholdLastValueCriterion, @@ -2691,6 +2752,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'c' }, + flattenGrouping: { groupByField: 'c' }, }, }, ]); @@ -2721,6 +2783,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2731,6 +2794,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2800,6 +2864,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2817,6 +2882,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, c: { ...customThresholdLastValueCriterion, @@ -2834,6 +2900,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'c' }, + flattenGrouping: { groupByField: 'c' }, }, }, ]); @@ -2855,6 +2922,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2865,6 +2933,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, c: { ...customThresholdLastValueCriterion, @@ -2875,6 +2944,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'c' }, + flattenGrouping: { groupByField: 'c' }, }, }, ]); @@ -2899,6 +2969,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2909,6 +2980,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -2946,6 +3018,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -2963,6 +3036,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, c: { ...customThresholdLastValueCriterion, @@ -2980,6 +3054,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'c' }, + flattenGrouping: { groupByField: 'c' }, }, }, ]); @@ -3001,6 +3076,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -3011,6 +3087,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: true, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, c: { ...customThresholdLastValueCriterion, @@ -3021,6 +3098,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'c' }, + flattenGrouping: { groupByField: 'c' }, }, }, ]); @@ -3048,6 +3126,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -3058,6 +3137,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: true, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -3122,6 +3202,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'host-01' }, + flattenGrouping: { 'host.name': 'host-01' }, context: { tags: ['host-01_tag1', 'host-01_tag2'], }, @@ -3135,6 +3216,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'host-02' }, + flattenGrouping: { 'host.name': 'host-02' }, context: { tags: ['host-02_tag1', 'host-02_tag2'], }, @@ -3162,6 +3244,9 @@ describe('The custom threshold alert type', () => { value: 'host-01', }, ], + 'kibana.alert.grouping': { + host: { name: 'host-01' }, + }, 'kibana.alert.reason': 'Last value of test.metric.1 is 1, above the threshold of 0.75. (duration: 1 min, data view: mockedDataViewName, group: host-01)', tags: ['host-01_tag1', 'host-01_tag2', 'ruleTag1', 'ruleTag2'], @@ -3183,6 +3268,9 @@ describe('The custom threshold alert type', () => { value: 'host-02', }, ], + 'kibana.alert.grouping': { + host: { name: 'host-02' }, + }, 'kibana.alert.reason': 'Last value of test.metric.1 is 3, above the threshold of 0.75. (duration: 1 min, data view: mockedDataViewName, group: host-02)', tags: ['host-02_tag1', 'host-02_tag2', 'ruleTag1', 'ruleTag2'], @@ -3346,6 +3434,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -3356,6 +3445,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, { @@ -3375,6 +3465,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -3392,6 +3483,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -3411,6 +3503,9 @@ describe('The custom threshold alert type', () => { value: 'a', }, ], + 'kibana.alert.grouping': { + groupByField: 'a', + }, 'kibana.alert.reason': 'Last value of test.metric.1 is 1, above or equal the threshold of 1; Last value of test.metric.2 is 3, above or equal the threshold of 3. (duration: 1 min, data view: mockedDataViewName, group: a)', tags: [], @@ -3709,6 +3804,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -3719,6 +3815,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -3750,6 +3847,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -3767,6 +3865,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -3794,6 +3893,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -3811,6 +3911,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, c: { ...customThresholdLastValueCriterion, @@ -3828,6 +3929,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'c' }, + flattenGrouping: { groupByField: 'c' }, }, }, ]); @@ -3847,6 +3949,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -3857,6 +3960,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -3953,6 +4057,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -3963,6 +4068,7 @@ describe('The custom threshold alert type', () => { shouldFire: true, isNoData: false, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); @@ -3992,6 +4098,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'a' }, + flattenGrouping: { groupByField: 'a' }, }, b: { ...customThresholdLastValueCriterion, @@ -4009,6 +4116,7 @@ describe('The custom threshold alert type', () => { shouldFire: false, isNoData: true, bucketKey: { groupBy0: 'b' }, + flattenGrouping: { groupByField: 'b' }, }, }, ]); diff --git a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts index 71a56a07dd8f4..f7664b9c8de77 100644 --- a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts +++ b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts @@ -12,13 +12,13 @@ import { ALERT_EVALUATION_THRESHOLD, ALERT_REASON, ALERT_GROUP, - ALERT_RULE_PARAMETERS, + ALERT_GROUPING, } from '@kbn/rule-data-utils'; import { LocatorPublic } from '@kbn/share-plugin/common'; import { RecoveredActionGroup } from '@kbn/alerting-plugin/common'; import { IBasePath, Logger, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { AlertsClientError, RuleExecutorOptions } from '@kbn/alerting-plugin/server'; -import { getEcsGroups, getFormattedGroupBy, getGroupByObject } from '@kbn/alerting-rule-utils'; +import { getEcsGroups, getFormattedGroups, unflattenGrouping } from '@kbn/alerting-rule-utils'; import { createTaskRunError, TaskErrorSource } from '@kbn/task-manager-plugin/server'; import { getEsQueryConfig } from '../../../utils/get_es_query_config'; import { AlertsLocatorParams, getAlertDetailsUrl } from '../../../../common'; @@ -170,8 +170,6 @@ export const createCustomThresholdExecutor = ({ } } - const groupByKeysObjectMapping = getFormattedGroupBy(params.groupBy, resultGroupSet); - const groupingObject = getGroupByObject(params.groupBy, resultGroupSet); const groupArray = [...resultGroupSet]; const nextMissingGroups = new Set(); const hasGroups = !isEqual(groupArray, [UNGROUPED_FACTORY_KEY]); @@ -242,6 +240,12 @@ export const createCustomThresholdExecutor = ({ const timestamp = startedAt.toISOString(); const threshold = getThreshold(criteria); const evaluationValues = getEvaluationValues(alertResults, group); + // alertResults is an array since there can be multiple conditions + // we use the first result as the group information is the same between different conditions + // and we should always have at least one condition + const flattenGrouping = alertResults[0][group].flattenGrouping; + const groups = getFormattedGroups(flattenGrouping); + const grouping = unflattenGrouping(flattenGrouping); const actionGroupId: CustomThresholdActionGroup = nextState === AlertStates.OK ? RecoveredActionGroup.id @@ -259,8 +263,6 @@ export const createCustomThresholdExecutor = ({ new Set([...(additionalContext.tags ?? []), ...options.rule.tags]) ); - const groups = groupByKeysObjectMapping[group]; - const { uuid, start } = alertsClient.report({ id: `${group}`, actionGroup: actionGroupId, @@ -268,7 +270,10 @@ export const createCustomThresholdExecutor = ({ [ALERT_REASON]: reason, [ALERT_EVALUATION_VALUES]: evaluationValues, [ALERT_EVALUATION_THRESHOLD]: threshold, + // Array of Group, example: [ { field: 'host.name', value: 'host-0' }] [ALERT_GROUP]: groups, + // Object, example: { host: { name: 'host-0' } } + [ALERT_GROUPING]: grouping, ...flattenAdditionalContext(additionalContext), ...getEcsGroups(groups), }, @@ -281,8 +286,10 @@ export const createCustomThresholdExecutor = ({ id: `${group}`, context: { alertDetailsUrl: getAlertDetailsUrl(basePath, spaceId, uuid), - group: groupByKeysObjectMapping[group], - grouping: groupingObject[group], + // Array of Group, example: [ { field: 'host.name', value: 'host-0' }] + group: groups, + // Object, example: { host: { name: 'host-0' } } + grouping, reason, timestamp, value: alertResults.map((result) => { @@ -310,33 +317,12 @@ export const createCustomThresholdExecutor = ({ alertsClient.setAlertLimitReached(hasReachedLimit); const recoveredAlerts = alertsClient.getRecoveredAlerts() ?? []; - let groupingObjectForRecovered: Record = {}; - - // extracing group by fields from kibana.alert.rule.params, - // since all recovered alert documents will have same group by fields, - // we are only checking first recovered alert document - if (recoveredAlerts.length > 0) { - const alertHit = recoveredAlerts[0].hit; - const ruleParamsOfRecoveredAlert = alertHit?.[ALERT_RULE_PARAMETERS] as EvaluatedRuleParams; - const groupByFields = ruleParamsOfRecoveredAlert?.groupBy; - - groupingObjectForRecovered = getGroupByObject( - groupByFields, - new Set(recoveredAlerts.map((recoveredAlert) => recoveredAlert.alert.getId())) - ); - } - - const groupByKeysObjectForRecovered = getFormattedGroupBy( - params.groupBy, - new Set(recoveredAlerts.map((recoveredAlert) => recoveredAlert.alert.getId())) - ); - for (const recoveredAlert of recoveredAlerts) { const recoveredAlertId = recoveredAlert.alert.getId(); const alertUuid = recoveredAlert.alert.getUuid(); const indexedStartedAt = recoveredAlert.alert.getStart() ?? startedAt.toISOString(); - const group = groupByKeysObjectForRecovered[recoveredAlertId]; - const grouping = groupingObjectForRecovered[recoveredAlertId]; + const group = recoveredAlert.hit?.[ALERT_GROUP]; + const grouping = recoveredAlert.hit?.[ALERT_GROUPING]; const alertHits = recoveredAlert.hit; const additionalContext = getContextForRecoveredAlerts(alertHits); diff --git a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts index e0d861f53ae38..93382eaeee0cf 100644 --- a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts +++ b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts @@ -33,6 +33,7 @@ export type Evaluation = CustomMetricExpressionParams & { shouldFire: boolean; isNoData: boolean; bucketKey: Record; + flattenGrouping?: Record; context?: AdditionalContext; }; @@ -118,6 +119,7 @@ export const evaluateRule = async ; } & AdditionalContext >; @@ -149,11 +150,17 @@ export const getData = async ( }; } else { const value = aggregatedValue ? aggregatedValue.value : null; + const flattenGrouping: Record = {}; + const groups: string[] = typeof groupBy === 'string' ? [groupBy] : groupBy ?? []; + groups.map((group: string, groupIndex) => { + flattenGrouping[group] = bucket.key[`groupBy${groupIndex}`]; + }); previous[key] = { trigger: (shouldTrigger && shouldTrigger.value > 0) || false, value, bucketKey: bucket.key, + flattenGrouping, container: containerList, ...additionalContextSource, }; diff --git a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts index 4e55485089f63..500b9cf108831 100644 --- a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts +++ b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts @@ -48,7 +48,21 @@ import { CustomThresholdAlert } from './types'; export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = { context: THRESHOLD_RULE_REGISTRATION_CONTEXT, - mappings: { fieldMap: legacyExperimentalFieldMap }, + mappings: { + fieldMap: legacyExperimentalFieldMap, + dynamicTemplates: [ + { + strings_as_keywords: { + path_match: 'kibana.alert.grouping.*', + match_mapping_type: 'string', + mapping: { + type: 'keyword', + ignore_above: 1024, + }, + }, + }, + ], + }, useEcs: true, useLegacyAlerts: false, shouldWrite: true, diff --git a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/types.ts b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/types.ts index 052741c474f46..78a391a1d0f76 100644 --- a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/types.ts +++ b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/types.ts @@ -17,6 +17,7 @@ import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUES, ALERT_GROUP, + ALERT_GROUPING, } from '@kbn/rule-data-utils'; import { Group } from '../../../../common/typings'; import { @@ -54,6 +55,7 @@ export type CustomThresholdAlertState = AlertState; // no specific instance stat export type CustomThresholdAlertContext = AlertContext & { alertDetailsUrl: string; group?: object; + grouping?: Record; reason?: string; timestamp: string; // ISO string // String type is for [NO DATA] @@ -80,4 +82,5 @@ export type CustomThresholdAlert = Omit< [ALERT_EVALUATION_VALUES]?: Array; [ALERT_EVALUATION_THRESHOLD]?: Array; [ALERT_GROUP]?: Group[]; + [ALERT_GROUPING]?: Record; }; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/group_by_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/group_by_fired.ts index 457b8bdceae44..6fd6ce4a9a58b 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/group_by_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/group_by_fired.ts @@ -162,6 +162,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { value: '{{context.value}}', host: '{{context.host}}', group: '{{context.group}}', + grouping: '{{context.grouping}}', }, ], }, @@ -248,6 +249,16 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { value: 'container-0', }, ]); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.grouping') + .eql({ + host: { + name: 'host-0', + }, + container: { + id: 'container-0', + }, + }); expect(resp.hits.hits[0]._source).property('kibana.alert.evaluation.threshold').eql([0.2]); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.parameters') @@ -288,6 +299,9 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { expect(resp.hits.hits[0]._source?.group).eql( '{"field":"host.name","value":"host-0"},{"field":"container.id","value":"container-0"}' ); + expect(resp.hits.hits[0]._source?.grouping).eql( + '{"host":{"name":"host-0"},"container":{"id":"container-0"}}' + ); }); }); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/rate_bytes_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/rate_bytes_fired.ts index 4011917659110..a39a86661a2a3 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/rate_bytes_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/rate_bytes_fired.ts @@ -151,6 +151,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { value: '{{context.value}}', host: '{{context.host}}', group: '{{context.group}}', + grouping: '{{context.grouping}}', }, ], }, @@ -232,6 +233,16 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { value: 'container-0', }, ]); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.grouping') + .eql({ + host: { + name: 'host-0', + }, + container: { + id: 'container-0', + }, + }); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.parameters') @@ -273,6 +284,9 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { expect(resp.hits.hits[0]._source?.group).eql( '{"field":"host.name","value":"host-0"},{"field":"container.id","value":"container-0"}' ); + expect(resp.hits.hits[0]._source?.grouping).eql( + '{"host":{"name":"host-0"},"container":{"id":"container-0"}}' + ); }); }); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/types.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/types.ts index 9002e9991292f..65983c404ff4f 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/types.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/types.ts @@ -16,6 +16,7 @@ export interface ActionDocument { viewInAppUrl: string; host?: string; group?: string; + grouping?: string; } export interface LogsExplorerLocatorParsedParams extends SerializableRecord {