mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[ResponseOps][OnWeek] Investigate isolating flapping feature in the code (#212454)](https://github.com/elastic/kibana/pull/212454) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Alexi Doak","email":"109488926+doakalexi@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-03-10T20:01:10Z","message":"[ResponseOps][OnWeek] Investigate isolating flapping feature in the code (#212454)\n\nPart of https://github.com/elastic/kibana/issues/194222\n\n## Summary\n\nThis PR moves the flapping and alert delay features into two new\nfunctions in the alerts client `determineFlappingAlerts` and\n`determineDelayedAlerts`. They will be called in the rule_type_runner\nafter `processAlerts`.\n- I removed `recoveredCurrent` and `activeCurrent` 😌 . This PR\nsimplifies them to be activeAlerts, recoveredAlerts,\ntrackedRecoveredAlerts, and trackedActiveAlerts. trackedRecoveredAlerts\nand trackedActiveAlerts are the alerts that will be stored in the task\nstate.\n- I also updated the logic so that the AAD docs and state match to help\nwith ongoing work to use AAD docs instead of the state.\n- I removed an optimization for the task state to stop tracking a\nrecovered alert if it wasn't flapping and doesn't have state changes. We\nwon't need this optimization with using AAD docs instead of the state.\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\n- [ ] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"eed986162baf239db04fe117e1f045c28ad43c91","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:ResponseOps","backport:version","v9.1.0","v8.19.0"],"title":"[ResponseOps][OnWeek] Investigate isolating flapping feature in the code","number":212454,"url":"https://github.com/elastic/kibana/pull/212454","mergeCommit":{"message":"[ResponseOps][OnWeek] Investigate isolating flapping feature in the code (#212454)\n\nPart of https://github.com/elastic/kibana/issues/194222\n\n## Summary\n\nThis PR moves the flapping and alert delay features into two new\nfunctions in the alerts client `determineFlappingAlerts` and\n`determineDelayedAlerts`. They will be called in the rule_type_runner\nafter `processAlerts`.\n- I removed `recoveredCurrent` and `activeCurrent` 😌 . This PR\nsimplifies them to be activeAlerts, recoveredAlerts,\ntrackedRecoveredAlerts, and trackedActiveAlerts. trackedRecoveredAlerts\nand trackedActiveAlerts are the alerts that will be stored in the task\nstate.\n- I also updated the logic so that the AAD docs and state match to help\nwith ongoing work to use AAD docs instead of the state.\n- I removed an optimization for the task state to stop tracking a\nrecovered alert if it wasn't flapping and doesn't have state changes. We\nwon't need this optimization with using AAD docs instead of the state.\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\n- [ ] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"eed986162baf239db04fe117e1f045c28ad43c91"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/212454","number":212454,"mergeCommit":{"message":"[ResponseOps][OnWeek] Investigate isolating flapping feature in the code (#212454)\n\nPart of https://github.com/elastic/kibana/issues/194222\n\n## Summary\n\nThis PR moves the flapping and alert delay features into two new\nfunctions in the alerts client `determineFlappingAlerts` and\n`determineDelayedAlerts`. They will be called in the rule_type_runner\nafter `processAlerts`.\n- I removed `recoveredCurrent` and `activeCurrent` 😌 . This PR\nsimplifies them to be activeAlerts, recoveredAlerts,\ntrackedRecoveredAlerts, and trackedActiveAlerts. trackedRecoveredAlerts\nand trackedActiveAlerts are the alerts that will be stored in the task\nstate.\n- I also updated the logic so that the AAD docs and state match to help\nwith ongoing work to use AAD docs instead of the state.\n- I removed an optimization for the task state to stop tracking a\nrecovered alert if it wasn't flapping and doesn't have state changes. We\nwon't need this optimization with using AAD docs instead of the state.\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\n- [ ] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"eed986162baf239db04fe117e1f045c28ad43c91"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
This commit is contained in:
parent
a9dda8c819
commit
1ee1b84d55
45 changed files with 2039 additions and 2245 deletions
|
@ -189,7 +189,7 @@ describe('createAlertFactory()', () => {
|
|||
|
||||
test('returns recovered alerts when setsRecoveryContext is true', () => {
|
||||
(processAlerts as jest.Mock).mockReturnValueOnce({
|
||||
currentRecoveredAlerts: {
|
||||
recoveredAlerts: {
|
||||
z: {
|
||||
id: 'z',
|
||||
state: { foo: true },
|
||||
|
|
|
@ -10,7 +10,6 @@ import { cloneDeep } from 'lodash';
|
|||
import { AlertInstanceContext, AlertInstanceState } from '../types';
|
||||
import { Alert, PublicAlert } from './alert';
|
||||
import { processAlerts } from '../lib';
|
||||
import { DISABLE_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
|
||||
export interface AlertFactory<
|
||||
State extends AlertInstanceState,
|
||||
|
@ -142,23 +141,17 @@ export function createAlertFactory<
|
|||
return [];
|
||||
}
|
||||
|
||||
const { currentRecoveredAlerts } = processAlerts<
|
||||
State,
|
||||
Context,
|
||||
ActionGroupIds,
|
||||
ActionGroupIds
|
||||
>({
|
||||
alerts,
|
||||
existingAlerts: originalAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit,
|
||||
alertLimit: maxAlerts,
|
||||
autoRecoverAlerts,
|
||||
// flappingSettings.enabled is false, as we only want to use this function to get the recovered alerts
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
return Object.keys(currentRecoveredAlerts ?? {}).map(
|
||||
(alertId: string) => currentRecoveredAlerts[alertId]
|
||||
const { recoveredAlerts } = processAlerts<State, Context, ActionGroupIds, ActionGroupIds>(
|
||||
{
|
||||
alerts,
|
||||
existingAlerts: originalAlerts,
|
||||
hasReachedAlertLimit,
|
||||
alertLimit: maxAlerts,
|
||||
autoRecoverAlerts,
|
||||
}
|
||||
);
|
||||
return Object.keys(recoveredAlerts ?? {}).map(
|
||||
(alertId: string) => recoveredAlerts[alertId]
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,13 +15,15 @@ const createAlertsClientMock = () => {
|
|||
getMaintenanceWindowScopedQueryAlerts: jest.fn(),
|
||||
getTrackedAlerts: jest.fn(),
|
||||
getProcessedAlerts: jest.fn(),
|
||||
getAlertsToSerialize: jest.fn(),
|
||||
getRawAlertInstancesForState: jest.fn(),
|
||||
hasReachedAlertLimit: jest.fn(),
|
||||
checkLimitUsage: jest.fn(),
|
||||
persistAlerts: jest.fn(),
|
||||
getSummarizedAlerts: jest.fn(),
|
||||
factory: jest.fn(),
|
||||
client: jest.fn(),
|
||||
determineDelayedAlerts: jest.fn(),
|
||||
determineFlappingAlerts: jest.fn(),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -57,7 +57,7 @@ import { AlertsClient, AlertsClientParams } from './alerts_client';
|
|||
import {
|
||||
GetSummarizedAlertsParams,
|
||||
GetMaintenanceWindowScopedQueryAlertsParams,
|
||||
ProcessAlertsOpts,
|
||||
DetermineDelayedAlertsOpts,
|
||||
LogAlertsOpts,
|
||||
} from './types';
|
||||
import { legacyAlertsClientMock } from './legacy_alerts_client.mock';
|
||||
|
@ -323,7 +323,7 @@ const logTags = { tags: ['test.rule-type', '1', 'alerts-client'] };
|
|||
|
||||
describe('Alerts Client', () => {
|
||||
let alertsClientParams: AlertsClientParams;
|
||||
let processAlertsOpts: ProcessAlertsOpts;
|
||||
let determineDelayedAlertsOpts: DetermineDelayedAlertsOpts;
|
||||
let logAlertsOpts: LogAlertsOpts;
|
||||
|
||||
beforeAll(() => {
|
||||
|
@ -375,9 +375,8 @@ describe('Alerts Client', () => {
|
|||
],
|
||||
maintenanceWindowsWithoutScopedQueryIds: ['test-id1', 'test-id2'],
|
||||
});
|
||||
processAlertsOpts = {
|
||||
determineDelayedAlertsOpts = {
|
||||
ruleRunMetricsStore,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
alertDelay: 0,
|
||||
};
|
||||
logAlertsOpts = { shouldLogAlerts: false, ruleRunMetricsStore };
|
||||
|
@ -563,14 +562,16 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
const { alertsToReturn } = alertsClient.getAlertsToSerialize();
|
||||
const uuid1 = alertsToReturn['1'].meta?.uuid;
|
||||
const uuid2 = alertsToReturn['2'].meta?.uuid;
|
||||
const { rawActiveAlerts } = alertsClient.getRawAlertInstancesForState();
|
||||
const uuid1 = rawActiveAlerts['1'].meta?.uuid;
|
||||
const uuid2 = rawActiveAlerts['2'].meta?.uuid;
|
||||
|
||||
expect(clusterClient.bulk).toHaveBeenCalledWith({
|
||||
index: '.alerts-test.alerts-default',
|
||||
|
@ -610,14 +611,16 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
const { alertsToReturn } = alertsClient.getAlertsToSerialize();
|
||||
const uuid1 = alertsToReturn['1'].meta?.uuid;
|
||||
const uuid2 = alertsToReturn['2'].meta?.uuid;
|
||||
const { rawActiveAlerts } = alertsClient.getRawAlertInstancesForState();
|
||||
const uuid1 = rawActiveAlerts['1'].meta?.uuid;
|
||||
const uuid2 = rawActiveAlerts['2'].meta?.uuid;
|
||||
|
||||
expect(clusterClient.bulk).toHaveBeenCalledWith({
|
||||
index: '.alerts-test.alerts-default',
|
||||
|
@ -656,7 +659,9 @@ describe('Alerts Client', () => {
|
|||
const alertExecutorService = alertsClient.factory();
|
||||
alertExecutorService.create('1').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
@ -704,13 +709,15 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
const { alertsToReturn } = alertsClient.getAlertsToSerialize();
|
||||
const uuid2 = alertsToReturn['2'].meta?.uuid;
|
||||
const { rawActiveAlerts } = alertsClient.getRawAlertInstancesForState();
|
||||
const uuid2 = rawActiveAlerts['2'].meta?.uuid;
|
||||
|
||||
expect(clusterClient.bulk).toHaveBeenCalledWith({
|
||||
index: '.alerts-test.alerts-default',
|
||||
|
@ -777,13 +784,15 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
const { alertsToReturn } = alertsClient.getAlertsToSerialize();
|
||||
const uuid2 = alertsToReturn['2'].meta?.uuid;
|
||||
const { rawActiveAlerts } = alertsClient.getRawAlertInstancesForState();
|
||||
const uuid2 = rawActiveAlerts['2'].meta?.uuid;
|
||||
|
||||
expect(clusterClient.bulk).toHaveBeenCalledWith({
|
||||
index: '.alerts-test.alerts-default',
|
||||
|
@ -905,14 +914,16 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default'); // will be skipped as getProcessedAlerts does not return it
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(5);
|
||||
expect(spy).toHaveBeenNthCalledWith(1, 'active');
|
||||
expect(spy).toHaveBeenNthCalledWith(2, 'recoveredCurrent');
|
||||
expect(spy).toHaveBeenNthCalledWith(2, 'recovered');
|
||||
expect(spy).toHaveBeenNthCalledWith(3, 'new');
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
|
@ -986,13 +997,15 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('2').scheduleActions('default');
|
||||
alertExecutorService.create('3').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
const { alertsToReturn } = alertsClient.getAlertsToSerialize();
|
||||
const uuid3 = alertsToReturn['3'].meta?.uuid;
|
||||
const { rawActiveAlerts } = alertsClient.getRawAlertInstancesForState();
|
||||
const uuid3 = rawActiveAlerts['3'].meta?.uuid;
|
||||
|
||||
expect(clusterClient.bulk).toHaveBeenCalledWith({
|
||||
index: '.alerts-test.alerts-default',
|
||||
|
@ -1085,13 +1098,15 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('2').scheduleActions('default');
|
||||
alertExecutorService.create('3').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
const { alertsToReturn } = alertsClient.getAlertsToSerialize();
|
||||
const uuid3 = alertsToReturn['3'].meta?.uuid;
|
||||
const { rawActiveAlerts } = alertsClient.getRawAlertInstancesForState();
|
||||
const uuid3 = rawActiveAlerts['3'].meta?.uuid;
|
||||
|
||||
expect(clusterClient.bulk).toHaveBeenCalledWith({
|
||||
index: '.alerts-test.alerts-default',
|
||||
|
@ -1244,13 +1259,15 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('2').scheduleActions('default');
|
||||
alertExecutorService.create('3').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
const { alertsToReturn } = alertsClient.getAlertsToSerialize();
|
||||
const uuid3 = alertsToReturn['3'].meta?.uuid;
|
||||
const { rawActiveAlerts } = alertsClient.getRawAlertInstancesForState();
|
||||
const uuid3 = rawActiveAlerts['3'].meta?.uuid;
|
||||
|
||||
expect(clusterClient.bulk).toHaveBeenCalledWith({
|
||||
index: '.alerts-test.alerts-default',
|
||||
|
@ -1362,13 +1379,15 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('2').scheduleActions('default');
|
||||
alertExecutorService.create('3').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
const { alertsToReturn } = alertsClient.getAlertsToSerialize();
|
||||
const uuid3 = alertsToReturn['3'].meta?.uuid;
|
||||
const { rawActiveAlerts } = alertsClient.getRawAlertInstancesForState();
|
||||
const uuid3 = rawActiveAlerts['3'].meta?.uuid;
|
||||
|
||||
expect(clusterClient.bulk).toHaveBeenCalledWith({
|
||||
index: '.alerts-test.alerts-default',
|
||||
|
@ -1444,7 +1463,9 @@ describe('Alerts Client', () => {
|
|||
|
||||
// Report no alerts
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
@ -1509,7 +1530,9 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
@ -1569,7 +1592,9 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
@ -1628,7 +1653,9 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
@ -1714,7 +1741,9 @@ describe('Alerts Client', () => {
|
|||
const alertExecutorService = alertsClient.factory();
|
||||
alertExecutorService.create('1').scheduleActions('default');
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await expect(alertsClient.persistAlerts()).rejects.toThrowError(
|
||||
|
@ -2536,14 +2565,16 @@ describe('Alerts Client', () => {
|
|||
payload: { count: 2, url: `https://url2` },
|
||||
});
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
const { alertsToReturn } = alertsClient.getAlertsToSerialize();
|
||||
const uuid1 = alertsToReturn['1'].meta?.uuid;
|
||||
const uuid2 = alertsToReturn['2'].meta?.uuid;
|
||||
const { rawActiveAlerts } = alertsClient.getRawAlertInstancesForState();
|
||||
const uuid1 = rawActiveAlerts['1'].meta?.uuid;
|
||||
const uuid2 = rawActiveAlerts['2'].meta?.uuid;
|
||||
|
||||
expect(clusterClient.bulk).toHaveBeenCalledWith({
|
||||
index: '.alerts-test.alerts-default',
|
||||
|
@ -2814,7 +2845,9 @@ describe('Alerts Client', () => {
|
|||
payload: { count: 100, url: `https://elastic.co` },
|
||||
});
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
@ -2915,7 +2948,9 @@ describe('Alerts Client', () => {
|
|||
payload: { count: 100, url: `https://elastic.co` },
|
||||
});
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
@ -3012,7 +3047,9 @@ describe('Alerts Client', () => {
|
|||
payload: { count: 100, url: `https://elastic.co` },
|
||||
});
|
||||
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts(determineDelayedAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
IIndexPatternString,
|
||||
} from '../alerts_service/resource_installer_utils';
|
||||
import { CreateAlertsClientParams } from '../alerts_service/alerts_service';
|
||||
import type { AlertRule, LogAlertsOpts, ProcessAlertsOpts, SearchResult } from './types';
|
||||
import type { AlertRule, LogAlertsOpts, SearchResult, DetermineDelayedAlertsOpts } from './types';
|
||||
import {
|
||||
IAlertsClient,
|
||||
InitializeExecutionOpts,
|
||||
|
@ -318,8 +318,16 @@ export class AlertsClient<
|
|||
return this.legacyAlertsClient.checkLimitUsage();
|
||||
}
|
||||
|
||||
public async processAlerts(opts: ProcessAlertsOpts) {
|
||||
await this.legacyAlertsClient.processAlerts(opts);
|
||||
public async processAlerts() {
|
||||
await this.legacyAlertsClient.processAlerts();
|
||||
}
|
||||
|
||||
public determineFlappingAlerts() {
|
||||
this.legacyAlertsClient.determineFlappingAlerts();
|
||||
}
|
||||
|
||||
public determineDelayedAlerts(opts: DetermineDelayedAlertsOpts) {
|
||||
this.legacyAlertsClient.determineDelayedAlerts(opts);
|
||||
}
|
||||
|
||||
public logAlerts(opts: LogAlertsOpts) {
|
||||
|
@ -327,7 +335,7 @@ export class AlertsClient<
|
|||
}
|
||||
|
||||
public getProcessedAlerts(
|
||||
type: 'new' | 'active' | 'activeCurrent' | 'recovered' | 'recoveredCurrent'
|
||||
type: 'new' | 'active' | 'trackedActiveAlerts' | 'recovered' | 'trackedRecoveredAlerts'
|
||||
) {
|
||||
return this.legacyAlertsClient.getProcessedAlerts(type);
|
||||
}
|
||||
|
@ -339,15 +347,8 @@ export class AlertsClient<
|
|||
return await this.updatePersistedAlertsWithMaintenanceWindowIds();
|
||||
}
|
||||
|
||||
public getAlertsToSerialize() {
|
||||
// The flapping value that is persisted inside the task manager state (and used in the next execution)
|
||||
// is different than the value that should be written to the alert document. For this reason, we call
|
||||
// getAlertsToSerialize() twice, once before building and bulk indexing alert docs and once after to return
|
||||
// the value for task state serialization
|
||||
|
||||
// This will be a blocker if ever we want to stop serializing alert data inside the task state and just use
|
||||
// the fetched alert document.
|
||||
return this.legacyAlertsClient.getAlertsToSerialize();
|
||||
public getRawAlertInstancesForState() {
|
||||
return this.legacyAlertsClient.getRawAlertInstancesForState();
|
||||
}
|
||||
|
||||
public factory() {
|
||||
|
@ -423,18 +424,17 @@ export class AlertsClient<
|
|||
const currentTime = this.startedAtString ?? new Date().toISOString();
|
||||
const esClient = await this.options.elasticsearchClientPromise;
|
||||
|
||||
const { alertsToReturn, recoveredAlertsToReturn } =
|
||||
this.legacyAlertsClient.getAlertsToSerialize(false);
|
||||
const { rawActiveAlerts, rawRecoveredAlerts } = this.getRawAlertInstancesForState();
|
||||
|
||||
const activeAlerts = this.legacyAlertsClient.getProcessedAlerts('active');
|
||||
const currentRecoveredAlerts = this.legacyAlertsClient.getProcessedAlerts('recoveredCurrent');
|
||||
const recoveredAlerts = this.legacyAlertsClient.getProcessedAlerts('recovered');
|
||||
|
||||
// TODO - Lifecycle alerts set some other fields based on alert status
|
||||
// Example: workflow status - default to 'open' if not set
|
||||
// event action: new alert = 'new', active alert: 'active', otherwise 'close'
|
||||
|
||||
const activeAlertsToIndex: Array<Alert & AlertData> = [];
|
||||
for (const id of keys(alertsToReturn)) {
|
||||
for (const id of keys(rawActiveAlerts)) {
|
||||
// See if there's an existing active alert document
|
||||
if (!!activeAlerts[id]) {
|
||||
if (
|
||||
|
@ -498,12 +498,12 @@ export class AlertsClient<
|
|||
}
|
||||
|
||||
const recoveredAlertsToIndex: Array<Alert & AlertData> = [];
|
||||
for (const id of keys(recoveredAlertsToReturn)) {
|
||||
for (const id of keys(rawRecoveredAlerts)) {
|
||||
// See if there's an existing alert document
|
||||
// If there is not, log an error because there should be
|
||||
if (Object.hasOwn(this.fetchedAlerts.data, id)) {
|
||||
recoveredAlertsToIndex.push(
|
||||
currentRecoveredAlerts[id]
|
||||
recoveredAlerts[id]
|
||||
? buildRecoveredAlert<
|
||||
AlertData,
|
||||
LegacyState,
|
||||
|
@ -512,7 +512,7 @@ export class AlertsClient<
|
|||
RecoveryActionGroupId
|
||||
>({
|
||||
alert: this.fetchedAlerts.data[id],
|
||||
legacyAlert: currentRecoveredAlerts[id],
|
||||
legacyAlert: recoveredAlerts[id],
|
||||
rule: this.rule,
|
||||
runTimestamp: this.runTimestampString,
|
||||
timestamp: currentTime,
|
||||
|
@ -522,7 +522,7 @@ export class AlertsClient<
|
|||
})
|
||||
: buildUpdatedRecoveredAlert<AlertData>({
|
||||
alert: this.fetchedAlerts.data[id],
|
||||
legacyRawAlert: recoveredAlertsToReturn[id],
|
||||
legacyRawAlert: rawRecoveredAlerts[id],
|
||||
runTimestamp: this.runTimestampString,
|
||||
timestamp: currentTime,
|
||||
rule: this.rule,
|
||||
|
|
|
@ -13,13 +13,15 @@ const createLegacyAlertsClientMock = () => {
|
|||
getProcessedAlerts: jest.fn(),
|
||||
processAlerts: jest.fn(),
|
||||
logAlerts: jest.fn(),
|
||||
getAlertsToSerialize: jest.fn(),
|
||||
getRawAlertInstancesForState: jest.fn(),
|
||||
hasReachedAlertLimit: jest.fn(),
|
||||
checkLimitUsage: jest.fn(),
|
||||
persistAlerts: jest.fn(),
|
||||
getAlert: jest.fn(),
|
||||
factory: jest.fn(),
|
||||
client: jest.fn(),
|
||||
determineDelayedAlerts: jest.fn(),
|
||||
determineFlappingAlerts: jest.fn(),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -11,14 +11,15 @@ import { LegacyAlertsClient } from './legacy_alerts_client';
|
|||
import { createAlertFactory, getPublicAlertFactory } from '../alert/create_alert_factory';
|
||||
import { Alert } from '../alert/alert';
|
||||
import { ruleRunMetricsStoreMock } from '../lib/rule_run_metrics_store.mock';
|
||||
import { getAlertsForNotification, processAlerts } from '../lib';
|
||||
import { trimRecoveredAlerts } from '../lib/trim_recovered_alerts';
|
||||
import { processAlerts } from '../lib';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { maintenanceWindowsServiceMock } from '../task_runner/maintenance_windows/maintenance_windows_service.mock';
|
||||
import { getMockMaintenanceWindow } from '../data/maintenance_window/test_helpers';
|
||||
import { KibanaRequest } from '@kbn/core/server';
|
||||
import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_event_logger.mock';
|
||||
import { determineFlappingAlerts } from '../lib/flapping/determine_flapping_alerts';
|
||||
import { determineDelayedAlerts } from '../lib/determine_delayed_alerts';
|
||||
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
const scheduleActions = jest.fn();
|
||||
|
@ -69,15 +70,15 @@ jest.mock('../lib', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('../lib/trim_recovered_alerts', () => {
|
||||
jest.mock('../lib/flapping/determine_flapping_alerts', () => {
|
||||
return {
|
||||
trimRecoveredAlerts: jest.fn(),
|
||||
determineFlappingAlerts: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../lib/get_alerts_for_notification', () => {
|
||||
jest.mock('../lib/determine_delayed_alerts', () => {
|
||||
return {
|
||||
getAlertsForNotification: jest.fn(),
|
||||
determineDelayedAlerts: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -258,7 +259,7 @@ describe('Legacy Alerts Client', () => {
|
|||
expect(alertsClient.getMaxAlertLimit()).toBe(1000);
|
||||
});
|
||||
|
||||
test('processAlerts() should call processAlerts, trimRecoveredAlerts and getAlertsForNotifications', async () => {
|
||||
test('processAlerts() should call processAlerts', async () => {
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockReturnValue({
|
||||
maintenanceWindows: [
|
||||
{
|
||||
|
@ -284,24 +285,6 @@ describe('Legacy Alerts Client', () => {
|
|||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
currentRecoveredAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
(trimRecoveredAlerts as jest.Mock).mockReturnValue({
|
||||
trimmedAlertsRecovered: {},
|
||||
earlyRecoveredAlerts: {},
|
||||
});
|
||||
(getAlertsForNotification as jest.Mock).mockReturnValue({
|
||||
newAlerts: {},
|
||||
activeAlerts: {
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
currentActiveAlerts: {
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
currentRecoveredAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
|
@ -315,11 +298,7 @@ describe('Legacy Alerts Client', () => {
|
|||
|
||||
await alertsClient.initializeExecution(defaultExecutionOpts);
|
||||
|
||||
await alertsClient.processAlerts({
|
||||
ruleRunMetricsStore,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
alertDelay: 5,
|
||||
});
|
||||
await alertsClient.processAlerts();
|
||||
|
||||
expect(processAlerts).toHaveBeenCalledWith({
|
||||
alerts: {
|
||||
|
@ -330,34 +309,12 @@ describe('Legacy Alerts Client', () => {
|
|||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 1000,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
startedAt: null,
|
||||
});
|
||||
|
||||
expect(trimRecoveredAlerts).toHaveBeenCalledWith(logger, {}, 1000);
|
||||
|
||||
expect(getAlertsForNotification).toHaveBeenCalledWith(
|
||||
{
|
||||
enabled: true,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 4,
|
||||
},
|
||||
'default',
|
||||
5,
|
||||
{},
|
||||
{
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
{},
|
||||
{},
|
||||
null
|
||||
);
|
||||
|
||||
expect(alertsClient.getProcessedAlerts('active')).toEqual({
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
|
@ -398,24 +355,6 @@ describe('Legacy Alerts Client', () => {
|
|||
activeAlerts: {
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
currentRecoveredAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
(trimRecoveredAlerts as jest.Mock).mockReturnValue({
|
||||
trimmedAlertsRecovered: {},
|
||||
earlyRecoveredAlerts: {},
|
||||
});
|
||||
(getAlertsForNotification as jest.Mock).mockReturnValue({
|
||||
newAlerts: {
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
},
|
||||
activeAlerts: {
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
currentActiveAlerts: {
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
currentRecoveredAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
|
@ -434,11 +373,7 @@ describe('Legacy Alerts Client', () => {
|
|||
},
|
||||
});
|
||||
|
||||
await alertsClient.processAlerts({
|
||||
ruleRunMetricsStore,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
alertDelay: 5,
|
||||
});
|
||||
await alertsClient.processAlerts();
|
||||
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
|
@ -446,28 +381,6 @@ describe('Legacy Alerts Client', () => {
|
|||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
|
||||
expect(getAlertsForNotification).toHaveBeenCalledWith(
|
||||
{
|
||||
enabled: true,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 4,
|
||||
},
|
||||
'default',
|
||||
5,
|
||||
{
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', {
|
||||
...testAlert1,
|
||||
meta: { ...testAlert1.meta, maintenanceWindowIds: ['test-id1', 'test-id2'] },
|
||||
}),
|
||||
},
|
||||
{
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
{},
|
||||
{},
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
test('isTrackedAlert() should return true if alert was active in a previous execution, false otherwise', async () => {
|
||||
|
@ -485,4 +398,96 @@ describe('Legacy Alerts Client', () => {
|
|||
expect(alertsClient.isTrackedAlert('2')).toBe(true);
|
||||
expect(alertsClient.isTrackedAlert('3')).toBe(false);
|
||||
});
|
||||
|
||||
test('determineFlappingAlerts() should call determineFlappingAlerts', async () => {
|
||||
(determineFlappingAlerts as jest.Mock).mockReturnValue({
|
||||
newAlerts: {},
|
||||
activeAlerts: {
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
trackedActiveAlerts: {
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
trackedRecoveredAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
spaceId: 'space1',
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
await alertsClient.initializeExecution(defaultExecutionOpts);
|
||||
|
||||
alertsClient.determineFlappingAlerts();
|
||||
|
||||
expect(determineFlappingAlerts).toHaveBeenCalledWith({
|
||||
logger,
|
||||
newAlerts: {},
|
||||
activeAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
flappingSettings: {
|
||||
enabled: true,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 4,
|
||||
},
|
||||
previouslyRecoveredAlerts: {},
|
||||
actionGroupId: 'default',
|
||||
maxAlerts: 1000,
|
||||
});
|
||||
|
||||
expect(alertsClient.getProcessedAlerts('active')).toEqual({
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
});
|
||||
});
|
||||
|
||||
test('determineDelayedAlerts() should call determineDelayedAlerts', async () => {
|
||||
(determineDelayedAlerts as jest.Mock).mockReturnValue({
|
||||
newAlerts: {},
|
||||
activeAlerts: {
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
},
|
||||
trackedActiveAlerts: {
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
},
|
||||
trackedRecoveredAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
spaceId: 'space1',
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
await alertsClient.initializeExecution(defaultExecutionOpts);
|
||||
|
||||
alertsClient.determineDelayedAlerts({
|
||||
ruleRunMetricsStore,
|
||||
alertDelay: 5,
|
||||
});
|
||||
|
||||
expect(determineDelayedAlerts).toHaveBeenCalledWith({
|
||||
newAlerts: {},
|
||||
activeAlerts: {},
|
||||
trackedActiveAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
trackedRecoveredAlerts: {},
|
||||
alertDelay: 5,
|
||||
ruleRunMetricsStore,
|
||||
startedAt: null,
|
||||
});
|
||||
|
||||
expect(alertsClient.getProcessedAlerts('active')).toEqual({
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,20 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { KibanaRequest, Logger } from '@kbn/core/server';
|
||||
import { cloneDeep, keys, merge } from 'lodash';
|
||||
import { cloneDeep, keys } from 'lodash';
|
||||
import { Alert } from '../alert/alert';
|
||||
import {
|
||||
AlertFactory,
|
||||
createAlertFactory,
|
||||
getPublicAlertFactory,
|
||||
} from '../alert/create_alert_factory';
|
||||
import {
|
||||
determineAlertsToReturn,
|
||||
processAlerts,
|
||||
setFlapping,
|
||||
getAlertsForNotification,
|
||||
} from '../lib';
|
||||
import { trimRecoveredAlerts } from '../lib/trim_recovered_alerts';
|
||||
import { toRawAlertInstances, processAlerts } from '../lib';
|
||||
import { logAlerts } from '../task_runner/log_alerts';
|
||||
import { AlertInstanceContext, AlertInstanceState, WithoutReservedActionGroups } from '../types';
|
||||
import {
|
||||
|
@ -28,14 +22,16 @@ import {
|
|||
import {
|
||||
IAlertsClient,
|
||||
InitializeExecutionOpts,
|
||||
ProcessAlertsOpts,
|
||||
LogAlertsOpts,
|
||||
TrackedAlerts,
|
||||
DetermineDelayedAlertsOpts,
|
||||
} from './types';
|
||||
import { DEFAULT_MAX_ALERTS } from '../config';
|
||||
import { UntypedNormalizedRuleType } from '../rule_type_registry';
|
||||
import { MaintenanceWindowsService } from '../task_runner/maintenance_windows';
|
||||
import { AlertingEventLogger } from '../lib/alerting_event_logger/alerting_event_logger';
|
||||
import { determineFlappingAlerts } from '../lib/flapping/determine_flapping_alerts';
|
||||
import { determineDelayedAlerts } from '../lib/determine_delayed_alerts';
|
||||
|
||||
export interface LegacyAlertsClientParams {
|
||||
alertingEventLogger: AlertingEventLogger;
|
||||
|
@ -70,9 +66,9 @@ export class LegacyAlertsClient<
|
|||
private processedAlerts: {
|
||||
new: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
active: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
activeCurrent: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
trackedActiveAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
recovered: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
recoveredCurrent: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
trackedRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
};
|
||||
|
||||
private alertFactory?: AlertFactory<
|
||||
|
@ -85,9 +81,9 @@ export class LegacyAlertsClient<
|
|||
this.processedAlerts = {
|
||||
new: {},
|
||||
active: {},
|
||||
activeCurrent: {},
|
||||
trackedActiveAlerts: {},
|
||||
recovered: {},
|
||||
recoveredCurrent: {},
|
||||
trackedRecoveredAlerts: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -145,24 +141,17 @@ export class LegacyAlertsClient<
|
|||
return !!this.trackedAlerts.active[id];
|
||||
}
|
||||
|
||||
public async processAlerts({
|
||||
flappingSettings,
|
||||
alertDelay,
|
||||
ruleRunMetricsStore,
|
||||
}: ProcessAlertsOpts) {
|
||||
public async processAlerts() {
|
||||
const {
|
||||
newAlerts: processedAlertsNew,
|
||||
activeAlerts: processedAlertsActive,
|
||||
currentRecoveredAlerts: processedAlertsRecoveredCurrent,
|
||||
recoveredAlerts: processedAlertsRecovered,
|
||||
} = processAlerts<State, Context, ActionGroupIds, RecoveryActionGroupId>({
|
||||
alerts: this.reportedAlerts,
|
||||
existingAlerts: this.trackedAlerts.active,
|
||||
previouslyRecoveredAlerts: this.trackedAlerts.recovered,
|
||||
hasReachedAlertLimit: this.alertFactory!.hasReachedAlertLimit(),
|
||||
alertLimit: this.maxAlerts,
|
||||
autoRecoverAlerts: this.options.ruleType.autoRecoverAlerts ?? true,
|
||||
flappingSettings,
|
||||
startedAt: this.startedAtString,
|
||||
});
|
||||
|
||||
|
@ -191,30 +180,11 @@ export class LegacyAlertsClient<
|
|||
}
|
||||
}
|
||||
|
||||
const { trimmedAlertsRecovered, earlyRecoveredAlerts } = trimRecoveredAlerts(
|
||||
this.options.logger,
|
||||
processedAlertsRecovered,
|
||||
this.maxAlerts
|
||||
);
|
||||
|
||||
const alerts = getAlertsForNotification<State, Context, ActionGroupIds, RecoveryActionGroupId>(
|
||||
flappingSettings,
|
||||
this.options.ruleType.defaultActionGroupId,
|
||||
alertDelay,
|
||||
processedAlertsNew,
|
||||
processedAlertsActive,
|
||||
trimmedAlertsRecovered,
|
||||
processedAlertsRecoveredCurrent,
|
||||
this.startedAtString
|
||||
);
|
||||
ruleRunMetricsStore.setNumberOfDelayedAlerts(alerts.delayedAlertsCount);
|
||||
alerts.currentRecoveredAlerts = merge(alerts.currentRecoveredAlerts, earlyRecoveredAlerts);
|
||||
|
||||
this.processedAlerts.new = alerts.newAlerts;
|
||||
this.processedAlerts.active = alerts.activeAlerts;
|
||||
this.processedAlerts.activeCurrent = alerts.currentActiveAlerts;
|
||||
this.processedAlerts.recovered = alerts.recoveredAlerts;
|
||||
this.processedAlerts.recoveredCurrent = alerts.currentRecoveredAlerts;
|
||||
this.processedAlerts.new = processedAlertsNew;
|
||||
this.processedAlerts.active = processedAlertsActive;
|
||||
this.processedAlerts.trackedActiveAlerts = processedAlertsActive;
|
||||
this.processedAlerts.recovered = processedAlertsRecovered;
|
||||
this.processedAlerts.trackedRecoveredAlerts = processedAlertsRecovered;
|
||||
}
|
||||
|
||||
public logAlerts({ ruleRunMetricsStore, shouldLogAlerts }: LogAlertsOpts) {
|
||||
|
@ -222,8 +192,8 @@ export class LegacyAlertsClient<
|
|||
logger: this.options.logger,
|
||||
alertingEventLogger: this.options.alertingEventLogger,
|
||||
newAlerts: this.processedAlerts.new,
|
||||
activeAlerts: this.processedAlerts.activeCurrent,
|
||||
recoveredAlerts: this.processedAlerts.recoveredCurrent,
|
||||
activeAlerts: this.processedAlerts.active,
|
||||
recoveredAlerts: this.processedAlerts.recovered,
|
||||
ruleLogPrefix: this.ruleLogPrefix,
|
||||
ruleRunMetricsStore,
|
||||
canSetRecoveryContext: this.options.ruleType.doesSetRecoveryContext ?? false,
|
||||
|
@ -232,7 +202,7 @@ export class LegacyAlertsClient<
|
|||
}
|
||||
|
||||
public getProcessedAlerts(
|
||||
type: 'new' | 'active' | 'activeCurrent' | 'recovered' | 'recoveredCurrent'
|
||||
type: 'new' | 'active' | 'trackedActiveAlerts' | 'recovered' | 'trackedRecoveredAlerts'
|
||||
) {
|
||||
if (Object.hasOwn(this.processedAlerts, type)) {
|
||||
return this.processedAlerts[type];
|
||||
|
@ -241,21 +211,53 @@ export class LegacyAlertsClient<
|
|||
return {};
|
||||
}
|
||||
|
||||
public getAlertsToSerialize(shouldSetFlappingAndOptimize: boolean = true) {
|
||||
if (shouldSetFlappingAndOptimize) {
|
||||
setFlapping<State, Context, ActionGroupIds, RecoveryActionGroupId>(
|
||||
this.flappingSettings,
|
||||
this.processedAlerts.active,
|
||||
this.processedAlerts.recovered
|
||||
);
|
||||
}
|
||||
return determineAlertsToReturn<State, Context, ActionGroupIds, RecoveryActionGroupId>(
|
||||
this.processedAlerts.active,
|
||||
this.processedAlerts.recovered,
|
||||
shouldSetFlappingAndOptimize
|
||||
public getRawAlertInstancesForState() {
|
||||
return toRawAlertInstances<State, Context, ActionGroupIds, RecoveryActionGroupId>(
|
||||
this.processedAlerts.trackedActiveAlerts,
|
||||
this.processedAlerts.trackedRecoveredAlerts
|
||||
);
|
||||
}
|
||||
|
||||
public determineFlappingAlerts() {
|
||||
if (this.flappingSettings.enabled) {
|
||||
const alerts = determineFlappingAlerts({
|
||||
logger: this.options.logger,
|
||||
newAlerts: this.processedAlerts.new,
|
||||
activeAlerts: this.processedAlerts.active,
|
||||
recoveredAlerts: this.processedAlerts.recovered,
|
||||
flappingSettings: this.flappingSettings,
|
||||
previouslyRecoveredAlerts: this.trackedAlerts.recovered,
|
||||
actionGroupId: this.options.ruleType.defaultActionGroupId,
|
||||
maxAlerts: this.maxAlerts,
|
||||
});
|
||||
|
||||
this.processedAlerts.new = alerts.newAlerts;
|
||||
this.processedAlerts.active = alerts.activeAlerts;
|
||||
this.processedAlerts.trackedActiveAlerts = alerts.trackedActiveAlerts;
|
||||
this.processedAlerts.recovered = alerts.recoveredAlerts;
|
||||
this.processedAlerts.trackedRecoveredAlerts = alerts.trackedRecoveredAlerts;
|
||||
}
|
||||
}
|
||||
|
||||
public determineDelayedAlerts(opts: DetermineDelayedAlertsOpts) {
|
||||
const alerts = determineDelayedAlerts({
|
||||
newAlerts: this.processedAlerts.new,
|
||||
activeAlerts: this.processedAlerts.active,
|
||||
trackedActiveAlerts: this.processedAlerts.trackedActiveAlerts,
|
||||
recoveredAlerts: this.processedAlerts.recovered,
|
||||
trackedRecoveredAlerts: this.processedAlerts.trackedRecoveredAlerts,
|
||||
alertDelay: opts.alertDelay,
|
||||
startedAt: this.startedAtString,
|
||||
ruleRunMetricsStore: opts.ruleRunMetricsStore,
|
||||
});
|
||||
|
||||
this.processedAlerts.new = alerts.newAlerts;
|
||||
this.processedAlerts.active = alerts.activeAlerts;
|
||||
this.processedAlerts.trackedActiveAlerts = alerts.trackedActiveAlerts;
|
||||
this.processedAlerts.recovered = alerts.recoveredAlerts;
|
||||
this.processedAlerts.trackedRecoveredAlerts = alerts.trackedRecoveredAlerts;
|
||||
}
|
||||
|
||||
public hasReachedAlertLimit(): boolean {
|
||||
return this.alertFactory!.hasReachedAlertLimit();
|
||||
}
|
||||
|
|
|
@ -74,20 +74,20 @@ export interface IAlertsClient<
|
|||
initializeExecution(opts: InitializeExecutionOpts): Promise<void>;
|
||||
hasReachedAlertLimit(): boolean;
|
||||
checkLimitUsage(): void;
|
||||
processAlerts(opts: ProcessAlertsOpts): void;
|
||||
processAlerts(): void;
|
||||
logAlerts(opts: LogAlertsOpts): void;
|
||||
getProcessedAlerts(
|
||||
type: 'new' | 'active' | 'activeCurrent'
|
||||
type: 'new' | 'active' | 'trackedActiveAlerts'
|
||||
): Record<string, LegacyAlert<State, Context, ActionGroupIds>> | {};
|
||||
getProcessedAlerts(
|
||||
type: 'recovered' | 'recoveredCurrent'
|
||||
type: 'recovered' | 'trackedRecoveredAlerts'
|
||||
): Record<string, LegacyAlert<State, Context, RecoveryActionGroupId>> | {};
|
||||
persistAlerts(): Promise<{ alertIds: string[]; maintenanceWindowIds: string[] } | null>;
|
||||
isTrackedAlert(id: string): boolean;
|
||||
getSummarizedAlerts?(params: GetSummarizedAlertsParams): Promise<SummarizedAlerts>;
|
||||
getAlertsToSerialize(): {
|
||||
alertsToReturn: Record<string, RawAlertInstance>;
|
||||
recoveredAlertsToReturn: Record<string, RawAlertInstance>;
|
||||
getRawAlertInstancesForState(): {
|
||||
rawActiveAlerts: Record<string, RawAlertInstance>;
|
||||
rawRecoveredAlerts: Record<string, RawAlertInstance>;
|
||||
};
|
||||
factory(): PublicAlertFactory<
|
||||
State,
|
||||
|
@ -100,6 +100,8 @@ export interface IAlertsClient<
|
|||
Context,
|
||||
WithoutReservedActionGroups<ActionGroupIds, RecoveryActionGroupId>
|
||||
> | null;
|
||||
determineFlappingAlerts(): void;
|
||||
determineDelayedAlerts(opts: DetermineDelayedAlertsOpts): void;
|
||||
}
|
||||
|
||||
export interface ProcessAndLogAlertsOpts {
|
||||
|
@ -111,12 +113,10 @@ export interface ProcessAndLogAlertsOpts {
|
|||
alertDelay: number;
|
||||
}
|
||||
|
||||
export interface ProcessAlertsOpts {
|
||||
flappingSettings: RulesSettingsFlappingProperties;
|
||||
export interface DetermineDelayedAlertsOpts {
|
||||
alertDelay: number;
|
||||
ruleRunMetricsStore: RuleRunMetricsStore;
|
||||
}
|
||||
|
||||
export interface LogAlertsOpts {
|
||||
shouldLogAlerts: boolean;
|
||||
ruleRunMetricsStore: RuleRunMetricsStore;
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { keys, size } from 'lodash';
|
||||
import { Alert } from '../alert';
|
||||
import { determineAlertsToReturn } from './determine_alerts_to_return';
|
||||
|
||||
describe('determineAlertsToReturn', () => {
|
||||
const flapping = new Array(16).fill(false).concat([true, true, true, true]);
|
||||
const notFlapping = new Array(20).fill(false);
|
||||
|
||||
describe('determineAlertsToReturn', () => {
|
||||
test('should return all active alerts regardless of flapping', () => {
|
||||
const activeAlerts = {
|
||||
'1': new Alert('1', { meta: { flappingHistory: flapping } }),
|
||||
'2': new Alert('2', { meta: { flappingHistory: [false, false] } }),
|
||||
};
|
||||
const { alertsToReturn } = determineAlertsToReturn(activeAlerts, {});
|
||||
expect(size(alertsToReturn)).toEqual(2);
|
||||
});
|
||||
|
||||
test('should return all flapping recovered alerts', () => {
|
||||
const recoveredAlerts = {
|
||||
'1': new Alert('1', { meta: { flappingHistory: flapping } }),
|
||||
'2': new Alert('2', { meta: { flappingHistory: notFlapping } }),
|
||||
};
|
||||
const { recoveredAlertsToReturn } = determineAlertsToReturn({}, recoveredAlerts);
|
||||
expect(keys(recoveredAlertsToReturn)).toEqual(['1']);
|
||||
});
|
||||
|
||||
test('should return all recovered alerts if the optimization flag is set to false', () => {
|
||||
const recoveredAlerts = {
|
||||
'1': new Alert('1', { meta: { flappingHistory: flapping } }),
|
||||
'2': new Alert('2', { meta: { flappingHistory: notFlapping } }),
|
||||
};
|
||||
const { recoveredAlertsToReturn } = determineAlertsToReturn({}, recoveredAlerts, false);
|
||||
expect(keys(recoveredAlertsToReturn)).toEqual(['1', '2']);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { keys } from 'lodash';
|
||||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext, RawAlertInstance } from '../types';
|
||||
|
||||
// determines which alerts to return in the state
|
||||
export function determineAlertsToReturn<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>(
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>> = {},
|
||||
shouldOptimizeTaskState: boolean = true
|
||||
): {
|
||||
alertsToReturn: Record<string, RawAlertInstance>;
|
||||
recoveredAlertsToReturn: Record<string, RawAlertInstance>;
|
||||
} {
|
||||
const alertsToReturn: Record<string, RawAlertInstance> = {};
|
||||
const recoveredAlertsToReturn: Record<string, RawAlertInstance> = {};
|
||||
|
||||
// return all active alerts regardless of whether or not the alert is flapping
|
||||
for (const id of keys(activeAlerts)) {
|
||||
alertsToReturn[id] = activeAlerts[id].toRaw();
|
||||
}
|
||||
|
||||
for (const id of keys(recoveredAlerts)) {
|
||||
const alert = recoveredAlerts[id];
|
||||
if (shouldOptimizeTaskState) {
|
||||
// return recovered alerts if they are flapping or if the flapping array is not at capacity
|
||||
// this is a space saving effort that will stop tracking a recovered alert if it wasn't flapping and doesn't have state changes
|
||||
// in the last max capcity number of executions
|
||||
const flapping = alert.getFlapping();
|
||||
const flappingHistory: boolean[] = alert.getFlappingHistory() || [];
|
||||
const numStateChanges = flappingHistory.filter((f) => f).length;
|
||||
if (flapping) {
|
||||
recoveredAlertsToReturn[id] = alert.toRaw(true);
|
||||
} else if (numStateChanges > 0) {
|
||||
recoveredAlertsToReturn[id] = alert.toRaw(true);
|
||||
}
|
||||
} else {
|
||||
recoveredAlertsToReturn[id] = alert.toRaw(true);
|
||||
}
|
||||
}
|
||||
return { alertsToReturn, recoveredAlertsToReturn };
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { determineDelayedAlerts } from './determine_delayed_alerts';
|
||||
import { Alert } from '../alert';
|
||||
import { alertsWithAnyUUID } from '../test_utils';
|
||||
import { ruleRunMetricsStoreMock } from './rule_run_metrics_store.mock';
|
||||
|
||||
describe('determineDelayedAlerts', () => {
|
||||
const ruleRunMetricsStore = ruleRunMetricsStoreMock.create();
|
||||
|
||||
test('should increment activeCount for all active alerts', () => {
|
||||
const alert1 = new Alert('1', {
|
||||
meta: { activeCount: 1, uuid: 'uuid-1' },
|
||||
});
|
||||
const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } });
|
||||
|
||||
const { newAlerts, activeAlerts, trackedActiveAlerts } = determineDelayedAlerts({
|
||||
newAlerts: {
|
||||
'1': alert1,
|
||||
},
|
||||
activeAlerts: {
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
},
|
||||
trackedActiveAlerts: {
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
},
|
||||
recoveredAlerts: {},
|
||||
trackedRecoveredAlerts: {},
|
||||
alertDelay: 0,
|
||||
startedAt: null,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 2,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(trackedActiveAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 2,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 1,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 2,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 1,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(ruleRunMetricsStore.setNumberOfDelayedAlerts).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
test('should reset activeCount for all recovered alerts', () => {
|
||||
const alert1 = new Alert('1', { meta: { activeCount: 3 } });
|
||||
const alert3 = new Alert('3');
|
||||
|
||||
const { recoveredAlerts, trackedRecoveredAlerts } = determineDelayedAlerts({
|
||||
newAlerts: {},
|
||||
activeAlerts: {},
|
||||
trackedActiveAlerts: {},
|
||||
recoveredAlerts: { '1': alert1, '3': alert3 },
|
||||
trackedRecoveredAlerts: { '1': alert1, '3': alert3 },
|
||||
alertDelay: 0,
|
||||
startedAt: null,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
||||
expect(alertsWithAnyUUID(trackedRecoveredAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(alertsWithAnyUUID(recoveredAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(ruleRunMetricsStore.setNumberOfDelayedAlerts).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
test('should remove the alert from newAlerts and should not return the alert in activeAlerts if the activeCount is less than the rule alertDelay', () => {
|
||||
const alert1 = new Alert('1', {
|
||||
meta: { activeCount: 1, uuid: 'uuid-1' },
|
||||
});
|
||||
const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } });
|
||||
|
||||
const { newAlerts, activeAlerts, trackedActiveAlerts } = determineDelayedAlerts({
|
||||
newAlerts: { '1': alert1 },
|
||||
activeAlerts: { '1': alert1, '2': alert2 },
|
||||
trackedActiveAlerts: { '1': alert1, '2': alert2 },
|
||||
recoveredAlerts: {},
|
||||
trackedRecoveredAlerts: {},
|
||||
alertDelay: 5,
|
||||
startedAt: null,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(trackedActiveAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 2,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 1,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(ruleRunMetricsStore.setNumberOfDelayedAlerts).toHaveBeenCalledWith(2);
|
||||
});
|
||||
|
||||
test('should remove the alert from recoveredAlerts and should not return the alert in trackedRecoveredAlerts if the activeCount is less than the rule alertDelay', () => {
|
||||
const alert1 = new Alert('1', {
|
||||
meta: { activeCount: 1, uuid: 'uuid-1' },
|
||||
});
|
||||
const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } });
|
||||
|
||||
const { recoveredAlerts, trackedRecoveredAlerts } = determineDelayedAlerts({
|
||||
newAlerts: {},
|
||||
activeAlerts: {},
|
||||
trackedActiveAlerts: {},
|
||||
recoveredAlerts: { '1': alert1, '2': alert2 },
|
||||
trackedRecoveredAlerts: { '1': alert1, '2': alert2 },
|
||||
alertDelay: 5,
|
||||
startedAt: null,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(trackedRecoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(ruleRunMetricsStore.setNumberOfDelayedAlerts).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
test('should update active alert to look like a new alert if the activeCount is equal to the rule alertDelay', () => {
|
||||
const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } });
|
||||
|
||||
const { newAlerts, activeAlerts, trackedActiveAlerts } = determineDelayedAlerts({
|
||||
newAlerts: {},
|
||||
activeAlerts: { '2': alert2 },
|
||||
trackedActiveAlerts: { '2': alert2 },
|
||||
recoveredAlerts: {},
|
||||
trackedRecoveredAlerts: {},
|
||||
alertDelay: 1,
|
||||
startedAt: null,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(newAlerts['2'].getState().duration).toBe('0');
|
||||
expect(newAlerts['2'].getState().start).toBeTruthy();
|
||||
|
||||
expect(trackedActiveAlerts['2'].getState().duration).toBe('0');
|
||||
expect(trackedActiveAlerts['2'].getState().start).toBeTruthy();
|
||||
|
||||
expect(activeAlerts['2'].getState().duration).toBe('0');
|
||||
expect(activeAlerts['2'].getState().start).toBeTruthy();
|
||||
|
||||
expect(ruleRunMetricsStore.setNumberOfDelayedAlerts).toHaveBeenCalledWith(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { keys } from 'lodash';
|
||||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../types';
|
||||
import { RuleRunMetricsStore } from './rule_run_metrics_store';
|
||||
|
||||
interface DetermineDelayedAlertsOpts<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
> {
|
||||
newAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
trackedActiveAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
trackedRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
alertDelay: number;
|
||||
startedAt?: string | null;
|
||||
ruleRunMetricsStore: RuleRunMetricsStore;
|
||||
}
|
||||
|
||||
export function determineDelayedAlerts<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>({
|
||||
newAlerts,
|
||||
activeAlerts,
|
||||
trackedActiveAlerts,
|
||||
recoveredAlerts,
|
||||
trackedRecoveredAlerts,
|
||||
alertDelay,
|
||||
startedAt,
|
||||
ruleRunMetricsStore,
|
||||
}: DetermineDelayedAlertsOpts<State, Context, ActionGroupIds, RecoveryActionGroupId>) {
|
||||
let delayedAlertsCount = 0;
|
||||
|
||||
for (const id of keys(activeAlerts)) {
|
||||
const alert = activeAlerts[id];
|
||||
alert.incrementActiveCount();
|
||||
// do not trigger an action notification if the number of consecutive
|
||||
// active alerts is less than the rule alertDelay threshold
|
||||
if (alert.getActiveCount() < alertDelay) {
|
||||
// remove from new alerts and active alerts
|
||||
delete newAlerts[id];
|
||||
delete activeAlerts[id];
|
||||
|
||||
delayedAlertsCount += 1;
|
||||
} else {
|
||||
// if the active count is equal to the alertDelay it is considered a new alert
|
||||
if (alert.getActiveCount() === alertDelay) {
|
||||
const currentTime = startedAt ?? new Date().toISOString();
|
||||
const state = alert.getState();
|
||||
// keep the state and update the start time and duration
|
||||
alert.replaceState({ ...state, start: currentTime, duration: '0' });
|
||||
newAlerts[id] = alert;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of keys(recoveredAlerts)) {
|
||||
const alert = recoveredAlerts[id];
|
||||
// if alert has not reached the alertDelay threshold don't recover the alert
|
||||
if (alert.getActiveCount() < alertDelay) {
|
||||
// remove from recovered alerts
|
||||
delete recoveredAlerts[id];
|
||||
delete trackedRecoveredAlerts[id];
|
||||
}
|
||||
alert.resetActiveCount();
|
||||
}
|
||||
|
||||
ruleRunMetricsStore.setNumberOfDelayedAlerts(delayedAlertsCount);
|
||||
|
||||
return {
|
||||
newAlerts,
|
||||
activeAlerts,
|
||||
trackedActiveAlerts,
|
||||
recoveredAlerts,
|
||||
trackedRecoveredAlerts,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '../../../common/rules_settings';
|
||||
import { Alert } from '../../alert';
|
||||
import { alertsWithAnyUUID } from '../../test_utils';
|
||||
import {
|
||||
delayRecoveredFlappingAlerts,
|
||||
getEarlyRecoveredAlertIds,
|
||||
} from './delay_recovered_flapping_alerts';
|
||||
|
||||
describe('delayRecoveredFlappingAlerts', () => {
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
|
||||
test('should set pendingRecoveredCount to zero for all active alerts', () => {
|
||||
const alert1 = new Alert('1', {
|
||||
meta: { flapping: true, pendingRecoveredCount: 3, uuid: 'uuid-1' },
|
||||
});
|
||||
const alert2 = new Alert('2', { meta: { flapping: false, uuid: 'uuid-2' } });
|
||||
|
||||
const { newAlerts, activeAlerts, trackedActiveAlerts } = delayRecoveredFlappingAlerts(
|
||||
logger,
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
1000,
|
||||
{
|
||||
// new alerts
|
||||
'1': alert1,
|
||||
},
|
||||
{
|
||||
// active alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
},
|
||||
{
|
||||
// tracked active alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
},
|
||||
{}, // recovered alerts
|
||||
{} // tracked recovered alerts
|
||||
);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(trackedActiveAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('should return flapping pending recovered alerts as active alerts and current active alerts', () => {
|
||||
const alert1 = new Alert('1', { meta: { flapping: true, pendingRecoveredCount: 3 } });
|
||||
const alert2 = new Alert('2', { meta: { flapping: false } });
|
||||
const alert3 = new Alert('3', { meta: { flapping: true } });
|
||||
|
||||
const {
|
||||
newAlerts,
|
||||
activeAlerts,
|
||||
trackedActiveAlerts,
|
||||
recoveredAlerts,
|
||||
trackedRecoveredAlerts,
|
||||
} = delayRecoveredFlappingAlerts(
|
||||
logger,
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
1000,
|
||||
{}, // new alerts
|
||||
{}, // active alerts
|
||||
{}, // tracked active alerts
|
||||
{
|
||||
// recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
},
|
||||
{
|
||||
// tracked recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
}
|
||||
);
|
||||
|
||||
expect(alertsWithAnyUUID(newAlerts)).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(alertsWithAnyUUID(trackedActiveAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 1,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(Object.values(trackedActiveAlerts).map((a) => a.getScheduledActionOptions()))
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"actionGroup": "default",
|
||||
"context": Object {},
|
||||
"state": Object {},
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(alertsWithAnyUUID(activeAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 1,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(Object.values(activeAlerts).map((a) => a.getScheduledActionOptions()))
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"actionGroup": "default",
|
||||
"context": Object {},
|
||||
"state": Object {},
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(alertsWithAnyUUID(trackedRecoveredAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(alertsWithAnyUUID(recoveredAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
describe('getEarlyRecoveredAlertIds', () => {
|
||||
const alert1 = new Alert('1', { meta: { flappingHistory: [true, true, true, true] } });
|
||||
const alert2 = new Alert('2', { meta: { flappingHistory: new Array(20).fill(false) } });
|
||||
const alert3 = new Alert('3', { meta: { flappingHistory: [true, true] } });
|
||||
|
||||
test('should remove longest recovered alerts', () => {
|
||||
const { recoveredAlerts, trackedRecoveredAlerts } = delayRecoveredFlappingAlerts(
|
||||
logger,
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
2,
|
||||
{}, // new alerts
|
||||
{}, // active alerts
|
||||
{}, // tracked active alerts
|
||||
{
|
||||
// recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
},
|
||||
{
|
||||
// tracked recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
}
|
||||
);
|
||||
expect(Object.keys(recoveredAlerts).length).toBe(3);
|
||||
expect(recoveredAlerts['2'].getFlapping()).toBe(false);
|
||||
expect(Object.keys(trackedRecoveredAlerts).length).toBe(2);
|
||||
});
|
||||
|
||||
test('should not remove alerts if the num of recovered alerts is not at the limit', () => {
|
||||
const { recoveredAlerts, trackedRecoveredAlerts } = delayRecoveredFlappingAlerts(
|
||||
logger,
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
3,
|
||||
{}, // new alerts
|
||||
{}, // active alerts
|
||||
{}, // tracked active alerts
|
||||
{
|
||||
// recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
},
|
||||
{
|
||||
// tracked recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
}
|
||||
);
|
||||
expect(Object.keys(recoveredAlerts).length).toBe(3);
|
||||
expect(recoveredAlerts['2'].getFlapping()).toBe(false);
|
||||
expect(Object.keys(trackedRecoveredAlerts).length).toBe(3);
|
||||
});
|
||||
|
||||
test('getEarlyRecoveredAlertIds should return longest recovered alerts', () => {
|
||||
const alertIds = getEarlyRecoveredAlertIds(
|
||||
logger,
|
||||
{
|
||||
// tracked recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
},
|
||||
2
|
||||
);
|
||||
expect(alertIds).toEqual(['2']);
|
||||
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
'Recovered alerts have exceeded the max alert limit of 2 : dropping 1 alert.'
|
||||
);
|
||||
});
|
||||
|
||||
test('getEarlyRecoveredAlertIds should not return alerts if the num of recovered alerts is not at the limit', () => {
|
||||
const trimmedAlerts = getEarlyRecoveredAlertIds(
|
||||
logger,
|
||||
{
|
||||
// tracked recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
},
|
||||
2
|
||||
);
|
||||
expect(trimmedAlerts).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { keys, map } from 'lodash';
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { RulesSettingsFlappingProperties } from '../../../common/rules_settings';
|
||||
import { Alert } from '../../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../../types';
|
||||
|
||||
export function delayRecoveredFlappingAlerts<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>(
|
||||
logger: Logger,
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
actionGroupId: string,
|
||||
maxAlerts: number,
|
||||
newAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
trackedActiveAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>> = {},
|
||||
trackedRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>> = {}
|
||||
) {
|
||||
for (const id of keys(activeAlerts)) {
|
||||
const alert = activeAlerts[id];
|
||||
alert.resetPendingRecoveredCount();
|
||||
}
|
||||
|
||||
for (const id of keys(recoveredAlerts)) {
|
||||
const alert = recoveredAlerts[id];
|
||||
const flapping = alert.getFlapping();
|
||||
if (flapping) {
|
||||
alert.incrementPendingRecoveredCount();
|
||||
|
||||
if (alert.getPendingRecoveredCount() < flappingSettings.statusChangeThreshold) {
|
||||
// keep the context and previous actionGroupId if available
|
||||
const context = alert.getContext();
|
||||
const lastActionGroupId = alert.getLastScheduledActions()?.group;
|
||||
|
||||
const newAlert = new Alert<State, Context, ActionGroupIds>(id, alert.toRaw());
|
||||
// unset the end time in the alert state
|
||||
const state = newAlert.getState();
|
||||
delete state.end;
|
||||
newAlert.replaceState(state);
|
||||
|
||||
// schedule actions for the new active alert
|
||||
newAlert.scheduleActions(
|
||||
(lastActionGroupId ? lastActionGroupId : actionGroupId) as ActionGroupIds,
|
||||
context
|
||||
);
|
||||
activeAlerts[id] = newAlert;
|
||||
trackedActiveAlerts[id] = newAlert;
|
||||
|
||||
// remove from recovered alerts
|
||||
delete recoveredAlerts[id];
|
||||
delete trackedRecoveredAlerts[id];
|
||||
} else {
|
||||
alert.resetPendingRecoveredCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const earlyRecoveredAlertIds = getEarlyRecoveredAlertIds(
|
||||
logger,
|
||||
trackedRecoveredAlerts,
|
||||
maxAlerts
|
||||
);
|
||||
for (const id of earlyRecoveredAlertIds) {
|
||||
const alert = trackedRecoveredAlerts[id];
|
||||
alert.setFlapping(false);
|
||||
recoveredAlerts[id] = alert;
|
||||
|
||||
delete trackedRecoveredAlerts[id];
|
||||
}
|
||||
|
||||
return {
|
||||
newAlerts,
|
||||
activeAlerts,
|
||||
trackedActiveAlerts,
|
||||
recoveredAlerts,
|
||||
trackedRecoveredAlerts,
|
||||
};
|
||||
}
|
||||
|
||||
export function getEarlyRecoveredAlertIds<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
RecoveryActionGroupId extends string
|
||||
>(
|
||||
logger: Logger,
|
||||
trackedRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>>,
|
||||
maxAlerts: number
|
||||
) {
|
||||
const alerts = map(trackedRecoveredAlerts, (alert, id) => {
|
||||
return {
|
||||
id,
|
||||
flappingHistory: alert.getFlappingHistory() || [],
|
||||
};
|
||||
});
|
||||
|
||||
let earlyRecoveredAlertIds: string[] = [];
|
||||
if (alerts.length > maxAlerts) {
|
||||
alerts.sort((a, b) => {
|
||||
return a.flappingHistory.length - b.flappingHistory.length;
|
||||
});
|
||||
|
||||
earlyRecoveredAlertIds = alerts.slice(maxAlerts).map((alert) => alert.id);
|
||||
logger.warn(
|
||||
`Recovered alerts have exceeded the max alert limit of ${maxAlerts} : dropping ${
|
||||
earlyRecoveredAlertIds.length
|
||||
} ${earlyRecoveredAlertIds.length > 1 ? 'alerts' : 'alert'}.`
|
||||
);
|
||||
}
|
||||
return earlyRecoveredAlertIds;
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { Alert } from '../../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../../types';
|
||||
import { RulesSettingsFlappingProperties } from '../../../common/rules_settings';
|
||||
import { setFlapping } from './set_flapping';
|
||||
import { setFlappingHistoryAndTrackedAlerts } from './set_flapping_history_and_tracked_alerts';
|
||||
import { delayRecoveredFlappingAlerts } from './delay_recovered_flapping_alerts';
|
||||
|
||||
interface DetermineFlappingAlertsOpts<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
> {
|
||||
logger: Logger;
|
||||
newAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
flappingSettings: RulesSettingsFlappingProperties;
|
||||
previouslyRecoveredAlerts: Record<string, Alert<State, Context>>;
|
||||
actionGroupId: string;
|
||||
maxAlerts: number;
|
||||
}
|
||||
|
||||
export function determineFlappingAlerts<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>({
|
||||
logger,
|
||||
newAlerts,
|
||||
activeAlerts,
|
||||
recoveredAlerts,
|
||||
flappingSettings,
|
||||
previouslyRecoveredAlerts,
|
||||
actionGroupId,
|
||||
maxAlerts,
|
||||
}: DetermineFlappingAlertsOpts<State, Context, ActionGroupIds, RecoveryActionGroupId>) {
|
||||
setFlapping<State, Context, ActionGroupIds, RecoveryActionGroupId>(
|
||||
flappingSettings,
|
||||
activeAlerts,
|
||||
recoveredAlerts
|
||||
);
|
||||
|
||||
let alerts = setFlappingHistoryAndTrackedAlerts<
|
||||
State,
|
||||
Context,
|
||||
ActionGroupIds,
|
||||
RecoveryActionGroupId
|
||||
>(flappingSettings, newAlerts, activeAlerts, recoveredAlerts, previouslyRecoveredAlerts);
|
||||
|
||||
alerts = delayRecoveredFlappingAlerts<State, Context, ActionGroupIds, RecoveryActionGroupId>(
|
||||
logger,
|
||||
flappingSettings,
|
||||
actionGroupId,
|
||||
maxAlerts,
|
||||
alerts.newAlerts,
|
||||
alerts.activeAlerts,
|
||||
alerts.trackedActiveAlerts,
|
||||
alerts.recoveredAlerts,
|
||||
alerts.trackedRecoveredAlerts
|
||||
);
|
||||
|
||||
return alerts;
|
||||
}
|
|
@ -5,7 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DEFAULT_FLAPPING_SETTINGS, DISABLE_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
import {
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
DISABLE_FLAPPING_SETTINGS,
|
||||
} from '../../../common/rules_settings';
|
||||
import { atCapacity, updateFlappingHistory, isFlapping } from './flapping_utils';
|
||||
|
||||
describe('flapping utils', () => {
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RulesSettingsFlappingProperties } from '../../common/rules_settings';
|
||||
import { RulesSettingsFlappingProperties } from '../../../common/rules_settings';
|
||||
|
||||
export function updateFlappingHistory(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
|
@ -6,10 +6,13 @@
|
|||
*/
|
||||
|
||||
import { pick } from 'lodash';
|
||||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext, DefaultActionGroupId } from '../../common';
|
||||
import { Alert } from '../../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext, DefaultActionGroupId } from '../../../common';
|
||||
import { setFlapping, isAlertFlapping } from './set_flapping';
|
||||
import { DEFAULT_FLAPPING_SETTINGS, DISABLE_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
import {
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
DISABLE_FLAPPING_SETTINGS,
|
||||
} from '../../../common/rules_settings';
|
||||
|
||||
describe('setFlapping', () => {
|
||||
const flapping = new Array(16).fill(false).concat([true, true, true, true]);
|
|
@ -6,10 +6,10 @@
|
|||
*/
|
||||
|
||||
import { keys } from 'lodash';
|
||||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../types';
|
||||
import { Alert } from '../../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../../types';
|
||||
import { isFlapping } from './flapping_utils';
|
||||
import { RulesSettingsFlappingProperties } from '../../common/rules_settings';
|
||||
import { RulesSettingsFlappingProperties } from '../../../common/rules_settings';
|
||||
|
||||
export function setFlapping<
|
||||
State extends AlertInstanceState,
|
|
@ -0,0 +1,481 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import {
|
||||
updateAlertFlappingHistory,
|
||||
setFlappingHistoryAndTrackedAlerts,
|
||||
} from './set_flapping_history_and_tracked_alerts';
|
||||
import { Alert } from '../../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../../types';
|
||||
import {
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
DISABLE_FLAPPING_SETTINGS,
|
||||
} from '../../../common/rules_settings';
|
||||
|
||||
describe('setFlappingHistoryAndTrackedAlerts', () => {
|
||||
let clock: sinon.SinonFakeTimers;
|
||||
|
||||
beforeAll(() => {
|
||||
clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
clock.reset();
|
||||
});
|
||||
|
||||
afterAll(() => clock.restore());
|
||||
|
||||
test('if new alert, set flapping state to true', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { uuid: 'uuid-1' },
|
||||
state: {
|
||||
duration: '0',
|
||||
start: '1970-01-01T00:00:00.000Z',
|
||||
},
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': activeAlert });
|
||||
alerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
|
||||
const { activeAlerts, newAlerts, trackedActiveAlerts, recoveredAlerts } =
|
||||
setFlappingHistoryAndTrackedAlerts(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
alerts, // new alerts
|
||||
alerts, // active alerts
|
||||
{}, // recovered alerts
|
||||
{} // previously recovered alerts
|
||||
);
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(trackedActiveAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
|
||||
test('if alert is still active, set flapping state to false', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': activeAlert });
|
||||
alerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
|
||||
const { activeAlerts, newAlerts, trackedActiveAlerts, recoveredAlerts } =
|
||||
setFlappingHistoryAndTrackedAlerts(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
{}, // new alerts
|
||||
alerts, // active alerts
|
||||
{}, // recovered alerts
|
||||
{} // previously recovered alerts
|
||||
);
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(trackedActiveAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
|
||||
test('if alert is active and previously recovered, set flapping state to true', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { uuid: 'uuid-1' },
|
||||
state: {
|
||||
duration: '0',
|
||||
start: '1970-01-01T00:00:00.000Z',
|
||||
},
|
||||
});
|
||||
const recoveredAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-2' },
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': activeAlert });
|
||||
alerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
alerts['1'].setFlappingHistory([false]);
|
||||
|
||||
const { activeAlerts, newAlerts, trackedActiveAlerts, recoveredAlerts } =
|
||||
setFlappingHistoryAndTrackedAlerts(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
alerts, // new alerts
|
||||
alerts, // active alerts
|
||||
{}, // recovered alerts
|
||||
{ '1': recoveredAlert } // previously recovered alerts
|
||||
);
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(trackedActiveAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
|
||||
test('if alert is recovered and previously active, set flapping state to true', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
activeAlert.scheduleActions('default' as never, { foo: '1' });
|
||||
const recoveredAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': recoveredAlert });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts, trackedRecoveredAlerts } =
|
||||
setFlappingHistoryAndTrackedAlerts(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
{}, // new alerts
|
||||
{}, // active alerts
|
||||
alerts, // recovered alerts
|
||||
{} // previously recovered alerts
|
||||
);
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(trackedRecoveredAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('if alert is still recovered, set flapping state to false', () => {
|
||||
const recoveredAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': recoveredAlert });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts, trackedRecoveredAlerts } =
|
||||
setFlappingHistoryAndTrackedAlerts(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
{}, // new alerts
|
||||
{}, // active alerts
|
||||
{}, // recovered alerts
|
||||
alerts // previously recovered alerts
|
||||
);
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(trackedRecoveredAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('if setFlapping is false should not update flappingHistory', () => {
|
||||
const activeAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { uuid: 'uuid-1' },
|
||||
state: {
|
||||
duration: '0',
|
||||
start: '1970-01-01T00:00:00.000Z',
|
||||
},
|
||||
});
|
||||
activeAlert1.scheduleActions('default' as never, { foo: '1' });
|
||||
const activeAlert2 = new Alert<AlertInstanceState, AlertInstanceContext>('2', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-2' },
|
||||
});
|
||||
activeAlert2.scheduleActions('default' as never, { foo: '1' });
|
||||
const recoveredAlert = new Alert<AlertInstanceState, AlertInstanceContext>('3', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-3' },
|
||||
});
|
||||
|
||||
const previouslyRecoveredAlerts = cloneDeep({ '3': recoveredAlert });
|
||||
const alerts = cloneDeep({ '1': activeAlert1, '2': activeAlert2 });
|
||||
|
||||
const {
|
||||
newAlerts,
|
||||
activeAlerts,
|
||||
trackedActiveAlerts,
|
||||
recoveredAlerts,
|
||||
trackedRecoveredAlerts,
|
||||
} = setFlappingHistoryAndTrackedAlerts(
|
||||
DISABLE_FLAPPING_SETTINGS,
|
||||
cloneDeep({ '1': activeAlert1 }), // new alerts
|
||||
alerts, // active alerts
|
||||
{}, // recovered alerts
|
||||
previouslyRecoveredAlerts
|
||||
);
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(trackedActiveAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(trackedRecoveredAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-3",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
describe('updateAlertFlappingHistory', () => {
|
||||
test('correctly updates flappingHistory', () => {
|
||||
const alert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false, false] },
|
||||
});
|
||||
updateAlertFlappingHistory(DEFAULT_FLAPPING_SETTINGS, alert, true);
|
||||
expect(alert.getFlappingHistory()).toEqual([false, false, true]);
|
||||
});
|
||||
|
||||
test('correctly updates flappingHistory while maintaining a fixed size', () => {
|
||||
const flappingHistory = new Array(20).fill(false);
|
||||
const alert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory },
|
||||
});
|
||||
updateAlertFlappingHistory(DEFAULT_FLAPPING_SETTINGS, alert, true);
|
||||
const fh = alert.getFlappingHistory() || [];
|
||||
expect(fh.length).toEqual(20);
|
||||
const result = new Array(19).fill(false);
|
||||
expect(fh).toEqual(result.concat(true));
|
||||
});
|
||||
|
||||
test('correctly updates flappingHistory while maintaining if array is larger than fixed size', () => {
|
||||
const flappingHistory = new Array(23).fill(false);
|
||||
const alert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory },
|
||||
});
|
||||
updateAlertFlappingHistory(DEFAULT_FLAPPING_SETTINGS, alert, true);
|
||||
const fh = alert.getFlappingHistory() || [];
|
||||
expect(fh.length).toEqual(20);
|
||||
const result = new Array(19).fill(false);
|
||||
expect(fh).toEqual(result.concat(true));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { keys } from 'lodash';
|
||||
import { Alert } from '../../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../../types';
|
||||
import { updateFlappingHistory } from './flapping_utils';
|
||||
import { RulesSettingsFlappingProperties } from '../../../common/rules_settings';
|
||||
|
||||
export function setFlappingHistoryAndTrackedAlerts<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupIds extends string
|
||||
>(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
newAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupIds>> = {},
|
||||
previouslyRecoveredAlerts: Record<string, Alert<State, Context>> = {}
|
||||
): {
|
||||
newAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
trackedActiveAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupIds>>;
|
||||
trackedRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupIds>>;
|
||||
} {
|
||||
const trackedActiveAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {};
|
||||
const trackedRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupIds>> = {};
|
||||
const previouslyRecoveredAlertIds = new Set(Object.keys(previouslyRecoveredAlerts));
|
||||
|
||||
for (const id of keys(activeAlerts)) {
|
||||
const alert = activeAlerts[id];
|
||||
trackedActiveAlerts[id] = alert;
|
||||
// this alert was not active in the previous run
|
||||
if (newAlerts[id]) {
|
||||
if (previouslyRecoveredAlertIds.has(id)) {
|
||||
// this alert has flapped from recovered to active
|
||||
const previousFlappingHistory = previouslyRecoveredAlerts[id].getFlappingHistory();
|
||||
alert.setFlappingHistory(previousFlappingHistory);
|
||||
previouslyRecoveredAlertIds.delete(id);
|
||||
}
|
||||
updateAlertFlappingHistory(flappingSettings, newAlerts[id], true);
|
||||
} else {
|
||||
// this alert is still active
|
||||
updateAlertFlappingHistory(flappingSettings, alert, false);
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of keys(recoveredAlerts)) {
|
||||
const alert = recoveredAlerts[id];
|
||||
trackedRecoveredAlerts[id] = alert;
|
||||
// this alert has flapped from active to recovered
|
||||
updateAlertFlappingHistory(flappingSettings, alert, true);
|
||||
}
|
||||
|
||||
for (const id of previouslyRecoveredAlertIds) {
|
||||
const alert = previouslyRecoveredAlerts[id];
|
||||
trackedRecoveredAlerts[id] = alert;
|
||||
// this alert is still recovered
|
||||
updateAlertFlappingHistory(flappingSettings, alert, false);
|
||||
}
|
||||
|
||||
return { newAlerts, activeAlerts, trackedActiveAlerts, recoveredAlerts, trackedRecoveredAlerts };
|
||||
}
|
||||
|
||||
export function updateAlertFlappingHistory<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
alert: Alert<State, Context, ActionGroupIds | RecoveryActionGroupId>,
|
||||
state: boolean
|
||||
) {
|
||||
const updatedFlappingHistory = updateFlappingHistory(
|
||||
flappingSettings,
|
||||
alert.getFlappingHistory() || [],
|
||||
state
|
||||
);
|
||||
alert.setFlappingHistory(updatedFlappingHistory);
|
||||
}
|
|
@ -1,613 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DEFAULT_FLAPPING_SETTINGS, DISABLE_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
import { getAlertsForNotification } from '.';
|
||||
import { Alert } from '../alert';
|
||||
import { alertsWithAnyUUID } from '../test_utils';
|
||||
|
||||
describe('getAlertsForNotification', () => {
|
||||
test('should set pendingRecoveredCount to zero for all active alerts', () => {
|
||||
const alert1 = new Alert('1', {
|
||||
meta: { flapping: true, pendingRecoveredCount: 3, uuid: 'uuid-1' },
|
||||
});
|
||||
const alert2 = new Alert('2', { meta: { flapping: false, uuid: 'uuid-2' } });
|
||||
|
||||
const { newAlerts, activeAlerts } = getAlertsForNotification(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
0,
|
||||
{
|
||||
// new alerts
|
||||
'1': alert1,
|
||||
},
|
||||
{
|
||||
// active alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
},
|
||||
{},
|
||||
{}
|
||||
);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 1,
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 1,
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 1,
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('should return flapping pending recovered alerts as active alerts and current active alerts', () => {
|
||||
const alert1 = new Alert('1', { meta: { flapping: true, pendingRecoveredCount: 3 } });
|
||||
const alert2 = new Alert('2', { meta: { flapping: false } });
|
||||
const alert3 = new Alert('3', { meta: { flapping: true } });
|
||||
|
||||
const {
|
||||
newAlerts,
|
||||
activeAlerts,
|
||||
currentActiveAlerts,
|
||||
recoveredAlerts,
|
||||
currentRecoveredAlerts,
|
||||
} = getAlertsForNotification(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
0,
|
||||
{},
|
||||
{},
|
||||
{
|
||||
// recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
},
|
||||
{
|
||||
// current recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
}
|
||||
);
|
||||
|
||||
expect(alertsWithAnyUUID(newAlerts)).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(alertsWithAnyUUID(activeAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 1,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(Object.values(activeAlerts).map((a) => a.getScheduledActionOptions()))
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"actionGroup": "default",
|
||||
"context": Object {},
|
||||
"state": Object {},
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(alertsWithAnyUUID(currentActiveAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 1,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(Object.values(currentActiveAlerts).map((a) => a.getScheduledActionOptions()))
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"actionGroup": "default",
|
||||
"context": Object {},
|
||||
"state": Object {},
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(alertsWithAnyUUID(recoveredAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(alertsWithAnyUUID(currentRecoveredAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('should reset counts and not modify alerts if flapping is disabled', () => {
|
||||
const alert1 = new Alert('1', {
|
||||
meta: { flapping: true, flappingHistory: [true, false, true], pendingRecoveredCount: 3 },
|
||||
});
|
||||
const alert2 = new Alert('2', {
|
||||
meta: { flapping: false, flappingHistory: [true, false, true] },
|
||||
});
|
||||
const alert3 = new Alert('3', {
|
||||
meta: { flapping: true, flappingHistory: [true, false, true] },
|
||||
});
|
||||
|
||||
const { newAlerts, activeAlerts, recoveredAlerts, currentRecoveredAlerts } =
|
||||
getAlertsForNotification(
|
||||
DISABLE_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
0,
|
||||
{},
|
||||
{},
|
||||
{
|
||||
// recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
},
|
||||
{
|
||||
// current recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
}
|
||||
);
|
||||
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(alertsWithAnyUUID(recoveredAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(alertsWithAnyUUID(currentRecoveredAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('should increment activeCount for all active alerts', () => {
|
||||
const alert1 = new Alert('1', {
|
||||
meta: { activeCount: 1, uuid: 'uuid-1' },
|
||||
});
|
||||
const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } });
|
||||
|
||||
const { newAlerts, activeAlerts, currentActiveAlerts, delayedAlertsCount } =
|
||||
getAlertsForNotification(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
0,
|
||||
{
|
||||
// new alerts
|
||||
'1': alert1,
|
||||
},
|
||||
{
|
||||
// active alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
},
|
||||
{},
|
||||
{}
|
||||
);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 2,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 2,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 1,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(currentActiveAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 2,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 1,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(delayedAlertsCount).toBe(0);
|
||||
});
|
||||
|
||||
test('should reset activeCount for all recovered alerts', () => {
|
||||
const alert1 = new Alert('1', { meta: { activeCount: 3 } });
|
||||
const alert3 = new Alert('3');
|
||||
|
||||
const { recoveredAlerts, currentRecoveredAlerts, delayedAlertsCount } =
|
||||
getAlertsForNotification(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
0,
|
||||
{},
|
||||
{},
|
||||
{
|
||||
// recovered alerts
|
||||
'1': alert1,
|
||||
'3': alert3,
|
||||
},
|
||||
{
|
||||
// current recovered alerts
|
||||
'1': alert1,
|
||||
'3': alert3,
|
||||
}
|
||||
);
|
||||
|
||||
expect(alertsWithAnyUUID(recoveredAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(alertsWithAnyUUID(currentRecoveredAlerts)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 0,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": Any<String>,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(delayedAlertsCount).toBe(0);
|
||||
});
|
||||
|
||||
test('should remove the alert from newAlerts and should not return the alert in currentActiveAlerts if the activeCount is less than the rule alertDelay', () => {
|
||||
const alert1 = new Alert('1', {
|
||||
meta: { activeCount: 1, uuid: 'uuid-1' },
|
||||
});
|
||||
const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } });
|
||||
|
||||
const { newAlerts, activeAlerts, currentActiveAlerts, delayedAlertsCount } =
|
||||
getAlertsForNotification(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
5,
|
||||
{
|
||||
// new alerts
|
||||
'1': alert1,
|
||||
},
|
||||
{
|
||||
// active alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
},
|
||||
{},
|
||||
{}
|
||||
);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 2,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"activeCount": 1,
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"pendingRecoveredCount": 0,
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(currentActiveAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(delayedAlertsCount).toBe(2);
|
||||
});
|
||||
|
||||
test('should remove the alert from recoveredAlerts and should not return the alert in currentRecoveredAlerts if the activeCount is less than the rule alertDelay', () => {
|
||||
const alert1 = new Alert('1', {
|
||||
meta: { activeCount: 1, uuid: 'uuid-1' },
|
||||
});
|
||||
const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } });
|
||||
|
||||
const { recoveredAlerts, currentRecoveredAlerts, delayedAlertsCount } =
|
||||
getAlertsForNotification(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
5,
|
||||
{},
|
||||
{},
|
||||
{
|
||||
// recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
},
|
||||
{
|
||||
// current recovered alerts
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
}
|
||||
);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(currentRecoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(delayedAlertsCount).toBe(0);
|
||||
});
|
||||
|
||||
test('should update active alert to look like a new alert if the activeCount is equal to the rule alertDelay', () => {
|
||||
const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } });
|
||||
|
||||
const { newAlerts, activeAlerts, currentActiveAlerts, delayedAlertsCount } =
|
||||
getAlertsForNotification(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
1,
|
||||
{},
|
||||
{
|
||||
// active alerts
|
||||
'2': alert2,
|
||||
},
|
||||
{},
|
||||
{}
|
||||
);
|
||||
expect(newAlerts['2'].getState().duration).toBe('0');
|
||||
expect(newAlerts['2'].getState().start).toBeTruthy();
|
||||
|
||||
expect(activeAlerts['2'].getState().duration).toBe('0');
|
||||
expect(activeAlerts['2'].getState().start).toBeTruthy();
|
||||
|
||||
expect(currentActiveAlerts['2'].getState().duration).toBe('0');
|
||||
expect(currentActiveAlerts['2'].getState().start).toBeTruthy();
|
||||
|
||||
expect(delayedAlertsCount).toBe(0);
|
||||
});
|
||||
});
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { keys } from 'lodash';
|
||||
import { RulesSettingsFlappingProperties } from '../../common/rules_settings';
|
||||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../types';
|
||||
|
||||
export function getAlertsForNotification<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
actionGroupId: string,
|
||||
alertDelay: number,
|
||||
newAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>> = {},
|
||||
currentRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>> = {},
|
||||
startedAt?: string | null
|
||||
) {
|
||||
const currentActiveAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {};
|
||||
let delayedAlertsCount = 0;
|
||||
|
||||
for (const id of keys(activeAlerts)) {
|
||||
const alert = activeAlerts[id];
|
||||
alert.incrementActiveCount();
|
||||
alert.resetPendingRecoveredCount();
|
||||
// do not trigger an action notification if the number of consecutive
|
||||
// active alerts is less than the rule alertDelay threshold
|
||||
if (alert.getActiveCount() < alertDelay) {
|
||||
// remove from new alerts
|
||||
delete newAlerts[id];
|
||||
delayedAlertsCount += 1;
|
||||
} else {
|
||||
currentActiveAlerts[id] = alert;
|
||||
// if the active count is equal to the alertDelay it is considered a new alert
|
||||
if (alert.getActiveCount() === alertDelay) {
|
||||
const currentTime = startedAt ?? new Date().toISOString();
|
||||
const state = alert.getState();
|
||||
// keep the state and update the start time and duration
|
||||
alert.replaceState({ ...state, start: currentTime, duration: '0' });
|
||||
newAlerts[id] = alert;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of keys(currentRecoveredAlerts)) {
|
||||
const alert = recoveredAlerts[id];
|
||||
// if alert has not reached the alertDelay threshold don't recover the alert
|
||||
if (alert.getActiveCount() < alertDelay) {
|
||||
// remove from recovered alerts
|
||||
delete recoveredAlerts[id];
|
||||
delete currentRecoveredAlerts[id];
|
||||
}
|
||||
alert.resetActiveCount();
|
||||
if (flappingSettings.enabled) {
|
||||
const flapping = alert.getFlapping();
|
||||
if (flapping) {
|
||||
alert.incrementPendingRecoveredCount();
|
||||
|
||||
if (alert.getPendingRecoveredCount() < flappingSettings.statusChangeThreshold) {
|
||||
// keep the context and previous actionGroupId if available
|
||||
const context = alert.getContext();
|
||||
const lastActionGroupId = alert.getLastScheduledActions()?.group;
|
||||
|
||||
const newAlert = new Alert<State, Context, ActionGroupIds>(id, alert.toRaw());
|
||||
// unset the end time in the alert state
|
||||
const state = newAlert.getState();
|
||||
delete state.end;
|
||||
newAlert.replaceState(state);
|
||||
|
||||
// schedule actions for the new active alert
|
||||
newAlert.scheduleActions(
|
||||
(lastActionGroupId ? lastActionGroupId : actionGroupId) as ActionGroupIds,
|
||||
context
|
||||
);
|
||||
activeAlerts[id] = newAlert;
|
||||
currentActiveAlerts[id] = newAlert;
|
||||
|
||||
// remove from recovered alerts
|
||||
delete recoveredAlerts[id];
|
||||
delete currentRecoveredAlerts[id];
|
||||
} else {
|
||||
alert.resetPendingRecoveredCount();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert.resetPendingRecoveredCount();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
newAlerts,
|
||||
activeAlerts,
|
||||
currentActiveAlerts,
|
||||
recoveredAlerts,
|
||||
currentRecoveredAlerts,
|
||||
delayedAlertsCount,
|
||||
};
|
||||
}
|
|
@ -41,11 +41,7 @@ export { isRuleSnoozed, getRuleSnoozeEndTime } from './is_rule_snoozed';
|
|||
export { convertRuleIdsToKueryNode } from './convert_rule_ids_to_kuery_node';
|
||||
export { convertEsSortToEventLogSort } from './convert_es_sort_to_event_log_sort';
|
||||
export * from './snooze';
|
||||
export { setFlapping } from './set_flapping';
|
||||
export { determineAlertsToReturn } from './determine_alerts_to_return';
|
||||
export { updateFlappingHistory, isFlapping } from './flapping_utils';
|
||||
export { getAlertsForNotification } from './get_alerts_for_notification';
|
||||
export { trimRecoveredAlerts } from './trim_recovered_alerts';
|
||||
export { toRawAlertInstances } from './to_raw_alert_instances';
|
||||
export { createGetAlertIndicesAliasFn } from './create_get_alert_indices_alias';
|
||||
export type { GetAlertIndicesAlias } from './create_get_alert_indices_alias';
|
||||
export { getEsRequestTimeout } from './get_es_request_timeout';
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
|
||||
import sinon from 'sinon';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { processAlerts, updateAlertFlappingHistory } from './process_alerts';
|
||||
import { processAlerts } from './process_alerts';
|
||||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../types';
|
||||
import { DEFAULT_FLAPPING_SETTINGS, DISABLE_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
|
||||
describe('processAlerts', () => {
|
||||
let clock: sinon.SinonFakeTimers;
|
||||
|
@ -30,17 +29,12 @@ describe('processAlerts', () => {
|
|||
const newAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1');
|
||||
const existingAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('2');
|
||||
const existingAlert2 = new Alert<AlertInstanceState, AlertInstanceContext>('3', {});
|
||||
const existingRecoveredAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('4');
|
||||
|
||||
const existingAlerts = {
|
||||
'2': existingAlert1,
|
||||
'3': existingAlert2,
|
||||
};
|
||||
|
||||
const previouslyRecoveredAlerts = {
|
||||
'4': existingRecoveredAlert1,
|
||||
};
|
||||
|
||||
const updatedAlerts = {
|
||||
...cloneDeep(existingAlerts),
|
||||
'1': newAlert,
|
||||
|
@ -53,11 +47,9 @@ describe('processAlerts', () => {
|
|||
const { newAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts,
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(newAlerts).toEqual({ '1': newAlert });
|
||||
|
@ -91,11 +83,9 @@ describe('processAlerts', () => {
|
|||
const { newAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(newAlerts).toEqual({ '1': newAlert1, '2': newAlert2 });
|
||||
|
@ -141,11 +131,9 @@ describe('processAlerts', () => {
|
|||
const { newAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
startedAt: '2023-10-03T20:03:08.716Z',
|
||||
});
|
||||
|
||||
|
@ -188,11 +176,10 @@ describe('processAlerts', () => {
|
|||
const { activeAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -226,11 +213,10 @@ describe('processAlerts', () => {
|
|||
const { activeAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -274,11 +260,9 @@ describe('processAlerts', () => {
|
|||
const { activeAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -332,11 +316,9 @@ describe('processAlerts', () => {
|
|||
const { activeAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -397,11 +379,9 @@ describe('processAlerts', () => {
|
|||
const { activeAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -458,17 +438,11 @@ describe('processAlerts', () => {
|
|||
const { activeAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts,
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(
|
||||
Object.keys(activeAlerts).map((id) => ({ [id]: activeAlerts[id].getFlappingHistory() }))
|
||||
).toEqual([{ '1': [true] }, { '2': [true] }, { '3': [false] }, { '4': [false] }]);
|
||||
|
||||
const previouslyRecoveredAlert1State = activeAlerts['1'].getState();
|
||||
const previouslyRecoveredAlert2State = activeAlerts['2'].getState();
|
||||
|
||||
|
@ -514,18 +488,12 @@ describe('processAlerts', () => {
|
|||
const { activeAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts,
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
startedAt: '2023-10-03T20:03:08.716Z',
|
||||
});
|
||||
|
||||
expect(
|
||||
Object.keys(activeAlerts).map((id) => ({ [id]: activeAlerts[id].getFlappingHistory() }))
|
||||
).toEqual([{ '1': [true] }, { '2': [true] }, { '3': [false] }, { '4': [false] }]);
|
||||
|
||||
const previouslyRecoveredAlert1State = activeAlerts['1'].getState();
|
||||
const previouslyRecoveredAlert2State = activeAlerts['2'].getState();
|
||||
|
||||
|
@ -558,11 +526,9 @@ describe('processAlerts', () => {
|
|||
const { recoveredAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({ '2': updatedAlerts['2'] });
|
||||
|
@ -586,11 +552,9 @@ describe('processAlerts', () => {
|
|||
const { recoveredAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({});
|
||||
|
@ -616,11 +580,9 @@ describe('processAlerts', () => {
|
|||
const { recoveredAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({ '2': updatedAlerts['2'], '3': updatedAlerts['3'] });
|
||||
|
@ -658,11 +620,10 @@ describe('processAlerts', () => {
|
|||
const { recoveredAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
|
||||
startedAt: '2023-10-03T20:03:08.716Z',
|
||||
});
|
||||
|
||||
|
@ -698,11 +659,9 @@ describe('processAlerts', () => {
|
|||
const { recoveredAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({ '2': updatedAlerts['2'], '3': updatedAlerts['3'] });
|
||||
|
@ -720,33 +679,6 @@ describe('processAlerts', () => {
|
|||
expect(recoveredAlert2State.end).not.toBeDefined();
|
||||
});
|
||||
|
||||
test('considers alert recovered if it was previously recovered and not active', () => {
|
||||
const recoveredAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('1');
|
||||
const recoveredAlert2 = new Alert<AlertInstanceState, AlertInstanceContext>('2');
|
||||
|
||||
const previouslyRecoveredAlerts = {
|
||||
'1': recoveredAlert1,
|
||||
'2': recoveredAlert2,
|
||||
};
|
||||
|
||||
const updatedAlerts = cloneDeep(previouslyRecoveredAlerts);
|
||||
|
||||
updatedAlerts['1'].setFlappingHistory([false]);
|
||||
updatedAlerts['2'].setFlappingHistory([false]);
|
||||
|
||||
const { recoveredAlerts } = processAlerts({
|
||||
alerts: {},
|
||||
existingAlerts: {},
|
||||
previouslyRecoveredAlerts,
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual(updatedAlerts);
|
||||
});
|
||||
|
||||
test('should skip recovery calculations if autoRecoverAlerts = false', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1');
|
||||
const recoveredAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('2');
|
||||
|
@ -767,11 +699,9 @@ describe('processAlerts', () => {
|
|||
const { recoveredAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({});
|
||||
|
@ -813,11 +743,9 @@ describe('processAlerts', () => {
|
|||
const { recoveredAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: true,
|
||||
alertLimit: 7,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({});
|
||||
|
@ -849,11 +777,9 @@ describe('processAlerts', () => {
|
|||
const { activeAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: true,
|
||||
alertLimit: 7,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -909,11 +835,9 @@ describe('processAlerts', () => {
|
|||
const { activeAlerts, newAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: true,
|
||||
alertLimit: MAX_ALERTS,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(Object.keys(activeAlerts).length).toEqual(MAX_ALERTS);
|
||||
|
@ -932,615 +856,4 @@ describe('processAlerts', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updating flappingHistory', () => {
|
||||
test('if new alert, set flapping state to true', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { uuid: 'uuid-1' },
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': activeAlert });
|
||||
alerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts } = processAlerts({
|
||||
alerts,
|
||||
existingAlerts: {},
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
|
||||
test('if alert is still active, set flapping state to false', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': activeAlert });
|
||||
alerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts } = processAlerts({
|
||||
alerts,
|
||||
existingAlerts: alerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
|
||||
test('if alert is active and previously recovered, set flapping state to true', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { uuid: 'uuid-1' },
|
||||
});
|
||||
const recoveredAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-2' },
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': activeAlert });
|
||||
alerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
alerts['1'].setFlappingHistory([false]);
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts } = processAlerts({
|
||||
alerts,
|
||||
existingAlerts: {},
|
||||
previouslyRecoveredAlerts: { '1': recoveredAlert },
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
|
||||
test('if alert is recovered and previously active, set flapping state to true', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
activeAlert.scheduleActions('default' as never, { foo: '1' });
|
||||
const recoveredAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': recoveredAlert });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts } = processAlerts({
|
||||
alerts,
|
||||
existingAlerts: { '1': activeAlert },
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('if alert is still recovered, set flapping state to false', () => {
|
||||
const recoveredAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': recoveredAlert });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts } = processAlerts({
|
||||
alerts: {},
|
||||
existingAlerts: {},
|
||||
previouslyRecoveredAlerts: alerts,
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('if setFlapping is false should not update flappingHistory', () => {
|
||||
const activeAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { uuid: 'uuid-1' },
|
||||
});
|
||||
activeAlert1.scheduleActions('default' as never, { foo: '1' });
|
||||
const activeAlert2 = new Alert<AlertInstanceState, AlertInstanceContext>('2', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-2' },
|
||||
});
|
||||
activeAlert2.scheduleActions('default' as never, { foo: '1' });
|
||||
const recoveredAlert = new Alert<AlertInstanceState, AlertInstanceContext>('3', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-3' },
|
||||
});
|
||||
|
||||
const previouslyRecoveredAlerts = cloneDeep({ '3': recoveredAlert });
|
||||
const alerts = cloneDeep({ '1': activeAlert1, '2': activeAlert2 });
|
||||
const existingAlerts = cloneDeep({ '2': activeAlert2 });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts } = processAlerts({
|
||||
alerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts,
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-3",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
describe('when hasReachedAlertLimit is true', () => {
|
||||
test('if alert is still active, set flapping state to false', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
|
||||
const alerts = cloneDeep({ '1': activeAlert });
|
||||
alerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts } = processAlerts({
|
||||
alerts,
|
||||
existingAlerts: alerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: true,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
|
||||
test('if new alert, set flapping state to true', () => {
|
||||
const activeAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
activeAlert1.scheduleActions('default' as never, { foo: '1' });
|
||||
const activeAlert2 = new Alert<AlertInstanceState, AlertInstanceContext>('2', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-2' },
|
||||
});
|
||||
activeAlert2.scheduleActions('default' as never, { foo: '1' });
|
||||
|
||||
const alerts = cloneDeep({ '1': activeAlert1, '2': activeAlert2 });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts } = processAlerts({
|
||||
alerts,
|
||||
existingAlerts: { '1': activeAlert1 },
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: true,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
|
||||
test('if alert is active and previously recovered, set flapping state to true', () => {
|
||||
const activeAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
activeAlert1.scheduleActions('default' as never, { foo: '1' });
|
||||
const activeAlert2 = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { uuid: 'uuid-2' },
|
||||
});
|
||||
activeAlert2.scheduleActions('default' as never, { foo: '1' });
|
||||
|
||||
const alerts = cloneDeep({ '1': activeAlert1, '2': activeAlert2 });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts } = processAlerts({
|
||||
alerts,
|
||||
existingAlerts: {},
|
||||
previouslyRecoveredAlerts: { '1': activeAlert1 },
|
||||
hasReachedAlertLimit: true,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
|
||||
test('if setFlapping is false should not update flappingHistory', () => {
|
||||
const activeAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false], uuid: 'uuid-1' },
|
||||
});
|
||||
activeAlert1.scheduleActions('default' as never, { foo: '1' });
|
||||
const activeAlert2 = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { uuid: 'uuid-2' },
|
||||
});
|
||||
activeAlert2.scheduleActions('default' as never, { foo: '1' });
|
||||
|
||||
const alerts = cloneDeep({ '1': activeAlert1, '2': activeAlert2 });
|
||||
|
||||
const { activeAlerts, newAlerts, recoveredAlerts } = processAlerts({
|
||||
alerts,
|
||||
existingAlerts: { '1': activeAlert1 },
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: true,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [
|
||||
false,
|
||||
],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-1",
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(newAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flappingHistory": Array [],
|
||||
"maintenanceWindowIds": Array [],
|
||||
"uuid": "uuid-2",
|
||||
},
|
||||
"state": Object {
|
||||
"duration": "0",
|
||||
"start": "1970-01-01T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAlertFlappingHistory function', () => {
|
||||
test('correctly updates flappingHistory', () => {
|
||||
const alert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false, false] },
|
||||
});
|
||||
updateAlertFlappingHistory(DEFAULT_FLAPPING_SETTINGS, alert, true);
|
||||
expect(alert.getFlappingHistory()).toEqual([false, false, true]);
|
||||
});
|
||||
|
||||
test('correctly updates flappingHistory while maintaining a fixed size', () => {
|
||||
const flappingHistory = new Array(20).fill(false);
|
||||
const alert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory },
|
||||
});
|
||||
updateAlertFlappingHistory(DEFAULT_FLAPPING_SETTINGS, alert, true);
|
||||
const fh = alert.getFlappingHistory() || [];
|
||||
expect(fh.length).toEqual(20);
|
||||
const result = new Array(19).fill(false);
|
||||
expect(fh).toEqual(result.concat(true));
|
||||
});
|
||||
|
||||
test('correctly updates flappingHistory while maintaining if array is larger than fixed size', () => {
|
||||
const flappingHistory = new Array(23).fill(false);
|
||||
const alert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory },
|
||||
});
|
||||
updateAlertFlappingHistory(DEFAULT_FLAPPING_SETTINGS, alert, true);
|
||||
const fh = alert.getFlappingHistory() || [];
|
||||
expect(fh.length).toEqual(20);
|
||||
const result = new Array(19).fill(false);
|
||||
expect(fh).toEqual(result.concat(true));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,8 +9,6 @@ import { millisToNanos } from '@kbn/event-log-plugin/server';
|
|||
import { cloneDeep } from 'lodash';
|
||||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../types';
|
||||
import { updateFlappingHistory } from './flapping_utils';
|
||||
import { RulesSettingsFlappingProperties } from '../../common/rules_settings';
|
||||
|
||||
interface ProcessAlertsOpts<
|
||||
State extends AlertInstanceState,
|
||||
|
@ -18,12 +16,10 @@ interface ProcessAlertsOpts<
|
|||
> {
|
||||
alerts: Record<string, Alert<State, Context>>;
|
||||
existingAlerts: Record<string, Alert<State, Context>>;
|
||||
previouslyRecoveredAlerts: Record<string, Alert<State, Context>>;
|
||||
hasReachedAlertLimit: boolean;
|
||||
alertLimit: number;
|
||||
autoRecoverAlerts: boolean;
|
||||
startedAt?: string | null;
|
||||
flappingSettings: RulesSettingsFlappingProperties;
|
||||
}
|
||||
interface ProcessAlertsResult<
|
||||
State extends AlertInstanceState,
|
||||
|
@ -33,8 +29,6 @@ interface ProcessAlertsResult<
|
|||
> {
|
||||
newAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
// recovered alerts in the current rule run that were previously active
|
||||
currentRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
}
|
||||
|
||||
|
@ -46,11 +40,9 @@ export function processAlerts<
|
|||
>({
|
||||
alerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts,
|
||||
hasReachedAlertLimit,
|
||||
alertLimit,
|
||||
autoRecoverAlerts,
|
||||
flappingSettings,
|
||||
startedAt,
|
||||
}: ProcessAlertsOpts<State, Context>): ProcessAlertsResult<
|
||||
State,
|
||||
|
@ -59,22 +51,8 @@ export function processAlerts<
|
|||
RecoveryActionGroupId
|
||||
> {
|
||||
return hasReachedAlertLimit
|
||||
? processAlertsLimitReached(
|
||||
alerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts,
|
||||
alertLimit,
|
||||
flappingSettings,
|
||||
startedAt
|
||||
)
|
||||
: processAlertsHelper(
|
||||
alerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts,
|
||||
autoRecoverAlerts,
|
||||
flappingSettings,
|
||||
startedAt
|
||||
);
|
||||
? processAlertsLimitReached(alerts, existingAlerts, alertLimit, startedAt)
|
||||
: processAlertsHelper(alerts, existingAlerts, autoRecoverAlerts, startedAt);
|
||||
}
|
||||
|
||||
function processAlertsHelper<
|
||||
|
@ -85,18 +63,14 @@ function processAlertsHelper<
|
|||
>(
|
||||
alerts: Record<string, Alert<State, Context>>,
|
||||
existingAlerts: Record<string, Alert<State, Context>>,
|
||||
previouslyRecoveredAlerts: Record<string, Alert<State, Context>>,
|
||||
autoRecoverAlerts: boolean,
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
startedAt?: string | null
|
||||
): ProcessAlertsResult<State, Context, ActionGroupIds, RecoveryActionGroupId> {
|
||||
const existingAlertIds = new Set(Object.keys(existingAlerts));
|
||||
const previouslyRecoveredAlertsIds = new Set(Object.keys(previouslyRecoveredAlerts));
|
||||
|
||||
const currentTime = startedAt ?? new Date().toISOString();
|
||||
const newAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {};
|
||||
const activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {};
|
||||
const currentRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>> = {};
|
||||
const recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>> = {};
|
||||
|
||||
for (const id in alerts) {
|
||||
|
@ -110,15 +84,6 @@ function processAlertsHelper<
|
|||
newAlerts[id] = alerts[id];
|
||||
const state = newAlerts[id].getState();
|
||||
newAlerts[id].replaceState({ ...state, start: currentTime, duration: '0' });
|
||||
|
||||
if (flappingSettings.enabled) {
|
||||
if (previouslyRecoveredAlertsIds.has(id)) {
|
||||
// this alert has flapped from recovered to active
|
||||
newAlerts[id].setFlappingHistory(previouslyRecoveredAlerts[id].getFlappingHistory());
|
||||
previouslyRecoveredAlertsIds.delete(id);
|
||||
}
|
||||
updateAlertFlappingHistory(flappingSettings, newAlerts[id], true);
|
||||
}
|
||||
} else {
|
||||
// this alert did exist in previous run
|
||||
// calculate duration to date for active alerts
|
||||
|
@ -132,15 +97,9 @@ function processAlertsHelper<
|
|||
...(state.start ? { start: state.start } : {}),
|
||||
...(duration !== undefined ? { duration } : {}),
|
||||
});
|
||||
|
||||
// this alert is still active
|
||||
if (flappingSettings.enabled) {
|
||||
updateAlertFlappingHistory(flappingSettings, activeAlerts[id], false);
|
||||
}
|
||||
}
|
||||
} else if (existingAlertIds.has(id) && autoRecoverAlerts) {
|
||||
recoveredAlerts[id] = alerts[id];
|
||||
currentRecoveredAlerts[id] = alerts[id];
|
||||
|
||||
// Inject end time into alert state of recovered alerts
|
||||
const state = recoveredAlerts[id].getState();
|
||||
|
@ -152,23 +111,11 @@ function processAlertsHelper<
|
|||
...(duration ? { duration } : {}),
|
||||
...(state.start ? { end: currentTime } : {}),
|
||||
});
|
||||
// this alert has flapped from active to recovered
|
||||
if (flappingSettings.enabled) {
|
||||
updateAlertFlappingHistory(flappingSettings, recoveredAlerts[id], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// alerts are still recovered
|
||||
for (const id of previouslyRecoveredAlertsIds) {
|
||||
recoveredAlerts[id] = previouslyRecoveredAlerts[id];
|
||||
if (flappingSettings.enabled) {
|
||||
updateAlertFlappingHistory(flappingSettings, recoveredAlerts[id], false);
|
||||
}
|
||||
}
|
||||
|
||||
return { recoveredAlerts, currentRecoveredAlerts, newAlerts, activeAlerts };
|
||||
return { recoveredAlerts, newAlerts, activeAlerts };
|
||||
}
|
||||
|
||||
function processAlertsLimitReached<
|
||||
|
@ -179,13 +126,10 @@ function processAlertsLimitReached<
|
|||
>(
|
||||
alerts: Record<string, Alert<State, Context>>,
|
||||
existingAlerts: Record<string, Alert<State, Context>>,
|
||||
previouslyRecoveredAlerts: Record<string, Alert<State, Context>>,
|
||||
alertLimit: number,
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
startedAt?: string | null
|
||||
): ProcessAlertsResult<State, Context, ActionGroupIds, RecoveryActionGroupId> {
|
||||
const existingAlertIds = new Set(Object.keys(existingAlerts));
|
||||
const previouslyRecoveredAlertsIds = new Set(Object.keys(previouslyRecoveredAlerts));
|
||||
|
||||
// When the alert limit has been reached,
|
||||
// - skip determination of recovered alerts
|
||||
|
@ -215,11 +159,6 @@ function processAlertsLimitReached<
|
|||
...(state.start ? { start: state.start } : {}),
|
||||
...(duration !== undefined ? { duration } : {}),
|
||||
});
|
||||
|
||||
// this alert is still active
|
||||
if (flappingSettings.enabled) {
|
||||
updateAlertFlappingHistory(flappingSettings, activeAlerts[id], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,7 +168,7 @@ function processAlertsLimitReached<
|
|||
|
||||
// if we don't have capacity for new alerts, return
|
||||
if (!hasCapacityForNewAlerts()) {
|
||||
return { recoveredAlerts: {}, currentRecoveredAlerts: {}, newAlerts: {}, activeAlerts };
|
||||
return { recoveredAlerts: {}, newAlerts: {}, activeAlerts };
|
||||
}
|
||||
|
||||
// look for new alerts and add until we hit capacity
|
||||
|
@ -243,37 +182,11 @@ function processAlertsLimitReached<
|
|||
const state = newAlerts[id].getState();
|
||||
newAlerts[id].replaceState({ ...state, start: currentTime, duration: '0' });
|
||||
|
||||
if (flappingSettings.enabled) {
|
||||
if (previouslyRecoveredAlertsIds.has(id)) {
|
||||
// this alert has flapped from recovered to active
|
||||
newAlerts[id].setFlappingHistory(previouslyRecoveredAlerts[id].getFlappingHistory());
|
||||
}
|
||||
updateAlertFlappingHistory(flappingSettings, newAlerts[id], true);
|
||||
}
|
||||
|
||||
if (!hasCapacityForNewAlerts()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { recoveredAlerts: {}, currentRecoveredAlerts: {}, newAlerts, activeAlerts };
|
||||
}
|
||||
|
||||
export function updateAlertFlappingHistory<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
alert: Alert<State, Context, ActionGroupIds | RecoveryActionGroupId>,
|
||||
state: boolean
|
||||
) {
|
||||
const updatedFlappingHistory = updateFlappingHistory(
|
||||
flappingSettings,
|
||||
alert.getFlappingHistory() || [],
|
||||
state
|
||||
);
|
||||
alert.setFlappingHistory(updatedFlappingHistory);
|
||||
return { recoveredAlerts: {}, newAlerts, activeAlerts };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { keys, size } from 'lodash';
|
||||
import { Alert } from '../alert';
|
||||
import { toRawAlertInstances } from './to_raw_alert_instances';
|
||||
|
||||
describe('toRawAlertInstances', () => {
|
||||
const flapping = new Array(16).fill(false).concat([true, true, true, true]);
|
||||
const notFlapping = new Array(20).fill(false);
|
||||
|
||||
describe('toRawAlertInstances', () => {
|
||||
test('should return all active alerts', () => {
|
||||
const activeAlerts = {
|
||||
'1': new Alert('1', { meta: { flappingHistory: flapping } }),
|
||||
'2': new Alert('2', { meta: { flappingHistory: [false, false] } }),
|
||||
};
|
||||
const { rawActiveAlerts } = toRawAlertInstances(activeAlerts, {});
|
||||
expect(size(rawActiveAlerts)).toEqual(2);
|
||||
});
|
||||
|
||||
test('should return all recovered alerts', () => {
|
||||
const recoveredAlerts = {
|
||||
'1': new Alert('1', { meta: { flappingHistory: flapping } }),
|
||||
'2': new Alert('2', { meta: { flappingHistory: notFlapping } }),
|
||||
};
|
||||
const { rawRecoveredAlerts } = toRawAlertInstances({}, recoveredAlerts);
|
||||
expect(keys(rawRecoveredAlerts)).toEqual(['1', '2']);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { keys } from 'lodash';
|
||||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext, RawAlertInstance } from '../types';
|
||||
|
||||
export function toRawAlertInstances<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>(
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupId>> = {}
|
||||
): {
|
||||
rawActiveAlerts: Record<string, RawAlertInstance>;
|
||||
rawRecoveredAlerts: Record<string, RawAlertInstance>;
|
||||
} {
|
||||
const rawActiveAlerts: Record<string, RawAlertInstance> = {};
|
||||
const rawRecoveredAlerts: Record<string, RawAlertInstance> = {};
|
||||
|
||||
for (const id of keys(activeAlerts)) {
|
||||
rawActiveAlerts[id] = activeAlerts[id].toRaw();
|
||||
}
|
||||
|
||||
for (const id of keys(recoveredAlerts)) {
|
||||
const alert = recoveredAlerts[id];
|
||||
rawRecoveredAlerts[id] = alert.toRaw(true);
|
||||
}
|
||||
return { rawActiveAlerts, rawRecoveredAlerts };
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { Alert } from '../alert';
|
||||
import { getEarlyRecoveredAlerts, trimRecoveredAlerts } from './trim_recovered_alerts';
|
||||
|
||||
describe('trimRecoveredAlerts', () => {
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
|
||||
const alert1 = new Alert('1', { meta: { flappingHistory: [true, true, true, true] } });
|
||||
const alert2 = new Alert('2', { meta: { flappingHistory: new Array(20).fill(false) } });
|
||||
const alert3 = new Alert('3', { meta: { flappingHistory: [true, true] } });
|
||||
const alert4 = {
|
||||
key: '4',
|
||||
flappingHistory: [true, true, true, true],
|
||||
};
|
||||
const alert5 = {
|
||||
key: '5',
|
||||
flappingHistory: new Array(20).fill(false),
|
||||
};
|
||||
const alert6 = {
|
||||
key: '6',
|
||||
flappingHistory: [true, true],
|
||||
};
|
||||
|
||||
test('should remove longest recovered alerts', () => {
|
||||
const recoveredAlerts = {
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
};
|
||||
|
||||
const trimmedAlerts = trimRecoveredAlerts(logger, recoveredAlerts, 2);
|
||||
expect(trimmedAlerts).toEqual({
|
||||
trimmedAlertsRecovered: { 1: alert1, 3: alert3 },
|
||||
earlyRecoveredAlerts: {
|
||||
2: new Alert('2', {
|
||||
meta: {
|
||||
flappingHistory: new Array(20).fill(false),
|
||||
flapping: false,
|
||||
uuid: expect.any(String),
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should not remove alerts if the num of recovered alerts is not at the limit', () => {
|
||||
const recoveredAlerts = {
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
};
|
||||
|
||||
const trimmedAlerts = trimRecoveredAlerts(logger, recoveredAlerts, 3);
|
||||
expect(trimmedAlerts).toEqual({
|
||||
trimmedAlertsRecovered: recoveredAlerts,
|
||||
earlyRecoveredAlerts: {},
|
||||
});
|
||||
});
|
||||
|
||||
test('getEarlyRecoveredAlerts should return longest recovered alerts', () => {
|
||||
const recoveredAlerts = [alert4, alert5, alert6];
|
||||
const trimmedAlerts = getEarlyRecoveredAlerts(logger, recoveredAlerts, 2);
|
||||
expect(trimmedAlerts).toEqual([alert5]);
|
||||
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
'Recovered alerts have exceeded the max alert limit of 2 : dropping 1 alert.'
|
||||
);
|
||||
});
|
||||
|
||||
test('getEarlyRecoveredAlerts should not return alerts if the num of recovered alerts is not at the limit', () => {
|
||||
const recoveredAlerts = [alert4, alert5];
|
||||
const trimmedAlerts = getEarlyRecoveredAlerts(logger, recoveredAlerts, 2);
|
||||
expect(trimmedAlerts).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { map } from 'lodash';
|
||||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../types';
|
||||
|
||||
interface TrimmedRecoveredAlertsResult<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
RecoveryActionGroupIds extends string
|
||||
> {
|
||||
trimmedAlertsRecovered: Record<string, Alert<State, Context, RecoveryActionGroupIds>>;
|
||||
earlyRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupIds>>;
|
||||
}
|
||||
|
||||
export interface TrimRecoveredOpts {
|
||||
key: string;
|
||||
flappingHistory: boolean[];
|
||||
}
|
||||
|
||||
export function trimRecoveredAlerts<
|
||||
State extends AlertInstanceState,
|
||||
Context extends AlertInstanceContext,
|
||||
RecoveryActionGroupIds extends string
|
||||
>(
|
||||
logger: Logger,
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupIds>> = {},
|
||||
maxAlerts: number
|
||||
): TrimmedRecoveredAlertsResult<State, Context, RecoveryActionGroupIds> {
|
||||
const alerts = map(recoveredAlerts, (value, key) => {
|
||||
return {
|
||||
key,
|
||||
flappingHistory: value.getFlappingHistory() || [],
|
||||
};
|
||||
});
|
||||
const earlyRecoveredAlertOpts = getEarlyRecoveredAlerts(logger, alerts, maxAlerts);
|
||||
const earlyRecoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupIds>> = {};
|
||||
earlyRecoveredAlertOpts.forEach((opt) => {
|
||||
const alert = recoveredAlerts[opt.key];
|
||||
alert.setFlapping(false);
|
||||
earlyRecoveredAlerts[opt.key] = recoveredAlerts[opt.key];
|
||||
|
||||
delete recoveredAlerts[opt.key];
|
||||
});
|
||||
return {
|
||||
trimmedAlertsRecovered: recoveredAlerts,
|
||||
earlyRecoveredAlerts,
|
||||
};
|
||||
}
|
||||
|
||||
export function getEarlyRecoveredAlerts(
|
||||
logger: Logger,
|
||||
recoveredAlerts: TrimRecoveredOpts[],
|
||||
maxAlerts: number
|
||||
) {
|
||||
let earlyRecoveredAlerts: TrimRecoveredOpts[] = [];
|
||||
if (recoveredAlerts.length > maxAlerts) {
|
||||
recoveredAlerts.sort((a, b) => {
|
||||
return a.flappingHistory.length - b.flappingHistory.length;
|
||||
});
|
||||
|
||||
earlyRecoveredAlerts = recoveredAlerts.slice(maxAlerts);
|
||||
logger.warn(
|
||||
`Recovered alerts have exceeded the max alert limit of ${maxAlerts} : dropping ${
|
||||
earlyRecoveredAlerts.length
|
||||
} ${earlyRecoveredAlerts.length > 1 ? 'alerts' : 'alert'}.`
|
||||
);
|
||||
}
|
||||
return earlyRecoveredAlerts;
|
||||
}
|
|
@ -109,7 +109,7 @@ describe('Action Scheduler', () => {
|
|||
test('schedules execution per selected action', async () => {
|
||||
const alerts = generateAlert({ id: 1 });
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext());
|
||||
await actionScheduler.run({ activeCurrentAlerts: alerts, recoveredCurrentAlerts: {} });
|
||||
await actionScheduler.run({ activeAlerts: alerts, recoveredAlerts: {} });
|
||||
|
||||
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1);
|
||||
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1);
|
||||
|
@ -212,8 +212,8 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1);
|
||||
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(2);
|
||||
|
@ -280,8 +280,8 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 2 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 2 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0);
|
||||
|
@ -295,8 +295,8 @@ describe('Action Scheduler', () => {
|
|||
});
|
||||
|
||||
await actionSchedulerForPreconfiguredAction.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 2 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 2 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
@ -338,8 +338,8 @@ describe('Action Scheduler', () => {
|
|||
|
||||
try {
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(getErrorSource(err)).toBe(TaskErrorSource.USER);
|
||||
|
@ -349,8 +349,8 @@ describe('Action Scheduler', () => {
|
|||
test('limits actionsPlugin.execute per action group', async () => {
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext());
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 2, group: 'other-group' }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 2, group: 'other-group' }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0);
|
||||
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(0);
|
||||
|
@ -360,8 +360,8 @@ describe('Action Scheduler', () => {
|
|||
test('context attribute gets parameterized', async () => {
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext());
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 2, context: { value: 'context-val' } }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 2, context: { value: 'context-val' } }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1);
|
||||
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1);
|
||||
|
@ -409,8 +409,8 @@ describe('Action Scheduler', () => {
|
|||
test('state attribute gets parameterized', async () => {
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext());
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
|
||||
expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
|
@ -456,11 +456,11 @@ describe('Action Scheduler', () => {
|
|||
test(`logs an error when action group isn't part of actionGroups available for the ruleType`, async () => {
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext());
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({
|
||||
activeAlerts: generateAlert({
|
||||
id: 2,
|
||||
group: 'invalid-group' as 'default' | 'other-group',
|
||||
}),
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(defaultSchedulerContext.logger.error).toHaveBeenCalledWith(
|
||||
|
@ -540,8 +540,8 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(2);
|
||||
|
@ -644,8 +644,8 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(4);
|
||||
|
@ -731,8 +731,8 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 2, state: { value: 'state-val' } }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(2);
|
||||
|
@ -768,8 +768,8 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {},
|
||||
recoveredCurrentAlerts: generateRecoveredAlert({ id: 1 }),
|
||||
activeAlerts: {},
|
||||
recoveredAlerts: generateRecoveredAlert({ id: 1 }),
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
|
||||
|
@ -838,8 +838,8 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {},
|
||||
recoveredCurrentAlerts: generateRecoveredAlert({ id: 1 }),
|
||||
activeAlerts: {},
|
||||
recoveredAlerts: generateRecoveredAlert({ id: 1 }),
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(0);
|
||||
|
@ -861,8 +861,8 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
clock.tick(30000);
|
||||
|
@ -894,11 +894,11 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({
|
||||
activeAlerts: generateAlert({
|
||||
id: 1,
|
||||
throttledActions: { '111-111': { date: new Date(DATE_1970).toISOString() } },
|
||||
}),
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
clock.tick(30000);
|
||||
|
@ -930,8 +930,8 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1, lastScheduledActionsGroup: 'recovered' }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1, lastScheduledActionsGroup: 'recovered' }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
clock.tick(30000);
|
||||
|
@ -951,8 +951,8 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(0);
|
||||
|
@ -1009,8 +1009,8 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({
|
||||
|
@ -1095,8 +1095,8 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {},
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
|
||||
|
@ -1150,8 +1150,8 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
const result = await actionScheduler.run({
|
||||
activeCurrentAlerts: {},
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({
|
||||
|
@ -1251,8 +1251,8 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {},
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(defaultSchedulerContext.logger.debug).toHaveBeenCalledTimes(1);
|
||||
expect(defaultSchedulerContext.logger.debug).toHaveBeenCalledWith(
|
||||
|
@ -1316,8 +1316,8 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
const result = await actionScheduler.run({
|
||||
activeCurrentAlerts: {},
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(result).toEqual({
|
||||
throttledSummaryActions: {
|
||||
|
@ -1354,8 +1354,8 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 2 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 2 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(defaultSchedulerContext.logger.error).toHaveBeenCalledWith(
|
||||
|
@ -1418,8 +1418,8 @@ describe('Action Scheduler', () => {
|
|||
})
|
||||
);
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {},
|
||||
recoveredCurrentAlerts: generateRecoveredAlert({ id: 1 }),
|
||||
activeAlerts: {},
|
||||
recoveredAlerts: generateRecoveredAlert({ id: 1 }),
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
|
||||
|
@ -1547,11 +1547,11 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {
|
||||
activeAlerts: {
|
||||
...generateAlert({ id: 1 }),
|
||||
...generateAlert({ id: 2 }),
|
||||
},
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({
|
||||
|
@ -1624,11 +1624,11 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {
|
||||
activeAlerts: {
|
||||
...generateAlert({ id: 1 }),
|
||||
...generateAlert({ id: 2 }),
|
||||
},
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({
|
||||
|
@ -1695,12 +1695,12 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {
|
||||
activeAlerts: {
|
||||
...generateAlert({ id: 1 }),
|
||||
...generateAlert({ id: 2 }),
|
||||
...generateAlert({ id: 3 }),
|
||||
},
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({
|
||||
|
@ -1807,12 +1807,12 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {
|
||||
activeAlerts: {
|
||||
...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }),
|
||||
...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }),
|
||||
...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }),
|
||||
},
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
|
||||
|
@ -1859,12 +1859,12 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {
|
||||
activeAlerts: {
|
||||
...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }),
|
||||
...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }),
|
||||
...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }),
|
||||
},
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
|
||||
|
@ -1880,12 +1880,12 @@ describe('Action Scheduler', () => {
|
|||
const actionScheduler = new ActionScheduler(getSchedulerContext());
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {
|
||||
activeAlerts: {
|
||||
...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }),
|
||||
...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }),
|
||||
...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }),
|
||||
},
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
|
||||
|
@ -1923,7 +1923,7 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {
|
||||
activeAlerts: {
|
||||
...generateAlert({
|
||||
id: 1,
|
||||
pendingRecoveredCount: 1,
|
||||
|
@ -1940,7 +1940,7 @@ describe('Action Scheduler', () => {
|
|||
lastScheduledActionsGroup: 'recovered',
|
||||
}),
|
||||
},
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
|
||||
|
@ -1967,7 +1967,7 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {
|
||||
activeAlerts: {
|
||||
...generateAlert({
|
||||
id: 1,
|
||||
pendingRecoveredCount: 1,
|
||||
|
@ -1984,7 +1984,7 @@ describe('Action Scheduler', () => {
|
|||
lastScheduledActionsGroup: 'recovered',
|
||||
}),
|
||||
},
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
|
||||
|
@ -2114,7 +2114,7 @@ describe('Action Scheduler', () => {
|
|||
);
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: {
|
||||
activeAlerts: {
|
||||
...generateAlert({
|
||||
id: 1,
|
||||
pendingRecoveredCount: 1,
|
||||
|
@ -2131,7 +2131,7 @@ describe('Action Scheduler', () => {
|
|||
lastScheduledActionsGroup: 'recovered',
|
||||
}),
|
||||
},
|
||||
recoveredCurrentAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
|
||||
|
@ -2286,8 +2286,8 @@ describe('Action Scheduler', () => {
|
|||
const actionScheduler = new ActionScheduler(getSchedulerContext(execParams));
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
|
@ -2322,8 +2322,8 @@ describe('Action Scheduler', () => {
|
|||
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext(execParams));
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(injectActionParamsMock.mock.calls[0][0].actionParams).toEqual({
|
||||
|
@ -2365,8 +2365,8 @@ describe('Action Scheduler', () => {
|
|||
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext(execParams));
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -2403,8 +2403,8 @@ describe('Action Scheduler', () => {
|
|||
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext(execParams));
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -2438,8 +2438,8 @@ describe('Action Scheduler', () => {
|
|||
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext(execParams));
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -2473,8 +2473,8 @@ describe('Action Scheduler', () => {
|
|||
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext(execParams));
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -2505,8 +2505,8 @@ describe('Action Scheduler', () => {
|
|||
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext(execParams));
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -2537,8 +2537,8 @@ describe('Action Scheduler', () => {
|
|||
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext(execParams));
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -2572,8 +2572,8 @@ describe('Action Scheduler', () => {
|
|||
|
||||
const actionScheduler = new ActionScheduler(getSchedulerContext(execParams));
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
@ -2643,8 +2643,8 @@ describe('Action Scheduler', () => {
|
|||
const actionScheduler = new ActionScheduler(getSchedulerContext(executorParams));
|
||||
|
||||
const res = await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
/**
|
||||
* Verifies that system actions are not throttled
|
||||
|
@ -2770,8 +2770,8 @@ describe('Action Scheduler', () => {
|
|||
const actionScheduler = new ActionScheduler(getSchedulerContext(executorParams));
|
||||
|
||||
const res = await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -2830,8 +2830,8 @@ describe('Action Scheduler', () => {
|
|||
const actionScheduler = new ActionScheduler(getSchedulerContext(executorParams));
|
||||
|
||||
const res = await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(res).toEqual({ throttledSummaryActions: {} });
|
||||
|
@ -2872,8 +2872,8 @@ describe('Action Scheduler', () => {
|
|||
const actionScheduler = new ActionScheduler(getSchedulerContext(executorParams));
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: generateAlert({ id: 1 }),
|
||||
recoveredCurrentAlerts: {},
|
||||
activeAlerts: generateAlert({ id: 1 }),
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
|
|
@ -75,11 +75,11 @@ export class ActionScheduler<
|
|||
}
|
||||
|
||||
public async run({
|
||||
activeCurrentAlerts,
|
||||
recoveredCurrentAlerts,
|
||||
activeAlerts,
|
||||
recoveredAlerts,
|
||||
}: {
|
||||
activeCurrentAlerts?: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
recoveredCurrentAlerts?: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
activeAlerts?: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
recoveredAlerts?: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
}): Promise<RunResult> {
|
||||
const throttledSummaryActions: ThrottledActions = getSummaryActionsFromTaskState({
|
||||
actions: this.context.rule.actions,
|
||||
|
@ -90,8 +90,8 @@ export class ActionScheduler<
|
|||
for (const scheduler of this.schedulers) {
|
||||
allActionsToScheduleResult.push(
|
||||
...(await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts,
|
||||
recoveredCurrentAlerts,
|
||||
activeAlerts,
|
||||
recoveredAlerts,
|
||||
throttledSummaryActions,
|
||||
}))
|
||||
);
|
||||
|
|
|
@ -223,7 +223,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
// 2 per-alert actions * 2 alerts = 4 actions to schedule
|
||||
const scheduler = new PerAlertActionScheduler(getSchedulerContext());
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -252,7 +252,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
priority: TaskPriority.Low,
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -281,7 +281,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
apiKeyId: '23534ybfsdsnsdf',
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -313,7 +313,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
});
|
||||
const alertsWithMaintenanceWindow = { ...newAlertWithMaintenanceWindow, ...newAlert2 };
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alertsWithMaintenanceWindow,
|
||||
activeAlerts: alertsWithMaintenanceWindow,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -352,7 +352,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
});
|
||||
const alertsWithInvalidActionGroup = { ...newAlertInvalidActionGroup, ...newAlert2 };
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alertsWithInvalidActionGroup,
|
||||
activeAlerts: alertsWithInvalidActionGroup,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -390,7 +390,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
});
|
||||
const alertsWithInvalidActionGroup = { ...newAlertInvalidActionGroup, ...newAlert2 };
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alertsWithInvalidActionGroup,
|
||||
activeAlerts: alertsWithInvalidActionGroup,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -422,7 +422,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
...newAlert2,
|
||||
};
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alertsWithPendingRecoveredCount,
|
||||
activeAlerts: alertsWithPendingRecoveredCount,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -468,7 +468,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
...newAlert2,
|
||||
};
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alertsWithPendingRecoveredCount,
|
||||
activeAlerts: alertsWithPendingRecoveredCount,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -495,7 +495,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
rule: { ...rule, mutedInstanceIds: ['2'] },
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -556,7 +556,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
});
|
||||
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alertsWithOngoingAlert,
|
||||
activeAlerts: alertsWithOngoingAlert,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -613,7 +613,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
});
|
||||
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alertsWithOngoingAlert,
|
||||
activeAlerts: alertsWithOngoingAlert,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -670,7 +670,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
});
|
||||
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alertsWithOngoingAlert,
|
||||
activeAlerts: alertsWithOngoingAlert,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -729,7 +729,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
rule: { ...rule, actions: [rule.actions[0], actionWithUseAlertDataForTemplate] },
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1);
|
||||
|
@ -790,7 +790,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
rule: { ...rule, actions: [rule.actions[0], actionWithUseAlertDataForTemplate] },
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1);
|
||||
|
@ -852,7 +852,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
rule: { ...rule, actions: [rule.actions[0], actionWithAlertsFilter] },
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1);
|
||||
|
@ -914,7 +914,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
rule: { ...rule, actions: [rule.actions[0], actionWithAlertsFilter] },
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1);
|
||||
|
@ -977,7 +977,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
rule: { ...rule, actions: [rule.actions[0], actionWithAlertsFilter] },
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1);
|
||||
|
@ -1038,7 +1038,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
rule: { ...rule, actions: [rule.actions[0], actionWithAlertsFilter] },
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledTimes(1);
|
||||
|
@ -1081,7 +1081,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
},
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -1119,7 +1119,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
},
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
});
|
||||
|
||||
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
|
||||
|
@ -1154,7 +1154,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
expect(alert.getLastScheduledActions()).toBeUndefined();
|
||||
expect(alert.hasScheduledActions()).toBe(true);
|
||||
await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: { '1': alert },
|
||||
activeAlerts: { '1': alert },
|
||||
});
|
||||
|
||||
expect(alert.getLastScheduledActions()).toEqual({
|
||||
|
@ -1193,7 +1193,7 @@ describe('Per-Alert Action Scheduler', () => {
|
|||
});
|
||||
|
||||
await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: { '1': alert },
|
||||
activeAlerts: { '1': alert },
|
||||
});
|
||||
|
||||
expect(alert.getLastScheduledActions()).toEqual({
|
||||
|
|
|
@ -100,8 +100,8 @@ export class PerAlertActionScheduler<
|
|||
}
|
||||
|
||||
public async getActionsToSchedule({
|
||||
activeCurrentAlerts,
|
||||
recoveredCurrentAlerts,
|
||||
activeAlerts,
|
||||
recoveredAlerts,
|
||||
}: GetActionsToScheduleOpts<State, Context, ActionGroupIds, RecoveryActionGroupId>): Promise<
|
||||
ActionsToSchedule[]
|
||||
> {
|
||||
|
@ -111,8 +111,8 @@ export class PerAlertActionScheduler<
|
|||
}> = [];
|
||||
const results: ActionsToSchedule[] = [];
|
||||
|
||||
const activeCurrentAlertsArray = Object.values(activeCurrentAlerts || {});
|
||||
const recoveredCurrentAlertsArray = Object.values(recoveredCurrentAlerts || {});
|
||||
const activeAlertsArray = Object.values(activeAlerts || {});
|
||||
const recoveredAlertsArray = Object.values(recoveredAlerts || {});
|
||||
|
||||
for (const action of this.actions) {
|
||||
let summarizedAlerts = null;
|
||||
|
@ -140,13 +140,13 @@ export class PerAlertActionScheduler<
|
|||
|
||||
logNumberOfFilteredAlerts({
|
||||
logger: this.context.logger,
|
||||
numberOfAlerts: activeCurrentAlertsArray.length + recoveredCurrentAlertsArray.length,
|
||||
numberOfAlerts: activeAlertsArray.length + recoveredAlertsArray.length,
|
||||
numberOfSummarizedAlerts: summarizedAlerts.all.count,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
for (const alert of activeCurrentAlertsArray) {
|
||||
for (const alert of activeAlertsArray) {
|
||||
if (
|
||||
this.isExecutableAlert({ alert, action, summarizedAlerts }) &&
|
||||
this.isExecutableActiveAlert({ alert, action })
|
||||
|
@ -157,7 +157,7 @@ export class PerAlertActionScheduler<
|
|||
}
|
||||
|
||||
if (this.isRecoveredAction(action.group)) {
|
||||
for (const alert of recoveredCurrentAlertsArray) {
|
||||
for (const alert of recoveredAlertsArray) {
|
||||
if (this.isExecutableAlert({ alert, action, summarizedAlerts })) {
|
||||
this.addSummarizedAlerts({ alert, summarizedAlerts });
|
||||
executables.push({ action, alert });
|
||||
|
|
|
@ -234,7 +234,7 @@ describe('Summary Action Scheduler', () => {
|
|||
const throttledSummaryActions = {};
|
||||
const scheduler = new SummaryActionScheduler(getSchedulerContext());
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions,
|
||||
});
|
||||
|
||||
|
@ -285,7 +285,7 @@ describe('Summary Action Scheduler', () => {
|
|||
priority: TaskPriority.Low,
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions,
|
||||
});
|
||||
|
||||
|
@ -336,7 +336,7 @@ describe('Summary Action Scheduler', () => {
|
|||
apiKeyId: '24534wy3wydfbs',
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions,
|
||||
});
|
||||
|
||||
|
@ -388,7 +388,7 @@ describe('Summary Action Scheduler', () => {
|
|||
|
||||
const throttledSummaryActions = {};
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions,
|
||||
});
|
||||
|
||||
|
@ -432,7 +432,7 @@ describe('Summary Action Scheduler', () => {
|
|||
|
||||
const throttledSummaryActions = {};
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions,
|
||||
});
|
||||
|
||||
|
@ -468,7 +468,7 @@ describe('Summary Action Scheduler', () => {
|
|||
|
||||
const throttledSummaryActions = { '444-444': { date: '1969-12-31T13:00:00.000Z' } };
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions,
|
||||
});
|
||||
|
||||
|
@ -505,7 +505,7 @@ describe('Summary Action Scheduler', () => {
|
|||
|
||||
const throttledSummaryActions = {};
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions,
|
||||
});
|
||||
|
||||
|
@ -570,8 +570,8 @@ describe('Summary Action Scheduler', () => {
|
|||
|
||||
const throttledSummaryActions = {};
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
recoveredCurrentAlerts: recoveredAlert,
|
||||
activeAlerts: alerts,
|
||||
recoveredAlerts: recoveredAlert,
|
||||
throttledSummaryActions,
|
||||
});
|
||||
|
||||
|
@ -618,7 +618,7 @@ describe('Summary Action Scheduler', () => {
|
|||
|
||||
const throttledSummaryActions = {};
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions,
|
||||
});
|
||||
|
||||
|
@ -648,7 +648,7 @@ describe('Summary Action Scheduler', () => {
|
|||
|
||||
try {
|
||||
await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions: {},
|
||||
});
|
||||
} catch (err) {
|
||||
|
@ -677,7 +677,7 @@ describe('Summary Action Scheduler', () => {
|
|||
},
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions: {},
|
||||
});
|
||||
|
||||
|
@ -734,7 +734,7 @@ describe('Summary Action Scheduler', () => {
|
|||
},
|
||||
});
|
||||
const results = await scheduler.getActionsToSchedule({
|
||||
activeCurrentAlerts: alerts,
|
||||
activeAlerts: alerts,
|
||||
throttledSummaryActions: {},
|
||||
});
|
||||
|
||||
|
|
|
@ -81,13 +81,13 @@ export class SummaryActionScheduler<
|
|||
}
|
||||
|
||||
public async getActionsToSchedule({
|
||||
activeCurrentAlerts,
|
||||
recoveredCurrentAlerts,
|
||||
activeAlerts,
|
||||
recoveredAlerts,
|
||||
throttledSummaryActions,
|
||||
}: GetActionsToScheduleOpts<State, Context, ActionGroupIds, RecoveryActionGroupId>): Promise<
|
||||
ActionsToSchedule[]
|
||||
> {
|
||||
const alerts = { ...activeCurrentAlerts, ...recoveredCurrentAlerts };
|
||||
const alerts = { ...activeAlerts, ...recoveredAlerts };
|
||||
const executables: Array<{
|
||||
action: RuleAction;
|
||||
summarizedAlerts: CombinedSummarizedAlerts;
|
||||
|
|
|
@ -97,8 +97,8 @@ export interface GetActionsToScheduleOpts<
|
|||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
> {
|
||||
activeCurrentAlerts?: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
recoveredCurrentAlerts?: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
activeAlerts?: Record<string, Alert<State, Context, ActionGroupIds>>;
|
||||
recoveredAlerts?: Record<string, Alert<State, Context, RecoveryActionGroupId>>;
|
||||
throttledSummaryActions?: ThrottledActions;
|
||||
}
|
||||
|
||||
|
|
|
@ -285,8 +285,8 @@ export class AdHocTaskRunner implements CancellableTask {
|
|||
});
|
||||
|
||||
await actionScheduler.run({
|
||||
activeCurrentAlerts: alertsClient.getProcessedAlerts('activeCurrent'),
|
||||
recoveredCurrentAlerts: alertsClient.getProcessedAlerts('recoveredCurrent'),
|
||||
activeAlerts: alertsClient.getProcessedAlerts('active'),
|
||||
recoveredAlerts: alertsClient.getProcessedAlerts('recovered'),
|
||||
});
|
||||
|
||||
return ruleRunMetricsStore.getMetrics();
|
||||
|
|
|
@ -269,8 +269,9 @@ describe('RuleTypeRunner', () => {
|
|||
`rule executed: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`
|
||||
);
|
||||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineFlappingAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineDelayedAlerts).toHaveBeenCalledWith({
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
@ -377,8 +378,9 @@ describe('RuleTypeRunner', () => {
|
|||
`rule executed: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`
|
||||
);
|
||||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineFlappingAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineDelayedAlerts).toHaveBeenCalledWith({
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
@ -439,8 +441,9 @@ describe('RuleTypeRunner', () => {
|
|||
`rule executed: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`
|
||||
);
|
||||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineFlappingAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineDelayedAlerts).toHaveBeenCalledWith({
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
@ -553,6 +556,8 @@ describe('RuleTypeRunner', () => {
|
|||
);
|
||||
expect(ruleRunMetricsStore.setSearchMetrics).not.toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.determineFlappingAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.determineDelayedAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.persistAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.logAlerts).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -657,6 +662,8 @@ describe('RuleTypeRunner', () => {
|
|||
);
|
||||
expect(ruleRunMetricsStore.setSearchMetrics).not.toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.determineFlappingAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.determineDelayedAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.persistAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.logAlerts).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -800,8 +807,9 @@ describe('RuleTypeRunner', () => {
|
|||
`rule executed: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`
|
||||
);
|
||||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineFlappingAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineDelayedAlerts).toHaveBeenCalledWith({
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
@ -915,8 +923,9 @@ describe('RuleTypeRunner', () => {
|
|||
`rule executed: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`
|
||||
);
|
||||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineFlappingAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineDelayedAlerts).toHaveBeenCalledWith({
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
@ -1024,11 +1033,9 @@ describe('RuleTypeRunner', () => {
|
|||
`rule executed: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`
|
||||
);
|
||||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineFlappingAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.determineDelayedAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.persistAlerts).not.toHaveBeenCalled();
|
||||
expect(alertsClient.logAlerts).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -1130,8 +1137,9 @@ describe('RuleTypeRunner', () => {
|
|||
`rule executed: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`
|
||||
);
|
||||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineFlappingAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineDelayedAlerts).toHaveBeenCalledWith({
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
@ -1236,8 +1244,9 @@ describe('RuleTypeRunner', () => {
|
|||
`rule executed: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`
|
||||
);
|
||||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineFlappingAlerts).toHaveBeenCalledWith();
|
||||
expect(alertsClient.determineDelayedAlerts).toHaveBeenCalledWith({
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
|
|
@ -338,8 +338,9 @@ export class RuleTypeRunner<
|
|||
|
||||
await withAlertingSpan('alerting:process-alerts', () =>
|
||||
this.options.timer.runWithTimer(TaskRunnerTimerSpan.ProcessAlerts, async () => {
|
||||
await alertsClient.processAlerts({
|
||||
flappingSettings: context.flappingSettings ?? DEFAULT_FLAPPING_SETTINGS,
|
||||
await alertsClient.processAlerts();
|
||||
alertsClient.determineFlappingAlerts();
|
||||
alertsClient.determineDelayedAlerts({
|
||||
alertDelay: alertDelay?.active ?? 0,
|
||||
ruleRunMetricsStore: context.ruleRunMetricsStore,
|
||||
});
|
||||
|
|
|
@ -1530,6 +1530,9 @@ describe('Task Runner', () => {
|
|||
{ tags: ['1', 'test'] }
|
||||
);
|
||||
|
||||
alertsClient.getRawAlertInstancesForState.mockResolvedValueOnce({ state: {}, meta: {} });
|
||||
alertsService.createAlertsClient.mockImplementation(() => alertsClient);
|
||||
|
||||
testAlertingEventLogCalls({
|
||||
activeAlerts: 1,
|
||||
recoveredAlerts: 1,
|
||||
|
@ -1754,7 +1757,7 @@ describe('Task Runner', () => {
|
|||
recovered: { count: 0, data: [] },
|
||||
});
|
||||
|
||||
alertsClient.getAlertsToSerialize.mockResolvedValueOnce({ state: {}, meta: {} });
|
||||
alertsClient.getRawAlertInstancesForState.mockResolvedValueOnce({ state: {}, meta: {} });
|
||||
alertsService.createAlertsClient.mockImplementation(() => alertsClient);
|
||||
|
||||
const taskRunner = new TaskRunner({
|
||||
|
|
|
@ -417,8 +417,8 @@ export class TaskRunner<
|
|||
this.countUsageOfActionExecutionAfterRuleCancellation();
|
||||
} else {
|
||||
actionSchedulerResult = await actionScheduler.run({
|
||||
activeCurrentAlerts: alertsClient.getProcessedAlerts('activeCurrent'),
|
||||
recoveredCurrentAlerts: alertsClient.getProcessedAlerts('recoveredCurrent'),
|
||||
activeAlerts: alertsClient.getProcessedAlerts('active'),
|
||||
recoveredAlerts: alertsClient.getProcessedAlerts('recovered'),
|
||||
});
|
||||
}
|
||||
})
|
||||
|
@ -430,10 +430,9 @@ export class TaskRunner<
|
|||
// Only serialize alerts into task state if we're auto-recovering, otherwise
|
||||
// we don't need to keep this information around.
|
||||
if (this.ruleType.autoRecoverAlerts) {
|
||||
const { alertsToReturn: alerts, recoveredAlertsToReturn: recovered } =
|
||||
alertsClient.getAlertsToSerialize();
|
||||
alertsToReturn = alerts;
|
||||
recoveredAlertsToReturn = recovered;
|
||||
const alerts = alertsClient.getRawAlertInstancesForState();
|
||||
alertsToReturn = alerts.rawActiveAlerts;
|
||||
recoveredAlertsToReturn = alerts.rawRecoveredAlerts;
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -297,9 +297,9 @@ describe('Task Runner', () => {
|
|||
.spyOn(RuleRunMetricsStoreModule, 'RuleRunMetricsStore')
|
||||
.mockImplementation(() => ruleRunMetricsStore);
|
||||
mockAlertsService.createAlertsClient.mockImplementation(() => mockAlertsClient);
|
||||
mockAlertsClient.getAlertsToSerialize.mockResolvedValue({
|
||||
alertsToReturn: {},
|
||||
recoveredAlertsToReturn: {},
|
||||
mockAlertsClient.getRawAlertInstancesForState.mockResolvedValue({
|
||||
rawActiveAlerts: {},
|
||||
rawRecoveredAlerts: {},
|
||||
});
|
||||
ruleRunMetricsStore.getMetrics.mockReturnValue({
|
||||
numSearches: 3,
|
||||
|
@ -656,9 +656,9 @@ describe('Task Runner', () => {
|
|||
mockAlertsService.createAlertsClient.mockImplementation(() => {
|
||||
throw new Error('Could not initialize!');
|
||||
});
|
||||
mockLegacyAlertsClient.getAlertsToSerialize.mockResolvedValue({
|
||||
alertsToReturn: {},
|
||||
recoveredAlertsToReturn: {},
|
||||
mockLegacyAlertsClient.getRawAlertInstancesForState.mockResolvedValue({
|
||||
rawActiveAlerts: {},
|
||||
rawRecoveredAlerts: {},
|
||||
});
|
||||
ruleRunMetricsStore.getMetrics.mockReturnValue({
|
||||
numSearches: 3,
|
||||
|
@ -750,9 +750,9 @@ describe('Task Runner', () => {
|
|||
const spy2 = jest
|
||||
.spyOn(RuleRunMetricsStoreModule, 'RuleRunMetricsStore')
|
||||
.mockImplementation(() => ruleRunMetricsStore);
|
||||
mockLegacyAlertsClient.getAlertsToSerialize.mockResolvedValue({
|
||||
alertsToReturn: {},
|
||||
recoveredAlertsToReturn: {},
|
||||
mockLegacyAlertsClient.getRawAlertInstancesForState.mockResolvedValue({
|
||||
rawActiveAlerts: {},
|
||||
rawRecoveredAlerts: {},
|
||||
});
|
||||
ruleRunMetricsStore.getMetrics.mockReturnValue({
|
||||
numSearches: 3,
|
||||
|
@ -836,9 +836,9 @@ describe('Task Runner', () => {
|
|||
|
||||
test('should use rule specific flapping settings if global flapping is enabled', async () => {
|
||||
mockAlertsService.createAlertsClient.mockImplementation(() => mockAlertsClient);
|
||||
mockAlertsClient.getAlertsToSerialize.mockResolvedValue({
|
||||
alertsToReturn: {},
|
||||
recoveredAlertsToReturn: {},
|
||||
mockAlertsClient.getRawAlertInstancesForState.mockResolvedValue({
|
||||
rawActiveAlerts: {},
|
||||
rawRecoveredAlerts: {},
|
||||
});
|
||||
|
||||
const taskRunner = new TaskRunner({
|
||||
|
@ -893,9 +893,9 @@ describe('Task Runner', () => {
|
|||
});
|
||||
|
||||
mockAlertsService.createAlertsClient.mockImplementation(() => mockAlertsClient);
|
||||
mockAlertsClient.getAlertsToSerialize.mockResolvedValue({
|
||||
alertsToReturn: {},
|
||||
recoveredAlertsToReturn: {},
|
||||
mockAlertsClient.getRawAlertInstancesForState.mockResolvedValue({
|
||||
rawActiveAlerts: {},
|
||||
rawRecoveredAlerts: {},
|
||||
});
|
||||
|
||||
const taskRunner = new TaskRunner({
|
||||
|
@ -983,15 +983,7 @@ describe('Task Runner', () => {
|
|||
expect(alertsClientToUse.checkLimitUsage).toHaveBeenCalled();
|
||||
expect(alertsClientNotToUse.checkLimitUsage).not.toHaveBeenCalled();
|
||||
|
||||
expect(alertsClientToUse.processAlerts).toHaveBeenCalledWith({
|
||||
alertDelay: 0,
|
||||
flappingSettings: {
|
||||
enabled: true,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 4,
|
||||
},
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(alertsClientToUse.processAlerts).toHaveBeenCalledWith();
|
||||
|
||||
expect(alertsClientToUse.logAlerts).toHaveBeenCalledWith({
|
||||
ruleRunMetricsStore,
|
||||
|
@ -1004,12 +996,12 @@ describe('Task Runner', () => {
|
|||
expect(alertsClientToUse.persistAlerts).toHaveBeenCalled();
|
||||
expect(alertsClientNotToUse.persistAlerts).not.toHaveBeenCalled();
|
||||
|
||||
expect(alertsClientToUse.getProcessedAlerts).toHaveBeenCalledWith('activeCurrent');
|
||||
expect(alertsClientToUse.getProcessedAlerts).toHaveBeenCalledWith('recoveredCurrent');
|
||||
expect(alertsClientToUse.getProcessedAlerts).toHaveBeenCalledWith('active');
|
||||
expect(alertsClientToUse.getProcessedAlerts).toHaveBeenCalledWith('recovered');
|
||||
expect(alertsClientNotToUse.getProcessedAlerts).not.toHaveBeenCalled();
|
||||
|
||||
expect(alertsClientToUse.getAlertsToSerialize).toHaveBeenCalled();
|
||||
expect(alertsClientNotToUse.getAlertsToSerialize).not.toHaveBeenCalled();
|
||||
expect(alertsClientToUse.getRawAlertInstancesForState).toHaveBeenCalled();
|
||||
expect(alertsClientNotToUse.getRawAlertInstancesForState).not.toHaveBeenCalled();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -127,11 +127,9 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
state.alertRecoveredInstances.alertA.meta.flappingHistory
|
||||
);
|
||||
|
||||
// Flapping value for alert doc should be false while flapping value for state should be true
|
||||
// This is because we write out the alert doc BEFORE calculating the latest flapping state and
|
||||
// persisting into task state
|
||||
// Flapping value for alert doc and task state should be false
|
||||
expect(alertDocs[0]._source![ALERT_FLAPPING]).to.equal(false);
|
||||
expect(state.alertRecoveredInstances.alertA.meta.flapping).to.equal(true);
|
||||
expect(state.alertRecoveredInstances.alertA.meta.flapping).to.equal(false);
|
||||
|
||||
// Run the rule 6 more times
|
||||
for (let i = 0; i < 6; i++) {
|
||||
|
@ -258,11 +256,9 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
state.alertRecoveredInstances.alertA.meta.flappingHistory
|
||||
);
|
||||
|
||||
// Flapping value for alert doc should be false while flapping value for state should be true
|
||||
// This is because we write out the alert doc BEFORE calculating the latest flapping state and
|
||||
// persisting into task state
|
||||
// Flapping value for task state should be false
|
||||
expect(alertDocs[0]._source![ALERT_FLAPPING]).to.equal(false);
|
||||
expect(state.alertRecoveredInstances.alertA.meta.flapping).to.equal(true);
|
||||
expect(state.alertRecoveredInstances.alertA.meta.flapping).to.equal(false);
|
||||
|
||||
// Run the rule 6 more times
|
||||
for (let i = 0; i < 6; i++) {
|
||||
|
@ -401,7 +397,7 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
// notify_when is not RuleNotifyWhen.CHANGE, so it's not added to activeCurrent
|
||||
// notify_when is not RuleNotifyWhen.CHANGE, so it's not added to active
|
||||
getTestRuleData({
|
||||
rule_type_id: 'test.patternFiringAad',
|
||||
// set the schedule long so we can use "runSoon" to specify rule runs
|
||||
|
@ -539,11 +535,9 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
state.alertInstances.alertA.meta.flappingHistory
|
||||
);
|
||||
|
||||
// Flapping value for alert doc should be false while flapping value for state should be true
|
||||
// This is because we write out the alert doc BEFORE calculating the latest flapping state and
|
||||
// persisting into task state
|
||||
// Flapping value for alert doc and task state should be false
|
||||
expect(alertDocs[0]._source![ALERT_FLAPPING]).to.equal(false);
|
||||
expect(state.alertInstances.alertA.meta.flapping).to.equal(true);
|
||||
expect(state.alertInstances.alertA.meta.flapping).to.equal(false);
|
||||
|
||||
// Run the rule 6 more times
|
||||
for (let i = 0; i < 6; i++) {
|
||||
|
@ -569,11 +563,9 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
state.alertInstances.alertA.meta.flappingHistory
|
||||
);
|
||||
|
||||
// Flapping value for alert doc should be true while flapping value for state should be false
|
||||
// This is because we write out the alert doc BEFORE calculating the latest flapping state and
|
||||
// persisting into task state
|
||||
// Flapping value for alert doc and task state should be true
|
||||
expect(alertDocs[0]._source![ALERT_FLAPPING]).to.equal(true);
|
||||
expect(state.alertInstances.alertA.meta.flapping).to.equal(false);
|
||||
expect(state.alertInstances.alertA.meta.flapping).to.equal(true);
|
||||
|
||||
// Run the rule 3 more times
|
||||
for (let i = 0; i < 3; i++) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue