kibana/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts
ymao1 ab082647ac
[Actions] Notify only on action group change (#82969)
* plugged Task Manager lifecycle into status reactively

* fixed tests

* Revert "fixed tests"

This reverts commit e9f2cd05bd.

* made action group fields optional

* revert deletion

* again

* extracted action type for mto its own component

* extracted more sections of the action form to their own components

* updated icon

* added docs

* fixed always firing alert

* fixed export of components

* fixed react warning

* Adding flag for notifying on state change

* Updating logic in task runner

* Starting to update tests

* Adding tests

* Fixing types check

* Tests and types

* Tests

* Tests

* Tests

* Tests

* Tests

* Renaming field to a more descriptive name. Adding migrations

* Renaming field to a more descriptive name. Adding migrations

* Fixing tests

* Type check and tests

* Moving schedule and notify interval to bottom of flyout. Implementing dropdown from mockup in new component

* Changing boolean flag to enum type and updating in triggers_actions_ui

* Changing boolean flag to enum type and updating in alerts plugin

* Fixing types check

* Fixing monitoring jest tests

* Changing last references to old variable names

* Moving form inputs back to the top

* Renaming to alert_notify_when

* Updating functional tests

* Adding new functional test for notifyWhen onActionGroupChange

* Updating wording

* Incorporating action subgroups into logic

* PR fixes

* Updating functional test

* Fixing types check

* Changing default throttle interval to hour

* Fixing types check

Co-authored-by: Gidi Meir Morris <github@gidi.io>
2020-12-10 15:51:52 -05:00

665 lines
19 KiB
TypeScript

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { SanitizedAlert, AlertInstanceSummary } from '../types';
import { IValidatedEvent } from '../../../event_log/server';
import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin';
import { alertInstanceSummaryFromEventLog } from './alert_instance_summary_from_event_log';
const ONE_HOUR_IN_MILLIS = 60 * 60 * 1000;
const dateStart = '2020-06-18T00:00:00.000Z';
const dateEnd = dateString(dateStart, ONE_HOUR_IN_MILLIS);
describe('alertInstanceSummaryFromEventLog', () => {
test('no events and muted ids', async () => {
const alert = createAlert({});
const events: IValidatedEvent[] = [];
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
expect(summary).toMatchInlineSnapshot(`
Object {
"alertTypeId": "123",
"consumer": "alert-consumer",
"enabled": false,
"errorMessages": Array [],
"id": "alert-123",
"instances": Object {},
"lastRun": undefined,
"muteAll": false,
"name": "alert-name",
"status": "OK",
"statusEndDate": "2020-06-18T01:00:00.000Z",
"statusStartDate": "2020-06-18T00:00:00.000Z",
"tags": Array [],
"throttle": null,
}
`);
});
test('different alert properties', async () => {
const alert = createAlert({
id: 'alert-456',
alertTypeId: '456',
schedule: { interval: '100s' },
enabled: true,
name: 'alert-name-2',
tags: ['tag-1', 'tag-2'],
consumer: 'alert-consumer-2',
throttle: '1h',
muteAll: true,
});
const events: IValidatedEvent[] = [];
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart: dateString(dateEnd, ONE_HOUR_IN_MILLIS),
dateEnd: dateString(dateEnd, ONE_HOUR_IN_MILLIS * 2),
});
expect(summary).toMatchInlineSnapshot(`
Object {
"alertTypeId": "456",
"consumer": "alert-consumer-2",
"enabled": true,
"errorMessages": Array [],
"id": "alert-456",
"instances": Object {},
"lastRun": undefined,
"muteAll": true,
"name": "alert-name-2",
"status": "OK",
"statusEndDate": "2020-06-18T03:00:00.000Z",
"statusStartDate": "2020-06-18T02:00:00.000Z",
"tags": Array [
"tag-1",
"tag-2",
],
"throttle": "1h",
}
`);
});
test('two muted instances', async () => {
const alert = createAlert({
mutedInstanceIds: ['instance-1', 'instance-2'],
});
const events: IValidatedEvent[] = [];
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
"instance-1": Object {
"actionGroupId": undefined,
"actionSubgroup": undefined,
"activeStartDate": undefined,
"muted": true,
"status": "OK",
},
"instance-2": Object {
"actionGroupId": undefined,
"actionSubgroup": undefined,
"activeStartDate": undefined,
"muted": true,
"status": "OK",
},
},
"lastRun": undefined,
"status": "OK",
}
`);
});
test('active alert but no instances', async () => {
const alert = createAlert({});
const eventsFactory = new EventsFactory();
const events = eventsFactory.addExecute().advanceTime(10000).addExecute().getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "OK",
}
`);
});
test('active alert with no instances but has errors', async () => {
const alert = createAlert({});
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute('oof!')
.advanceTime(10000)
.addExecute('rut roh!')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, errorMessages, instances } = summary;
expect({ lastRun, status, errorMessages, instances }).toMatchInlineSnapshot(`
Object {
"errorMessages": Array [
Object {
"date": "2020-06-18T00:00:00.000Z",
"message": "oof!",
},
Object {
"date": "2020-06-18T00:00:10.000Z",
"message": "rut roh!",
},
],
"instances": Object {},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "Error",
}
`);
});
test('alert with currently inactive instance', async () => {
const alert = createAlert({});
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
.addActiveInstance('instance-1', 'action group A')
.advanceTime(10000)
.addExecute()
.addRecoveredInstance('instance-1')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
"instance-1": Object {
"actionGroupId": undefined,
"actionSubgroup": undefined,
"activeStartDate": undefined,
"muted": false,
"status": "OK",
},
},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "OK",
}
`);
});
test('legacy alert with currently inactive instance', async () => {
const alert = createAlert({});
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
.addActiveInstance('instance-1', 'action group A')
.advanceTime(10000)
.addExecute()
.addLegacyResolvedInstance('instance-1')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
"instance-1": Object {
"actionGroupId": undefined,
"actionSubgroup": undefined,
"activeStartDate": undefined,
"muted": false,
"status": "OK",
},
},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "OK",
}
`);
});
test('alert with currently inactive instance, no new-instance', async () => {
const alert = createAlert({});
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
.addActiveInstance('instance-1', 'action group A')
.advanceTime(10000)
.addExecute()
.addRecoveredInstance('instance-1')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
"instance-1": Object {
"actionGroupId": undefined,
"actionSubgroup": undefined,
"activeStartDate": undefined,
"muted": false,
"status": "OK",
},
},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "OK",
}
`);
});
test('alert with currently active instance', async () => {
const alert = createAlert({});
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
.addActiveInstance('instance-1', 'action group A')
.advanceTime(10000)
.addExecute()
.addActiveInstance('instance-1', 'action group A')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
"instance-1": Object {
"actionGroupId": "action group A",
"actionSubgroup": undefined,
"activeStartDate": "2020-06-18T00:00:00.000Z",
"muted": false,
"status": "Active",
},
},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "Active",
}
`);
});
test('alert with currently active instance with no action group in event log', async () => {
const alert = createAlert({});
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
.addActiveInstance('instance-1', undefined)
.advanceTime(10000)
.addExecute()
.addActiveInstance('instance-1', undefined)
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
"instance-1": Object {
"actionGroupId": undefined,
"actionSubgroup": undefined,
"activeStartDate": "2020-06-18T00:00:00.000Z",
"muted": false,
"status": "Active",
},
},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "Active",
}
`);
});
test('alert with currently active instance that switched action groups', async () => {
const alert = createAlert({});
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
.addActiveInstance('instance-1', 'action group A')
.advanceTime(10000)
.addExecute()
.addActiveInstance('instance-1', 'action group B')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
"instance-1": Object {
"actionGroupId": "action group B",
"actionSubgroup": undefined,
"activeStartDate": "2020-06-18T00:00:00.000Z",
"muted": false,
"status": "Active",
},
},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "Active",
}
`);
});
test('alert with currently active instance, no new-instance', async () => {
const alert = createAlert({});
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
.addActiveInstance('instance-1', 'action group A')
.advanceTime(10000)
.addExecute()
.addActiveInstance('instance-1', 'action group A')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
"instance-1": Object {
"actionGroupId": "action group A",
"actionSubgroup": undefined,
"activeStartDate": undefined,
"muted": false,
"status": "Active",
},
},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "Active",
}
`);
});
test('alert with active and inactive muted alerts', async () => {
const alert = createAlert({ mutedInstanceIds: ['instance-1', 'instance-2'] });
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
.addActiveInstance('instance-1', 'action group A')
.addNewInstance('instance-2')
.addActiveInstance('instance-2', 'action group B')
.advanceTime(10000)
.addExecute()
.addActiveInstance('instance-1', 'action group A')
.addRecoveredInstance('instance-2')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
"instance-1": Object {
"actionGroupId": "action group A",
"actionSubgroup": undefined,
"activeStartDate": "2020-06-18T00:00:00.000Z",
"muted": true,
"status": "Active",
},
"instance-2": Object {
"actionGroupId": undefined,
"actionSubgroup": undefined,
"activeStartDate": undefined,
"muted": true,
"status": "OK",
},
},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "Active",
}
`);
});
test('alert with active and inactive alerts over many executes', async () => {
const alert = createAlert({});
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
.addActiveInstance('instance-1', 'action group A')
.addNewInstance('instance-2')
.addActiveInstance('instance-2', 'action group B')
.advanceTime(10000)
.addExecute()
.addActiveInstance('instance-1', 'action group A')
.addRecoveredInstance('instance-2')
.advanceTime(10000)
.addExecute()
.addActiveInstance('instance-1', 'action group B')
.advanceTime(10000)
.addExecute()
.addActiveInstance('instance-1', 'action group B')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
alert,
events,
dateStart,
dateEnd,
});
const { lastRun, status, instances } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
"instance-1": Object {
"actionGroupId": "action group B",
"actionSubgroup": undefined,
"activeStartDate": "2020-06-18T00:00:00.000Z",
"muted": false,
"status": "Active",
},
"instance-2": Object {
"actionGroupId": undefined,
"actionSubgroup": undefined,
"activeStartDate": undefined,
"muted": false,
"status": "OK",
},
},
"lastRun": "2020-06-18T00:00:30.000Z",
"status": "Active",
}
`);
});
});
function dateString(isoBaseDate: string, offsetMillis = 0): string {
return new Date(Date.parse(isoBaseDate) + offsetMillis).toISOString();
}
export class EventsFactory {
private events: IValidatedEvent[] = [];
constructor(private date: string = dateStart) {}
getEvents(): IValidatedEvent[] {
// ES normally returns events sorted newest to oldest, so we need to sort
// that way also
const events = this.events.slice();
events.sort((a, b) => -a!['@timestamp']!.localeCompare(b!['@timestamp']!));
return events;
}
getTime(): string {
return this.date;
}
advanceTime(millis: number): EventsFactory {
this.date = dateString(this.date, millis);
return this;
}
addExecute(errorMessage?: string): EventsFactory {
let event: IValidatedEvent = {
'@timestamp': this.date,
event: {
provider: EVENT_LOG_PROVIDER,
action: EVENT_LOG_ACTIONS.execute,
},
};
if (errorMessage) {
event = { ...event, error: { message: errorMessage } };
}
this.events.push(event);
return this;
}
addActiveInstance(instanceId: string, actionGroupId: string | undefined): EventsFactory {
const kibanaAlerting = actionGroupId
? { instance_id: instanceId, action_group_id: actionGroupId }
: { instance_id: instanceId };
this.events.push({
'@timestamp': this.date,
event: {
provider: EVENT_LOG_PROVIDER,
action: EVENT_LOG_ACTIONS.activeInstance,
},
kibana: { alerting: kibanaAlerting },
});
return this;
}
addNewInstance(instanceId: string): EventsFactory {
this.events.push({
'@timestamp': this.date,
event: {
provider: EVENT_LOG_PROVIDER,
action: EVENT_LOG_ACTIONS.newInstance,
},
kibana: { alerting: { instance_id: instanceId } },
});
return this;
}
addRecoveredInstance(instanceId: string): EventsFactory {
this.events.push({
'@timestamp': this.date,
event: {
provider: EVENT_LOG_PROVIDER,
action: EVENT_LOG_ACTIONS.recoveredInstance,
},
kibana: { alerting: { instance_id: instanceId } },
});
return this;
}
addLegacyResolvedInstance(instanceId: string): EventsFactory {
this.events.push({
'@timestamp': this.date,
event: {
provider: EVENT_LOG_PROVIDER,
action: LEGACY_EVENT_LOG_ACTIONS.resolvedInstance,
},
kibana: { alerting: { instance_id: instanceId } },
});
return this;
}
}
function createAlert(overrides: Partial<SanitizedAlert>): SanitizedAlert {
return { ...BaseAlert, ...overrides };
}
const BaseAlert: SanitizedAlert = {
id: 'alert-123',
alertTypeId: '123',
schedule: { interval: '10s' },
enabled: false,
name: 'alert-name',
tags: [],
consumer: 'alert-consumer',
throttle: null,
notifyWhen: null,
muteAll: false,
mutedInstanceIds: [],
params: { bar: true },
actions: [],
createdBy: null,
updatedBy: null,
createdAt: new Date(),
updatedAt: new Date(),
apiKeyOwner: null,
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
};