From ce8fa36111f9a74588b824f8a405bf827c8f68dc Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 18 Dec 2024 14:40:38 +0100 Subject: [PATCH] [ResponseOps][Cases] Fill working alert status with updated at time (#204282) Fixes: https://github.com/elastic/kibana/issues/192252 ### How to test: I've created a rule in Security Solution which triggers alerts. After attached couple alerts to the case. Then I've closed case, so status alerts will be closed as well so I can filter out them from other alerts. After I checked if they have filled `kibana.alert.workflow_status_updated_at` column. ![Screenshot 2024-12-16 at 09 57 16](https://github.com/user-attachments/assets/b090e5f6-4aca-4c7f-8367-9cc2ba4412e8) ### Checklist Check the PR satisfies following conditions. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../server/services/alerts/index.test.ts | 31 ++++++++++++++----- .../cases/server/services/alerts/index.ts | 8 +++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/cases/server/services/alerts/index.test.ts b/x-pack/plugins/cases/server/services/alerts/index.test.ts index a18681c6478a3..450efd67c9e12 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.test.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.test.ts @@ -16,11 +16,19 @@ describe('updateAlertsStatus', () => { const alertsClient = alertsClientMock.create(); let alertService: AlertService; - beforeEach(async () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date('2022-02-21T17:35:00Z')); + alertService = new AlertService(esClient, logger, alertsClient); jest.clearAllMocks(); }); + afterEach(() => { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + describe('happy path', () => { it('updates the status of the alert correctly', async () => { const args = [{ id: 'alert-id-1', index: '.siem-signals', status: CaseStatuses.closed }]; @@ -41,7 +49,8 @@ describe('updateAlertsStatus', () => { "script": Object { "lang": "painless", "source": "if (ctx._source['kibana.alert.workflow_status'] != null) { - ctx._source['kibana.alert.workflow_status'] = 'closed' + ctx._source['kibana.alert.workflow_status'] = 'closed'; + ctx._source['kibana.alert.workflow_status_updated_at'] = '2022-02-21T17:35:00.000Z'; } if (ctx._source.signal != null && ctx._source.signal.status != null) { ctx._source.signal.status = 'closed' @@ -80,7 +89,8 @@ describe('updateAlertsStatus', () => { "script": Object { "lang": "painless", "source": "if (ctx._source['kibana.alert.workflow_status'] != null) { - ctx._source['kibana.alert.workflow_status'] = 'closed' + ctx._source['kibana.alert.workflow_status'] = 'closed'; + ctx._source['kibana.alert.workflow_status_updated_at'] = '2022-02-21T17:35:00.000Z'; } if (ctx._source.signal != null && ctx._source.signal.status != null) { ctx._source.signal.status = 'closed' @@ -115,7 +125,8 @@ describe('updateAlertsStatus', () => { "script": Object { "lang": "painless", "source": "if (ctx._source['kibana.alert.workflow_status'] != null) { - ctx._source['kibana.alert.workflow_status'] = 'acknowledged' + ctx._source['kibana.alert.workflow_status'] = 'acknowledged'; + ctx._source['kibana.alert.workflow_status_updated_at'] = '2022-02-21T17:35:00.000Z'; } if (ctx._source.signal != null && ctx._source.signal.status != null) { ctx._source.signal.status = 'acknowledged' @@ -154,7 +165,8 @@ describe('updateAlertsStatus', () => { "script": Object { "lang": "painless", "source": "if (ctx._source['kibana.alert.workflow_status'] != null) { - ctx._source['kibana.alert.workflow_status'] = 'closed' + ctx._source['kibana.alert.workflow_status'] = 'closed'; + ctx._source['kibana.alert.workflow_status_updated_at'] = '2022-02-21T17:35:00.000Z'; } if (ctx._source.signal != null && ctx._source.signal.status != null) { ctx._source.signal.status = 'closed' @@ -183,7 +195,8 @@ describe('updateAlertsStatus', () => { "script": Object { "lang": "painless", "source": "if (ctx._source['kibana.alert.workflow_status'] != null) { - ctx._source['kibana.alert.workflow_status'] = 'open' + ctx._source['kibana.alert.workflow_status'] = 'open'; + ctx._source['kibana.alert.workflow_status_updated_at'] = '2022-02-21T17:35:00.000Z'; } if (ctx._source.signal != null && ctx._source.signal.status != null) { ctx._source.signal.status = 'open' @@ -222,7 +235,8 @@ describe('updateAlertsStatus', () => { "script": Object { "lang": "painless", "source": "if (ctx._source['kibana.alert.workflow_status'] != null) { - ctx._source['kibana.alert.workflow_status'] = 'closed' + ctx._source['kibana.alert.workflow_status'] = 'closed'; + ctx._source['kibana.alert.workflow_status_updated_at'] = '2022-02-21T17:35:00.000Z'; } if (ctx._source.signal != null && ctx._source.signal.status != null) { ctx._source.signal.status = 'closed' @@ -251,7 +265,8 @@ describe('updateAlertsStatus', () => { "script": Object { "lang": "painless", "source": "if (ctx._source['kibana.alert.workflow_status'] != null) { - ctx._source['kibana.alert.workflow_status'] = 'open' + ctx._source['kibana.alert.workflow_status'] = 'open'; + ctx._source['kibana.alert.workflow_status_updated_at'] = '2022-02-21T17:35:00.000Z'; } if (ctx._source.signal != null && ctx._source.signal.status != null) { ctx._source.signal.status = 'open' diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts index 50a8e286cea4e..94694b7f243b5 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.ts @@ -10,7 +10,10 @@ import { isEmpty } from 'lodash'; import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import type { STATUS_VALUES } from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names'; -import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names'; +import { + ALERT_WORKFLOW_STATUS, + ALERT_WORKFLOW_STATUS_UPDATED_AT, +} from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names'; import type { MgetResponse } from '@elastic/elasticsearch/lib/api/types'; import type { AlertsClient } from '@kbn/rule-registry-plugin/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; @@ -169,7 +172,8 @@ export class AlertService { body: { script: { source: `if (ctx._source['${ALERT_WORKFLOW_STATUS}'] != null) { - ctx._source['${ALERT_WORKFLOW_STATUS}'] = '${status}' + ctx._source['${ALERT_WORKFLOW_STATUS}'] = '${status}'; + ctx._source['${ALERT_WORKFLOW_STATUS_UPDATED_AT}'] = '${new Date().toISOString()}'; } if (ctx._source.signal != null && ctx._source.signal.status != null) { ctx._source.signal.status = '${status}'