diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/wrap_suppressed_threshold_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/wrap_suppressed_threshold_alerts.ts index a3df4faa14c56..e2fa437a03543 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/wrap_suppressed_threshold_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/wrap_suppressed_threshold_alerts.ts @@ -6,6 +6,7 @@ */ import objectHash from 'object-hash'; +import sortBy from 'lodash/sortBy'; import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas'; import { @@ -79,9 +80,7 @@ export const wrapSuppressedThresholdALerts = ({ completeRule.ruleParams.ruleId ); - const suppressedValues = Object.entries(bucket.key) - .map(([key, value]) => value) - .sort((a, b) => a.localeCompare(b)); + const suppressedValues = sortBy(Object.entries(bucket.key).map(([_, value]) => value)); const id = objectHash([ hit._index, diff --git a/x-pack/test/functional/es_archives/security_solution/ecs_compliant/mappings.json b/x-pack/test/functional/es_archives/security_solution/ecs_compliant/mappings.json index 897ac651fcb43..1e2c79a11e26e 100644 --- a/x-pack/test/functional/es_archives/security_solution/ecs_compliant/mappings.json +++ b/x-pack/test/functional/es_archives/security_solution/ecs_compliant/mappings.json @@ -66,6 +66,9 @@ "properties": { "name": { "type": "keyword" + }, + "uptime": { + "type": "long" } } }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threshold_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threshold_alert_suppression.ts index 59ab5185f6ab6..39b012748930b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threshold_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/threshold_alert_suppression.ts @@ -655,6 +655,82 @@ export default ({ getService }: FtrProviderContext) => { }); }); + // should work correctly if one of the suppressed fields is keyword, another - number + it('should update an existing alert in the time window with multiple fields of different types', async () => { + const id = uuidv4(); + const timestamp = '2020-10-28T05:45:00.000Z'; + const firstRunDoc = { + id, + '@timestamp': timestamp, + agent: { + name: 'agent-1', + }, + host: { + uptime: 100, + }, + }; + + const secondRunDoc = { + ...firstRunDoc, + '@timestamp': '2020-10-28T06:15:00.000Z', + }; + + await indexListOfDocuments([firstRunDoc, firstRunDoc, secondRunDoc, secondRunDoc]); + + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForAlertTesting(['ecs_compliant']), + query: `id:${id}`, + threshold: { + field: ['agent.name', 'host.uptime'], + value: 2, + }, + alert_suppression: { + duration: { + value: 2, + unit: 'h', + }, + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 2, + }); + + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['agent.type', ALERT_ORIGINAL_TIME], + }); + + expect(previewAlerts.length).toEqual(1); + + expect(previewAlerts[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: 'agent-1', + }, + { + field: 'host.uptime', + value: 100, + }, + ], + [TIMESTAMP]: '2020-10-28T06:00:00.000Z', + [ALERT_LAST_DETECTED]: '2020-10-28T06:30:00.000Z', + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: '2020-10-28T06:00:00.000Z', + [ALERT_SUPPRESSION_END]: '2020-10-28T06:30:00.000Z', + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }) + ); + }); + it('should correctly suppress when using a timestamp override', async () => { const id = uuidv4(); const timestamp = '2020-10-28T05:45:00.000Z';