From 0d7c5e362c110d004dcb48871eba5be7faca179f Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 31 Aug 2021 09:42:33 +0200 Subject: [PATCH] [RAC][Observability] preserve lifecycle alert changes for active alerts (#110124) * preserve lifecycle changes for active alerts * fix failing tests * fix failing lifecycle executor tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../utils/create_lifecycle_executor.test.ts | 30 ++++++++++++++--- .../server/utils/create_lifecycle_executor.ts | 12 +++---- .../utils/create_lifecycle_rule_type.test.ts | 32 ++++++++++++++++++- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts index c1a4fccaf205b..ad09228580637 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts @@ -17,6 +17,7 @@ import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, + ALERT_WORKFLOW_STATUS, ALERT_UUID, EVENT_ACTION, EVENT_KIND, @@ -118,7 +119,7 @@ describe('createLifecycleExecutor', () => { ); }); - it('overwrites existing documents for repeatedly firing alerts', async () => { + it('updates existing documents for repeatedly firing alerts', async () => { const logger = loggerMock.create(); const ruleDataClientMock = createRuleDataClientMock(); ruleDataClientMock.getReader().search.mockResolvedValue({ @@ -126,17 +127,35 @@ describe('createLifecycleExecutor', () => { hits: [ { fields: { + '@timestamp': '', [ALERT_ID]: 'TEST_ALERT_0', + [ALERT_UUID]: 'ALERT_0_UUID', + [ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME', [ALERT_RULE_CONSUMER]: 'CONSUMER', + [ALERT_RULE_NAME]: 'NAME', + [ALERT_RULE_PRODUCER]: 'PRODUCER', [ALERT_RULE_TYPE_ID]: 'RULE_TYPE_ID', - labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must not show up in the written doc + [ALERT_RULE_UUID]: 'RULE_UUID', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [ALERT_WORKFLOW_STATUS]: 'closed', + [SPACE_IDS]: ['fake-space-id'], + labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must show up in the written doc }, }, { fields: { + '@timestamp': '', [ALERT_ID]: 'TEST_ALERT_1', + [ALERT_UUID]: 'ALERT_1_UUID', + [ALERT_RULE_CATEGORY]: 'RULE_TYPE_NAME', [ALERT_RULE_CONSUMER]: 'CONSUMER', + [ALERT_RULE_NAME]: 'NAME', + [ALERT_RULE_PRODUCER]: 'PRODUCER', [ALERT_RULE_TYPE_ID]: 'RULE_TYPE_ID', + [ALERT_RULE_UUID]: 'RULE_UUID', + [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [ALERT_WORKFLOW_STATUS]: 'open', + [SPACE_IDS]: ['fake-space-id'], labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, // this must not show up in the written doc }, }, @@ -188,14 +207,19 @@ describe('createLifecycleExecutor', () => { { index: { _id: 'TEST_ALERT_0_UUID' } }, expect.objectContaining({ [ALERT_ID]: 'TEST_ALERT_0', + [ALERT_WORKFLOW_STATUS]: 'closed', [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + labels: { LABEL_0_KEY: 'LABEL_0_VALUE' }, + [EVENT_ACTION]: 'active', [EVENT_KIND]: 'signal', }), { index: { _id: 'TEST_ALERT_1_UUID' } }, expect.objectContaining({ [ALERT_ID]: 'TEST_ALERT_1', + [ALERT_WORKFLOW_STATUS]: 'open', [ALERT_STATUS]: ALERT_STATUS_ACTIVE, + [EVENT_ACTION]: 'active', [EVENT_KIND]: 'signal', }), @@ -216,8 +240,6 @@ describe('createLifecycleExecutor', () => { }); it('updates existing documents for recovered alerts', async () => { - // NOTE: the documents should actually also be updated for recurring, - // active alerts (see elastic/kibana#108670) const logger = loggerMock.create(); const ruleDataClientMock = createRuleDataClientMock(); ruleDataClientMock.getReader().search.mockResolvedValue({ diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 97337e3a5e09e..231d3060a2c54 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -182,19 +182,17 @@ export const createLifecycleExecutor = ( const allAlertIds = [...new Set(currentAlertIds.concat(trackedAlertIds))]; - const trackedAlertStatesOfRecovered = Object.values(state.trackedAlerts).filter( - (trackedAlertState) => !currentAlerts[trackedAlertState.alertId] - ); + const trackedAlertStates = Object.values(state.trackedAlerts); logger.debug( - `Tracking ${allAlertIds.length} alerts (${newAlertIds.length} new, ${trackedAlertStatesOfRecovered.length} recovered)` + `Tracking ${allAlertIds.length} alerts (${newAlertIds.length} new, ${trackedAlertStates.length} previous)` ); const alertsDataMap: Record> = { ...currentAlerts, }; - if (trackedAlertStatesOfRecovered.length) { + if (trackedAlertStates.length) { const { hits } = await ruleDataClient.getReader().search({ body: { query: { @@ -207,7 +205,7 @@ export const createLifecycleExecutor = ( }, { terms: { - [ALERT_UUID]: trackedAlertStatesOfRecovered.map( + [ALERT_UUID]: trackedAlertStates.map( (trackedAlertState) => trackedAlertState.alertUuid ), }, @@ -215,7 +213,7 @@ export const createLifecycleExecutor = ( ], }, }, - size: trackedAlertStatesOfRecovered.length, + size: trackedAlertStates.length, collapse: { field: ALERT_UUID, }, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index 2b138ae723305..b2cd69be7fdad 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -263,6 +263,36 @@ describe('createLifecycleRuleTypeFactory', () => { }, ]); + // TODO mock the resolved value before calling alertWithLifecycle again + const lastOpbeansNodeDoc = helpers.ruleDataClientMock + .getWriter() + .bulk.mock.calls[0][0].body?.concat() + .reverse() + .find( + (doc: any) => !('index' in doc) && doc['service.name'] === 'opbeans-node' + ) as Record; + + const stored = mapValues(lastOpbeansNodeDoc, (val) => { + return castArray(val); + }); + + helpers.ruleDataClientMock.getReader().search.mockResolvedValueOnce({ + hits: { + hits: [{ fields: stored } as any], + total: { + value: 1, + relation: 'eq', + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + successful: 1, + total: 1, + }, + }); + await helpers.alertWithLifecycle([ { id: 'opbeans-java', @@ -274,6 +304,7 @@ describe('createLifecycleRuleTypeFactory', () => { id: 'opbeans-node', fields: { 'service.name': 'opbeans-node', + 'kibana.alert.workflow_status': 'closed', }, }, ]); @@ -281,7 +312,6 @@ describe('createLifecycleRuleTypeFactory', () => { it('writes the correct alerts', () => { expect(helpers.ruleDataClientMock.getWriter().bulk).toHaveBeenCalledTimes(2); - const body = helpers.ruleDataClientMock.getWriter().bulk.mock.calls[1][0].body!; const documents = body.filter((op: any) => !('index' in op)) as any[];