mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[ResponseOps][Flapping] Update flapping code once the flapping lookback value is configurable (#149448)
Resolves https://github.com/elastic/kibana/issues/145929 ## Summary Updates previous flapping tests to use the new flapping settings configs. Updates flapping logic to use flapping configs instead of hardcoded values. Calls the flapping api on every rule execution, and then passes in the flapping settings to the rule executors so they can be used by the rule registry. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### To verify I think it's helpful to hide the whitespace when reviewing this pr. - The flapping logic should remain the same, and all previous tests should pass. I only updated them to pass in the flapping settings. - Create rules, and set flapping settings in the ui and see the flapping behavior change for your rules. - Verify that the `x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts` run with the new flapping configs and output results we would expect --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
0f12bbce71
commit
cb7cc4a4c8
40 changed files with 770 additions and 275 deletions
|
@ -44,8 +44,13 @@ export const RULES_SETTINGS_SAVED_OBJECT_ID = 'rules-settings';
|
|||
export const DEFAULT_LOOK_BACK_WINDOW = 20;
|
||||
export const DEFAULT_STATUS_CHANGE_THRESHOLD = 4;
|
||||
|
||||
export const DEFAULT_FLAPPING_SETTINGS = {
|
||||
export const DEFAULT_FLAPPING_SETTINGS: RulesSettingsFlappingProperties = {
|
||||
enabled: true,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 4,
|
||||
lookBackWindow: DEFAULT_LOOK_BACK_WINDOW,
|
||||
statusChangeThreshold: DEFAULT_STATUS_CHANGE_THRESHOLD,
|
||||
};
|
||||
|
||||
export const DISABLE_FLAPPING_SETTINGS: RulesSettingsFlappingProperties = {
|
||||
...DEFAULT_FLAPPING_SETTINGS,
|
||||
enabled: false,
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ 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,
|
||||
|
@ -149,8 +150,8 @@ export function createAlertFactory<
|
|||
hasReachedAlertLimit,
|
||||
alertLimit: maxAlerts,
|
||||
autoRecoverAlerts,
|
||||
// setFlapping is false, as we only want to use this function to get the recovered alerts
|
||||
setFlapping: false,
|
||||
// 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]
|
||||
|
|
|
@ -14,6 +14,7 @@ import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_e
|
|||
import { ruleRunMetricsStoreMock } from '../lib/rule_run_metrics_store.mock';
|
||||
import { getAlertsForNotification, processAlerts, setFlapping } from '../lib';
|
||||
import { logAlerts } from '../task_runner/log_alerts';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
|
||||
const scheduleActions = jest.fn();
|
||||
const replaceState = jest.fn(() => ({ scheduleActions }));
|
||||
|
@ -229,6 +230,7 @@ describe('Legacy Alerts Client', () => {
|
|||
ruleLabel: `ruleLogPrefix`,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAndScheduleActionsForAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(processAlerts).toHaveBeenCalledWith({
|
||||
|
@ -244,10 +246,15 @@ describe('Legacy Alerts Client', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 1000,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(setFlapping).toHaveBeenCalledWith(
|
||||
{
|
||||
enabled: true,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 4,
|
||||
},
|
||||
{
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
|
@ -256,6 +263,11 @@ describe('Legacy Alerts Client', () => {
|
|||
);
|
||||
|
||||
expect(getAlertsForNotification).toHaveBeenCalledWith(
|
||||
{
|
||||
enabled: true,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 4,
|
||||
},
|
||||
'default',
|
||||
{},
|
||||
{
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
RawAlertInstance,
|
||||
WithoutReservedActionGroups,
|
||||
} from '../types';
|
||||
import { RulesSettingsFlappingProperties } from '../../common/rules_settings';
|
||||
|
||||
interface ConstructorOpts {
|
||||
logger: Logger;
|
||||
|
@ -111,11 +112,13 @@ export class LegacyAlertsClient<
|
|||
ruleLabel,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAndScheduleActionsForAlerts,
|
||||
flappingSettings,
|
||||
}: {
|
||||
eventLogger: AlertingEventLogger;
|
||||
ruleLabel: string;
|
||||
shouldLogAndScheduleActionsForAlerts: boolean;
|
||||
ruleRunMetricsStore: RuleRunMetricsStore;
|
||||
flappingSettings: RulesSettingsFlappingProperties;
|
||||
}) {
|
||||
const {
|
||||
newAlerts: processedAlertsNew,
|
||||
|
@ -132,10 +135,11 @@ export class LegacyAlertsClient<
|
|||
this.options.ruleType.autoRecoverAlerts !== undefined
|
||||
? this.options.ruleType.autoRecoverAlerts
|
||||
: true,
|
||||
setFlapping: true,
|
||||
flappingSettings,
|
||||
});
|
||||
|
||||
setFlapping<State, Context, ActionGroupIds, RecoveryActionGroupId>(
|
||||
flappingSettings,
|
||||
processedAlertsActive,
|
||||
processedAlertsRecovered
|
||||
);
|
||||
|
@ -147,6 +151,7 @@ export class LegacyAlertsClient<
|
|||
);
|
||||
|
||||
const alerts = getAlertsForNotification<State, Context, ActionGroupIds, RecoveryActionGroupId>(
|
||||
flappingSettings,
|
||||
this.options.ruleType.defaultActionGroupId,
|
||||
processedAlertsNew,
|
||||
processedAlertsActive,
|
||||
|
|
|
@ -5,18 +5,23 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DEFAULT_FLAPPING_SETTINGS, DISABLE_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
import { atCapacity, updateFlappingHistory, isFlapping } from './flapping_utils';
|
||||
|
||||
describe('flapping utils', () => {
|
||||
describe('updateFlappingHistory function', () => {
|
||||
test('correctly updates flappingHistory', () => {
|
||||
const flappingHistory = updateFlappingHistory([false, false], true);
|
||||
const flappingHistory = updateFlappingHistory(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
[false, false],
|
||||
true
|
||||
);
|
||||
expect(flappingHistory).toEqual([false, false, true]);
|
||||
});
|
||||
|
||||
test('correctly updates flappingHistory while maintaining a fixed size', () => {
|
||||
const flappingHistory = new Array(20).fill(false);
|
||||
const fh = updateFlappingHistory(flappingHistory, true);
|
||||
const fh = updateFlappingHistory(DEFAULT_FLAPPING_SETTINGS, flappingHistory, true);
|
||||
expect(fh.length).toEqual(20);
|
||||
const result = new Array(19).fill(false);
|
||||
expect(fh).toEqual(result.concat(true));
|
||||
|
@ -24,27 +29,36 @@ describe('flapping utils', () => {
|
|||
|
||||
test('correctly updates flappingHistory while maintaining if array is larger than fixed size', () => {
|
||||
const flappingHistory = new Array(23).fill(false);
|
||||
const fh = updateFlappingHistory(flappingHistory, true);
|
||||
const fh = updateFlappingHistory(DEFAULT_FLAPPING_SETTINGS, flappingHistory, true);
|
||||
expect(fh.length).toEqual(20);
|
||||
const result = new Array(19).fill(false);
|
||||
expect(fh).toEqual(result.concat(true));
|
||||
});
|
||||
|
||||
test('does not update flappingHistory if flapping is disabled', () => {
|
||||
const flappingHistory = updateFlappingHistory(
|
||||
DISABLE_FLAPPING_SETTINGS,
|
||||
[false, false],
|
||||
true
|
||||
);
|
||||
expect(flappingHistory).toEqual([false, false]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('atCapacity and getCapacityDiff functions', () => {
|
||||
test('returns true if flappingHistory == set capacity', () => {
|
||||
const flappingHistory = new Array(20).fill(false);
|
||||
expect(atCapacity(flappingHistory)).toEqual(true);
|
||||
expect(atCapacity(DEFAULT_FLAPPING_SETTINGS, flappingHistory)).toEqual(true);
|
||||
});
|
||||
|
||||
test('returns true if flappingHistory > set capacity', () => {
|
||||
const flappingHistory = new Array(25).fill(false);
|
||||
expect(atCapacity(flappingHistory)).toEqual(true);
|
||||
expect(atCapacity(DEFAULT_FLAPPING_SETTINGS, flappingHistory)).toEqual(true);
|
||||
});
|
||||
|
||||
test('returns false if flappingHistory < set capacity', () => {
|
||||
const flappingHistory = new Array(15).fill(false);
|
||||
expect(atCapacity(flappingHistory)).toEqual(false);
|
||||
expect(atCapacity(DEFAULT_FLAPPING_SETTINGS, flappingHistory)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -52,39 +66,46 @@ describe('flapping utils', () => {
|
|||
describe('not currently flapping', () => {
|
||||
test('returns true if at capacity and flap count exceeds the threshold', () => {
|
||||
const flappingHistory = [true, true, true, true].concat(new Array(16).fill(false));
|
||||
expect(isFlapping(flappingHistory)).toEqual(true);
|
||||
expect(isFlapping(DEFAULT_FLAPPING_SETTINGS, flappingHistory)).toEqual(true);
|
||||
});
|
||||
|
||||
test("returns false if at capacity and flap count doesn't exceed the threshold", () => {
|
||||
const flappingHistory = [true, true].concat(new Array(20).fill(false));
|
||||
expect(isFlapping(flappingHistory)).toEqual(false);
|
||||
expect(isFlapping(DEFAULT_FLAPPING_SETTINGS, flappingHistory)).toEqual(false);
|
||||
});
|
||||
|
||||
test('returns true if not at capacity', () => {
|
||||
const flappingHistory = new Array(5).fill(true);
|
||||
expect(isFlapping(flappingHistory)).toEqual(true);
|
||||
expect(isFlapping(DEFAULT_FLAPPING_SETTINGS, flappingHistory)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('currently flapping', () => {
|
||||
test('returns true if at capacity and the flap count exceeds the threshold', () => {
|
||||
const flappingHistory = new Array(16).fill(false).concat([true, true, true, true]);
|
||||
expect(isFlapping(flappingHistory, true)).toEqual(true);
|
||||
expect(isFlapping(DEFAULT_FLAPPING_SETTINGS, flappingHistory, true)).toEqual(true);
|
||||
});
|
||||
|
||||
test("returns true if not at capacity and the flap count doesn't exceed the threshold", () => {
|
||||
const flappingHistory = new Array(16).fill(false);
|
||||
expect(isFlapping(flappingHistory, true)).toEqual(true);
|
||||
expect(isFlapping(DEFAULT_FLAPPING_SETTINGS, flappingHistory, true)).toEqual(true);
|
||||
});
|
||||
|
||||
test('returns true if not at capacity and the flap count exceeds the threshold', () => {
|
||||
const flappingHistory = new Array(10).fill(false).concat([true, true, true, true]);
|
||||
expect(isFlapping(flappingHistory, true)).toEqual(true);
|
||||
expect(isFlapping(DEFAULT_FLAPPING_SETTINGS, flappingHistory, true)).toEqual(true);
|
||||
});
|
||||
|
||||
test("returns false if at capacity and the flap count doesn't exceed the threshold", () => {
|
||||
const flappingHistory = new Array(20).fill(false);
|
||||
expect(isFlapping(flappingHistory, true)).toEqual(false);
|
||||
expect(isFlapping(DEFAULT_FLAPPING_SETTINGS, flappingHistory, true)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('flapping disabled', () => {
|
||||
test('returns false if flapping is disabled', () => {
|
||||
const flappingHistory = new Array(16).fill(false).concat([true, true, true, true]);
|
||||
expect(isFlapping(DISABLE_FLAPPING_SETTINGS, flappingHistory, true)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,31 +5,46 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
const MAX_CAPACITY = 20;
|
||||
export const MAX_FLAP_COUNT = 4;
|
||||
import { RulesSettingsFlappingProperties } from '../../common/rules_settings';
|
||||
|
||||
export function updateFlappingHistory(flappingHistory: boolean[], state: boolean) {
|
||||
const updatedFlappingHistory = flappingHistory.concat(state).slice(MAX_CAPACITY * -1);
|
||||
return updatedFlappingHistory;
|
||||
export function updateFlappingHistory(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
flappingHistory: boolean[],
|
||||
state: boolean
|
||||
) {
|
||||
if (flappingSettings.enabled) {
|
||||
const updatedFlappingHistory = flappingHistory
|
||||
.concat(state)
|
||||
.slice(flappingSettings.lookBackWindow * -1);
|
||||
return updatedFlappingHistory;
|
||||
}
|
||||
return flappingHistory;
|
||||
}
|
||||
|
||||
export function isFlapping(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
flappingHistory: boolean[],
|
||||
isCurrentlyFlapping: boolean = false
|
||||
): boolean {
|
||||
const numStateChanges = flappingHistory.filter((f) => f).length;
|
||||
if (isCurrentlyFlapping) {
|
||||
// if an alert is currently flapping,
|
||||
// it will return false if the flappingHistory array is at capacity and there are 0 state changes
|
||||
// else it will return true
|
||||
return !(atCapacity(flappingHistory) && numStateChanges === 0);
|
||||
} else {
|
||||
// if an alert is not currently flapping,
|
||||
// it will return true if the number of state changes in flappingHistory array >= the max flapping count
|
||||
return numStateChanges >= MAX_FLAP_COUNT;
|
||||
if (flappingSettings.enabled) {
|
||||
const numStateChanges = flappingHistory.filter((f) => f).length;
|
||||
if (isCurrentlyFlapping) {
|
||||
// if an alert is currently flapping,
|
||||
// it will return false if the flappingHistory array is at capacity and there are 0 state changes
|
||||
// else it will return true
|
||||
return !(atCapacity(flappingSettings, flappingHistory) && numStateChanges === 0);
|
||||
} else {
|
||||
// if an alert is not currently flapping,
|
||||
// it will return true if the number of state changes in flappingHistory array >= the flapping status change threshold
|
||||
return numStateChanges >= flappingSettings.statusChangeThreshold;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function atCapacity(flappingHistory: boolean[] = []): boolean {
|
||||
return flappingHistory.length >= MAX_CAPACITY;
|
||||
export function atCapacity(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
flappingHistory: boolean[] = []
|
||||
): boolean {
|
||||
return flappingHistory.length >= flappingSettings.lookBackWindow;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DEFAULT_FLAPPING_SETTINGS, DISABLE_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
import { getAlertsForNotification } from '.';
|
||||
import { Alert } from '../alert';
|
||||
|
||||
|
@ -14,6 +15,7 @@ describe('getAlertsForNotification', () => {
|
|||
const alert2 = new Alert('2', { meta: { flapping: false } });
|
||||
|
||||
const { newAlerts, activeAlerts } = getAlertsForNotification(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
{
|
||||
'1': alert1,
|
||||
|
@ -66,6 +68,7 @@ describe('getAlertsForNotification', () => {
|
|||
|
||||
const { newAlerts, activeAlerts, recoveredAlerts, currentRecoveredAlerts } =
|
||||
getAlertsForNotification(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'default',
|
||||
{},
|
||||
{},
|
||||
|
@ -143,4 +146,117 @@ describe('getAlertsForNotification', () => {
|
|||
}
|
||||
`);
|
||||
});
|
||||
|
||||
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',
|
||||
{},
|
||||
{},
|
||||
{
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
},
|
||||
{
|
||||
'1': alert1,
|
||||
'2': alert2,
|
||||
'3': alert3,
|
||||
}
|
||||
);
|
||||
|
||||
expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
expect(recoveredAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"pendingRecoveredCount": 0,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"pendingRecoveredCount": 0,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"pendingRecoveredCount": 0,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(currentRecoveredAlerts).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"pendingRecoveredCount": 0,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"pendingRecoveredCount": 0,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"flapping": true,
|
||||
"flappingHistory": Array [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
"pendingRecoveredCount": 0,
|
||||
},
|
||||
"state": Object {},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
*/
|
||||
|
||||
import { keys } from 'lodash';
|
||||
import { RulesSettingsFlappingProperties } from '../../common/rules_settings';
|
||||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../types';
|
||||
import { MAX_FLAP_COUNT } from './flapping_utils';
|
||||
|
||||
export function getAlertsForNotification<
|
||||
State extends AlertInstanceState,
|
||||
|
@ -16,6 +16,7 @@ export function getAlertsForNotification<
|
|||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
actionGroupId: string,
|
||||
newAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
|
@ -29,34 +30,38 @@ export function getAlertsForNotification<
|
|||
|
||||
for (const id of keys(currentRecoveredAlerts)) {
|
||||
const alert = recoveredAlerts[id];
|
||||
const flapping = alert.getFlapping();
|
||||
if (flapping) {
|
||||
alert.incrementPendingRecoveredCount();
|
||||
if (flappingSettings.enabled) {
|
||||
const flapping = alert.getFlapping();
|
||||
if (flapping) {
|
||||
alert.incrementPendingRecoveredCount();
|
||||
|
||||
if (alert.getPendingRecoveredCount() < MAX_FLAP_COUNT) {
|
||||
// keep the context and previous actionGroupId if available
|
||||
const context = alert.getContext();
|
||||
const lastActionGroupId = alert.getLastScheduledActions()?.group;
|
||||
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);
|
||||
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;
|
||||
// schedule actions for the new active alert
|
||||
newAlert.scheduleActions(
|
||||
(lastActionGroupId ? lastActionGroupId : actionGroupId) as ActionGroupIds,
|
||||
context
|
||||
);
|
||||
activeAlerts[id] = newAlert;
|
||||
|
||||
// remove from recovered alerts
|
||||
delete recoveredAlerts[id];
|
||||
delete currentRecoveredAlerts[id];
|
||||
} else {
|
||||
alert.resetPendingRecoveredCount();
|
||||
// remove from recovered alerts
|
||||
delete recoveredAlerts[id];
|
||||
delete currentRecoveredAlerts[id];
|
||||
} else {
|
||||
alert.resetPendingRecoveredCount();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert.resetPendingRecoveredCount();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { cloneDeep } from 'lodash';
|
|||
import { processAlerts, updateAlertFlappingHistory } 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;
|
||||
|
@ -56,7 +57,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(newAlerts).toEqual({ '1': newAlert });
|
||||
|
@ -94,7 +95,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(newAlerts).toEqual({ '1': newAlert1, '2': newAlert2 });
|
||||
|
@ -140,7 +141,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -178,7 +179,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -226,7 +227,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -284,7 +285,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -345,7 +346,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -388,7 +389,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({ '2': updatedAlerts['2'] });
|
||||
|
@ -416,7 +417,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({});
|
||||
|
@ -446,7 +447,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({ '2': updatedAlerts['2'], '3': updatedAlerts['3'] });
|
||||
|
@ -485,7 +486,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({ '2': updatedAlerts['2'], '3': updatedAlerts['3'] });
|
||||
|
@ -524,7 +525,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual(updatedAlerts);
|
||||
|
@ -554,7 +555,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: false,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({});
|
||||
|
@ -600,7 +601,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: true,
|
||||
alertLimit: 7,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({});
|
||||
|
@ -636,7 +637,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: true,
|
||||
alertLimit: 7,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -696,7 +697,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: true,
|
||||
alertLimit: MAX_ALERTS,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(Object.keys(activeAlerts).length).toEqual(MAX_ALERTS);
|
||||
|
@ -730,7 +731,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -781,7 +782,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -818,7 +819,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -874,7 +875,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
|
@ -908,7 +909,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
|
@ -950,7 +951,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1017,7 +1018,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: true,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1054,7 +1055,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: true,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1116,7 +1117,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: true,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1193,7 +1194,7 @@ describe('processAlerts', () => {
|
|||
hasReachedAlertLimit: true,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
setFlapping: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1240,7 +1241,7 @@ describe('processAlerts', () => {
|
|||
const alert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory: [false, false] },
|
||||
});
|
||||
updateAlertFlappingHistory(alert, true);
|
||||
updateAlertFlappingHistory(DEFAULT_FLAPPING_SETTINGS, alert, true);
|
||||
expect(alert.getFlappingHistory()).toEqual([false, false, true]);
|
||||
});
|
||||
|
||||
|
@ -1249,7 +1250,7 @@ describe('processAlerts', () => {
|
|||
const alert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory },
|
||||
});
|
||||
updateAlertFlappingHistory(alert, true);
|
||||
updateAlertFlappingHistory(DEFAULT_FLAPPING_SETTINGS, alert, true);
|
||||
const fh = alert.getFlappingHistory() || [];
|
||||
expect(fh.length).toEqual(20);
|
||||
const result = new Array(19).fill(false);
|
||||
|
@ -1261,7 +1262,7 @@ describe('processAlerts', () => {
|
|||
const alert = new Alert<AlertInstanceState, AlertInstanceContext>('1', {
|
||||
meta: { flappingHistory },
|
||||
});
|
||||
updateAlertFlappingHistory(alert, true);
|
||||
updateAlertFlappingHistory(DEFAULT_FLAPPING_SETTINGS, alert, true);
|
||||
const fh = alert.getFlappingHistory() || [];
|
||||
expect(fh.length).toEqual(20);
|
||||
const result = new Array(19).fill(false);
|
||||
|
|
|
@ -10,6 +10,7 @@ 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,
|
||||
|
@ -21,8 +22,7 @@ interface ProcessAlertsOpts<
|
|||
hasReachedAlertLimit: boolean;
|
||||
alertLimit: number;
|
||||
autoRecoverAlerts: boolean;
|
||||
// flag used to determine whether or not we want to push the flapping state on to the flappingHistory array
|
||||
setFlapping: boolean;
|
||||
flappingSettings: RulesSettingsFlappingProperties;
|
||||
}
|
||||
interface ProcessAlertsResult<
|
||||
State extends AlertInstanceState,
|
||||
|
@ -49,7 +49,7 @@ export function processAlerts<
|
|||
hasReachedAlertLimit,
|
||||
alertLimit,
|
||||
autoRecoverAlerts,
|
||||
setFlapping,
|
||||
flappingSettings,
|
||||
}: ProcessAlertsOpts<State, Context>): ProcessAlertsResult<
|
||||
State,
|
||||
Context,
|
||||
|
@ -62,14 +62,14 @@ export function processAlerts<
|
|||
existingAlerts,
|
||||
previouslyRecoveredAlerts,
|
||||
alertLimit,
|
||||
setFlapping
|
||||
flappingSettings
|
||||
)
|
||||
: processAlertsHelper(
|
||||
alerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts,
|
||||
autoRecoverAlerts,
|
||||
setFlapping
|
||||
flappingSettings
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ function processAlertsHelper<
|
|||
existingAlerts: Record<string, Alert<State, Context>>,
|
||||
previouslyRecoveredAlerts: Record<string, Alert<State, Context>>,
|
||||
autoRecoverAlerts: boolean,
|
||||
setFlapping: boolean
|
||||
flappingSettings: RulesSettingsFlappingProperties
|
||||
): ProcessAlertsResult<State, Context, ActionGroupIds, RecoveryActionGroupId> {
|
||||
const existingAlertIds = new Set(Object.keys(existingAlerts));
|
||||
const previouslyRecoveredAlertsIds = new Set(Object.keys(previouslyRecoveredAlerts));
|
||||
|
@ -106,13 +106,13 @@ function processAlertsHelper<
|
|||
const state = newAlerts[id].getState();
|
||||
newAlerts[id].replaceState({ ...state, start: currentTime, duration: '0' });
|
||||
|
||||
if (setFlapping) {
|
||||
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(newAlerts[id], true);
|
||||
updateAlertFlappingHistory(flappingSettings, newAlerts[id], true);
|
||||
}
|
||||
} else {
|
||||
// this alert did exist in previous run
|
||||
|
@ -128,8 +128,8 @@ function processAlertsHelper<
|
|||
});
|
||||
|
||||
// this alert is still active
|
||||
if (setFlapping) {
|
||||
updateAlertFlappingHistory(activeAlerts[id], false);
|
||||
if (flappingSettings.enabled) {
|
||||
updateAlertFlappingHistory(flappingSettings, activeAlerts[id], false);
|
||||
}
|
||||
}
|
||||
} else if (existingAlertIds.has(id) && autoRecoverAlerts) {
|
||||
|
@ -147,8 +147,8 @@ function processAlertsHelper<
|
|||
...(state.start ? { end: currentTime } : {}),
|
||||
});
|
||||
// this alert has flapped from active to recovered
|
||||
if (setFlapping) {
|
||||
updateAlertFlappingHistory(recoveredAlerts[id], true);
|
||||
if (flappingSettings.enabled) {
|
||||
updateAlertFlappingHistory(flappingSettings, recoveredAlerts[id], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,8 +157,8 @@ function processAlertsHelper<
|
|||
// alerts are still recovered
|
||||
for (const id of previouslyRecoveredAlertsIds) {
|
||||
recoveredAlerts[id] = previouslyRecoveredAlerts[id];
|
||||
if (setFlapping) {
|
||||
updateAlertFlappingHistory(recoveredAlerts[id], false);
|
||||
if (flappingSettings.enabled) {
|
||||
updateAlertFlappingHistory(flappingSettings, recoveredAlerts[id], false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,7 @@ function processAlertsLimitReached<
|
|||
existingAlerts: Record<string, Alert<State, Context>>,
|
||||
previouslyRecoveredAlerts: Record<string, Alert<State, Context>>,
|
||||
alertLimit: number,
|
||||
setFlapping: boolean
|
||||
flappingSettings: RulesSettingsFlappingProperties
|
||||
): ProcessAlertsResult<State, Context, ActionGroupIds, RecoveryActionGroupId> {
|
||||
const existingAlertIds = new Set(Object.keys(existingAlerts));
|
||||
const previouslyRecoveredAlertsIds = new Set(Object.keys(previouslyRecoveredAlerts));
|
||||
|
@ -210,8 +210,8 @@ function processAlertsLimitReached<
|
|||
});
|
||||
|
||||
// this alert is still active
|
||||
if (setFlapping) {
|
||||
updateAlertFlappingHistory(activeAlerts[id], false);
|
||||
if (flappingSettings.enabled) {
|
||||
updateAlertFlappingHistory(flappingSettings, activeAlerts[id], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -236,12 +236,12 @@ function processAlertsLimitReached<
|
|||
const state = newAlerts[id].getState();
|
||||
newAlerts[id].replaceState({ ...state, start: currentTime, duration: '0' });
|
||||
|
||||
if (setFlapping) {
|
||||
if (flappingSettings.enabled) {
|
||||
if (previouslyRecoveredAlertsIds.has(id)) {
|
||||
// this alert has flapped from recovered to active
|
||||
newAlerts[id].setFlappingHistory(previouslyRecoveredAlerts[id].getFlappingHistory());
|
||||
}
|
||||
updateAlertFlappingHistory(newAlerts[id], true);
|
||||
updateAlertFlappingHistory(flappingSettings, newAlerts[id], true);
|
||||
}
|
||||
|
||||
if (!hasCapacityForNewAlerts()) {
|
||||
|
@ -258,7 +258,15 @@ export function updateAlertFlappingHistory<
|
|||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>(alert: Alert<State, Context, ActionGroupIds | RecoveryActionGroupId>, state: boolean) {
|
||||
const updatedFlappingHistory = updateFlappingHistory(alert.getFlappingHistory() || [], state);
|
||||
>(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
alert: Alert<State, Context, ActionGroupIds | RecoveryActionGroupId>,
|
||||
state: boolean
|
||||
) {
|
||||
const updatedFlappingHistory = updateFlappingHistory(
|
||||
flappingSettings,
|
||||
alert.getFlappingHistory() || [],
|
||||
state
|
||||
);
|
||||
alert.setFlappingHistory(updatedFlappingHistory);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { pick } from 'lodash';
|
|||
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';
|
||||
|
||||
describe('setFlapping', () => {
|
||||
const flapping = new Array(16).fill(false).concat([true, true, true, true]);
|
||||
|
@ -29,7 +30,7 @@ describe('setFlapping', () => {
|
|||
'4': new Alert('4', { meta: { flapping: true, flappingHistory: notFlapping } }),
|
||||
};
|
||||
|
||||
setFlapping(activeAlerts, recoveredAlerts);
|
||||
setFlapping(DEFAULT_FLAPPING_SETTINGS, activeAlerts, recoveredAlerts);
|
||||
const fields = ['1.meta.flapping', '2.meta.flapping', '3.meta.flapping', '4.meta.flapping'];
|
||||
expect(pick(activeAlerts, fields)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -81,6 +82,73 @@ describe('setFlapping', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test('should set flapping to false on alerts when flapping is disabled', () => {
|
||||
const activeAlerts = {
|
||||
'1': new Alert('1', { meta: { flappingHistory: flapping } }),
|
||||
'2': new Alert('2', { meta: { flappingHistory: [false, false] } }),
|
||||
'3': new Alert('3', { meta: { flapping: true, flappingHistory: flapping } }),
|
||||
'4': new Alert('4', { meta: { flapping: true, flappingHistory: [false, false] } }),
|
||||
};
|
||||
|
||||
const recoveredAlerts = {
|
||||
'1': new Alert('1', { meta: { flappingHistory: [true, true, true, true] } }),
|
||||
'2': new Alert('2', { meta: { flappingHistory: notFlapping } }),
|
||||
'3': new Alert('3', { meta: { flapping: true, flappingHistory: [true, true] } }),
|
||||
'4': new Alert('4', { meta: { flapping: true, flappingHistory: notFlapping } }),
|
||||
};
|
||||
|
||||
setFlapping(DISABLE_FLAPPING_SETTINGS, activeAlerts, recoveredAlerts);
|
||||
const fields = ['1.meta.flapping', '2.meta.flapping', '3.meta.flapping', '4.meta.flapping'];
|
||||
expect(pick(activeAlerts, fields)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
},
|
||||
},
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
},
|
||||
},
|
||||
"4": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(pick(recoveredAlerts, fields)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
},
|
||||
},
|
||||
"2": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
},
|
||||
},
|
||||
"3": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
},
|
||||
},
|
||||
"4": Object {
|
||||
"meta": Object {
|
||||
"flapping": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
describe('isAlertFlapping', () => {
|
||||
describe('not currently flapping', () => {
|
||||
test('returns true if the flap count exceeds the threshold', () => {
|
||||
|
@ -91,7 +159,7 @@ describe('setFlapping', () => {
|
|||
meta: { flappingHistory },
|
||||
}
|
||||
);
|
||||
expect(isAlertFlapping(alert)).toEqual(true);
|
||||
expect(isAlertFlapping(DEFAULT_FLAPPING_SETTINGS, alert)).toEqual(true);
|
||||
});
|
||||
|
||||
test("returns false the flap count doesn't exceed the threshold", () => {
|
||||
|
@ -102,7 +170,7 @@ describe('setFlapping', () => {
|
|||
meta: { flappingHistory },
|
||||
}
|
||||
);
|
||||
expect(isAlertFlapping(alert)).toEqual(false);
|
||||
expect(isAlertFlapping(DEFAULT_FLAPPING_SETTINGS, alert)).toEqual(false);
|
||||
});
|
||||
|
||||
test('returns true if not at capacity and the flap count exceeds the threshold', () => {
|
||||
|
@ -113,7 +181,7 @@ describe('setFlapping', () => {
|
|||
meta: { flappingHistory },
|
||||
}
|
||||
);
|
||||
expect(isAlertFlapping(alert)).toEqual(true);
|
||||
expect(isAlertFlapping(DEFAULT_FLAPPING_SETTINGS, alert)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -126,7 +194,7 @@ describe('setFlapping', () => {
|
|||
meta: { flappingHistory, flapping: true },
|
||||
}
|
||||
);
|
||||
expect(isAlertFlapping(alert)).toEqual(true);
|
||||
expect(isAlertFlapping(DEFAULT_FLAPPING_SETTINGS, alert)).toEqual(true);
|
||||
});
|
||||
|
||||
test("returns true if not at capacity and the flap count doesn't exceed the threshold", () => {
|
||||
|
@ -137,7 +205,7 @@ describe('setFlapping', () => {
|
|||
meta: { flappingHistory, flapping: true },
|
||||
}
|
||||
);
|
||||
expect(isAlertFlapping(alert)).toEqual(true);
|
||||
expect(isAlertFlapping(DEFAULT_FLAPPING_SETTINGS, alert)).toEqual(true);
|
||||
});
|
||||
|
||||
test('returns true if not at capacity and the flap count exceeds the threshold', () => {
|
||||
|
@ -148,7 +216,7 @@ describe('setFlapping', () => {
|
|||
meta: { flappingHistory, flapping: true },
|
||||
}
|
||||
);
|
||||
expect(isAlertFlapping(alert)).toEqual(true);
|
||||
expect(isAlertFlapping(DEFAULT_FLAPPING_SETTINGS, alert)).toEqual(true);
|
||||
});
|
||||
|
||||
test("returns false if at capacity and the flap count doesn't exceed the threshold", () => {
|
||||
|
@ -159,7 +227,7 @@ describe('setFlapping', () => {
|
|||
meta: { flappingHistory, flapping: true },
|
||||
}
|
||||
);
|
||||
expect(isAlertFlapping(alert)).toEqual(false);
|
||||
expect(isAlertFlapping(DEFAULT_FLAPPING_SETTINGS, alert)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import { keys } from 'lodash';
|
|||
import { Alert } from '../alert';
|
||||
import { AlertInstanceState, AlertInstanceContext } from '../types';
|
||||
import { isFlapping } from './flapping_utils';
|
||||
import { RulesSettingsFlappingProperties } from '../../common/rules_settings';
|
||||
|
||||
export function setFlapping<
|
||||
State extends AlertInstanceState,
|
||||
|
@ -16,18 +17,19 @@ export function setFlapping<
|
|||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupIds extends string
|
||||
>(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
activeAlerts: Record<string, Alert<State, Context, ActionGroupIds>> = {},
|
||||
recoveredAlerts: Record<string, Alert<State, Context, RecoveryActionGroupIds>> = {}
|
||||
) {
|
||||
for (const id of keys(activeAlerts)) {
|
||||
const alert = activeAlerts[id];
|
||||
const flapping = isAlertFlapping(alert);
|
||||
const flapping = isAlertFlapping(flappingSettings, alert);
|
||||
alert.setFlapping(flapping);
|
||||
}
|
||||
|
||||
for (const id of keys(recoveredAlerts)) {
|
||||
const alert = recoveredAlerts[id];
|
||||
const flapping = isAlertFlapping(alert);
|
||||
const flapping = isAlertFlapping(flappingSettings, alert);
|
||||
alert.setFlapping(flapping);
|
||||
}
|
||||
}
|
||||
|
@ -37,8 +39,13 @@ export function isAlertFlapping<
|
|||
Context extends AlertInstanceContext,
|
||||
ActionGroupIds extends string,
|
||||
RecoveryActionGroupId extends string
|
||||
>(alert: Alert<State, Context, ActionGroupIds | RecoveryActionGroupId>): boolean {
|
||||
>(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
alert: Alert<State, Context, ActionGroupIds | RecoveryActionGroupId>
|
||||
): boolean {
|
||||
const flappingHistory: boolean[] = alert.getFlappingHistory() || [];
|
||||
const isCurrentlyFlapping = alert.getFlapping();
|
||||
return isFlapping(flappingHistory, isCurrentlyFlapping);
|
||||
return flappingSettings.enabled
|
||||
? isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping)
|
||||
: false;
|
||||
}
|
||||
|
|
|
@ -464,6 +464,10 @@ export class AlertingPlugin {
|
|||
return alertingAuthorizationClientFactory!.create(request);
|
||||
};
|
||||
|
||||
const getRulesSettingsClientWithRequest = (request: KibanaRequest) => {
|
||||
return rulesSettingsClientFactory!.create(request);
|
||||
};
|
||||
|
||||
taskRunnerFactory.initialize({
|
||||
logger,
|
||||
data: plugins.data,
|
||||
|
@ -488,6 +492,7 @@ export class AlertingPlugin {
|
|||
maxAlerts: this.config.rules.run.alerts.max,
|
||||
actionsConfigMap: getActionsConfigMap(this.config.rules.run.actions),
|
||||
usageCounter: this.usageCounter,
|
||||
getRulesSettingsClientWithRequest,
|
||||
});
|
||||
|
||||
this.eventLogService!.registerSavedObjectProvider('alert', (request) => {
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RulesSettingsClientApi, RulesSettingsFlappingClientApi } from './types';
|
||||
import {
|
||||
RulesSettingsClientApi,
|
||||
RulesSettingsFlappingClientApi,
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
} from './types';
|
||||
|
||||
export type RulesSettingsClientMock = jest.Mocked<RulesSettingsClientApi>;
|
||||
export type RulesSettingsFlappingClientMock = jest.Mocked<RulesSettingsFlappingClientApi>;
|
||||
|
@ -14,7 +18,7 @@ export type RulesSettingsFlappingClientMock = jest.Mocked<RulesSettingsFlappingC
|
|||
// the mock return value on the flapping
|
||||
const createRulesSettingsClientMock = () => {
|
||||
const flappingMocked: RulesSettingsFlappingClientMock = {
|
||||
get: jest.fn(),
|
||||
get: jest.fn().mockReturnValue(DEFAULT_FLAPPING_SETTINGS),
|
||||
update: jest.fn(),
|
||||
};
|
||||
const mocked: RulesSettingsClientMock = {
|
||||
|
|
|
@ -76,6 +76,7 @@ import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_e
|
|||
import { SharePluginStart } from '@kbn/share-plugin/server';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
|
||||
import { rulesSettingsClientMock } from '../rules_settings_client.mock';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -162,6 +163,7 @@ describe('Task Runner', () => {
|
|||
max: 10000,
|
||||
},
|
||||
},
|
||||
getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClientMock.create()),
|
||||
};
|
||||
|
||||
const ephemeralTestParams: Array<
|
||||
|
@ -209,6 +211,9 @@ describe('Task Runner', () => {
|
|||
taskRunnerFactoryInitializerParams.executionContext.withContext.mockImplementation((ctx, fn) =>
|
||||
fn()
|
||||
);
|
||||
taskRunnerFactoryInitializerParams.getRulesSettingsClientWithRequest.mockReturnValue(
|
||||
rulesSettingsClientMock.create()
|
||||
);
|
||||
mockedRuleTypeSavedObject.monitoring!.run.history = [];
|
||||
mockedRuleTypeSavedObject.monitoring!.run.calculated_metrics.success_ratio = 0;
|
||||
|
||||
|
|
|
@ -295,6 +295,8 @@ export class TaskRunner<
|
|||
...wrappedClientOptions,
|
||||
searchSourceClient,
|
||||
});
|
||||
const rulesSettingsClient = this.context.getRulesSettingsClientWithRequest(fakeRequest);
|
||||
const flappingSettings = await rulesSettingsClient.flapping().get();
|
||||
|
||||
const { updatedRuleTypeState } = await this.timer.runWithTimer(
|
||||
TaskRunnerTimerSpan.RuleTypeRun,
|
||||
|
@ -373,6 +375,7 @@ export class TaskRunner<
|
|||
notifyWhen,
|
||||
},
|
||||
logger: this.logger,
|
||||
flappingSettings,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -418,6 +421,7 @@ export class TaskRunner<
|
|||
ruleLabel,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAndScheduleActionsForAlerts: this.shouldLogAndScheduleActionsForAlerts(),
|
||||
flappingSettings,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ import { EVENT_LOG_ACTIONS } from '../plugin';
|
|||
import { SharePluginStart } from '@kbn/share-plugin/server';
|
||||
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { rulesSettingsClientMock } from '../rules_settings_client.mock';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -138,6 +139,7 @@ describe('Task Runner Cancel', () => {
|
|||
max: 1000,
|
||||
},
|
||||
},
|
||||
getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClientMock.create()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -165,6 +167,9 @@ describe('Task Runner Cancel', () => {
|
|||
taskRunnerFactoryInitializerParams.executionContext.withContext.mockImplementation((ctx, fn) =>
|
||||
fn()
|
||||
);
|
||||
taskRunnerFactoryInitializerParams.getRulesSettingsClientWithRequest.mockReturnValue(
|
||||
rulesSettingsClientMock.create()
|
||||
);
|
||||
rulesClient.getAlertFromRaw.mockReturnValue(mockedRuleTypeSavedObject as Rule);
|
||||
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({
|
||||
|
|
|
@ -29,6 +29,7 @@ import { inMemoryMetricsMock } from '../monitoring/in_memory_metrics.mock';
|
|||
import { SharePluginStart } from '@kbn/share-plugin/server';
|
||||
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { rulesSettingsClientMock } from '../rules_settings_client.mock';
|
||||
|
||||
const inMemoryMetrics = inMemoryMetricsMock.create();
|
||||
const executionContext = executionContextServiceMock.createSetupContract();
|
||||
|
@ -115,6 +116,7 @@ describe('Task Runner Factory', () => {
|
|||
max: 1000,
|
||||
},
|
||||
},
|
||||
getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClientMock.create()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
RulesClientApi,
|
||||
RulesSettingsClientApi,
|
||||
} from '../types';
|
||||
import { TaskRunner } from './task_runner';
|
||||
import { NormalizedRuleType } from '../rule_type_registry';
|
||||
|
@ -61,6 +62,7 @@ export interface TaskRunnerContext {
|
|||
actionsConfigMap: ActionsConfigMap;
|
||||
cancelAlertsOnRuleTimeout: boolean;
|
||||
usageCounter?: UsageCounter;
|
||||
getRulesSettingsClientWithRequest(request: KibanaRequest): RulesSettingsClientApi;
|
||||
}
|
||||
|
||||
export class TaskRunnerFactory {
|
||||
|
|
|
@ -51,6 +51,7 @@ import {
|
|||
} from '../common';
|
||||
import { PublicAlertFactory } from './alert/create_alert_factory';
|
||||
import { FieldMap } from '../common/alert_schema/field_maps/types';
|
||||
import { RulesSettingsFlappingProperties } from '../common/rules_settings';
|
||||
export type WithoutQueryAndParams<T> = Pick<T, Exclude<keyof T, 'query' | 'params'>>;
|
||||
export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined;
|
||||
export type { RuleTypeParams };
|
||||
|
@ -111,6 +112,7 @@ export interface RuleExecutorOptions<
|
|||
startedAt: Date;
|
||||
state: State;
|
||||
namespace?: string;
|
||||
flappingSettings: RulesSettingsFlappingProperties;
|
||||
}
|
||||
|
||||
export interface RuleParamsAndRefs<Params extends RuleTypeParams> {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { IRuleDataClient } from '@kbn/rule-registry-plugin/server';
|
|||
import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks';
|
||||
import { PluginSetupContract as AlertingPluginSetupContract } from '@kbn/alerting-plugin/server';
|
||||
import { ObservabilityPluginSetup } from '@kbn/observability-plugin/server';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common';
|
||||
import { APMConfig, APM_SERVER_FEATURE_ID } from '../../..';
|
||||
|
||||
export const createRuleTypeMocks = () => {
|
||||
|
@ -80,6 +81,7 @@ export const createRuleTypeMocks = () => {
|
|||
ruleTypeName: 'ruleTypeName',
|
||||
},
|
||||
startedAt: new Date(),
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
} from './metric_threshold_executor';
|
||||
import { Evaluation } from './lib/evaluate_rule';
|
||||
import type { LogMeta, Logger } from '@kbn/logging';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common';
|
||||
|
||||
jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() }));
|
||||
|
||||
|
@ -116,6 +117,7 @@ const mockOptions = {
|
|||
ruleTypeName: '',
|
||||
},
|
||||
logger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
};
|
||||
|
||||
const setEvaluationResults = (response: Array<Record<string, Evaluation>>) => {
|
||||
|
|
|
@ -19,6 +19,7 @@ import { ISearchStartSearchSource } from '@kbn/data-plugin/public';
|
|||
import { MockedLogger } from '@kbn/logging-mocks';
|
||||
import { SanitizedRuleConfig } from '@kbn/alerting-plugin/common';
|
||||
import { Alert, RuleExecutorServices } from '@kbn/alerting-plugin/server';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings';
|
||||
import {
|
||||
ALERT_EVALUATION_THRESHOLD,
|
||||
ALERT_EVALUATION_VALUE,
|
||||
|
@ -118,6 +119,7 @@ describe('BurnRateRuleExecutor', () => {
|
|||
rule: {} as SanitizedRuleConfig,
|
||||
spaceId: 'irrelevant',
|
||||
state: {},
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(alertWithLifecycleMock).not.toBeCalled();
|
||||
|
@ -142,6 +144,7 @@ describe('BurnRateRuleExecutor', () => {
|
|||
rule: {} as SanitizedRuleConfig,
|
||||
spaceId: 'irrelevant',
|
||||
state: {},
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(alertWithLifecycleMock).not.toBeCalled();
|
||||
|
@ -166,6 +169,7 @@ describe('BurnRateRuleExecutor', () => {
|
|||
rule: {} as SanitizedRuleConfig,
|
||||
spaceId: 'irrelevant',
|
||||
state: {},
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(alertWithLifecycleMock).not.toBeCalled();
|
||||
|
@ -195,6 +199,7 @@ describe('BurnRateRuleExecutor', () => {
|
|||
rule: {} as SanitizedRuleConfig,
|
||||
spaceId: 'irrelevant',
|
||||
state: {},
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(alertWithLifecycleMock).toBeCalledWith({
|
||||
|
@ -242,6 +247,7 @@ describe('BurnRateRuleExecutor', () => {
|
|||
rule: {} as SanitizedRuleConfig,
|
||||
spaceId: 'irrelevant',
|
||||
state: {},
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(alertWithLifecycleMock).not.toBeCalled();
|
||||
|
|
|
@ -162,6 +162,7 @@ export const createLifecycleExecutor =
|
|||
const {
|
||||
services: { alertFactory, shouldWriteAlerts },
|
||||
state: previousState,
|
||||
flappingSettings,
|
||||
} = options;
|
||||
|
||||
const ruleDataClientWriter = await ruleDataClient.getWriter();
|
||||
|
@ -266,6 +267,7 @@ export const createLifecycleExecutor =
|
|||
const isActive = !isRecovered;
|
||||
|
||||
const flappingHistory = getUpdatedFlappingHistory<State>(
|
||||
flappingSettings,
|
||||
alertId,
|
||||
state,
|
||||
isNew,
|
||||
|
@ -290,7 +292,7 @@ export const createLifecycleExecutor =
|
|||
pendingRecoveredCount: 0,
|
||||
};
|
||||
|
||||
const flapping = isFlapping(flappingHistory, isCurrentlyFlapping);
|
||||
const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping);
|
||||
|
||||
const event: ParsedTechnicalFields & ParsedExperimentalFields = {
|
||||
...alertData?.fields,
|
||||
|
@ -329,7 +331,7 @@ export const createLifecycleExecutor =
|
|||
const newEventsToIndex = makeEventsDataMapFor(newAlertIds);
|
||||
const trackedRecoveredEventsToIndex = makeEventsDataMapFor(trackedAlertRecoveredIds);
|
||||
const allEventsToIndex = [
|
||||
...getAlertsForNotification(trackedEventsToIndex),
|
||||
...getAlertsForNotification(flappingSettings, trackedEventsToIndex),
|
||||
...newEventsToIndex,
|
||||
];
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import { createLifecycleRuleTypeFactory } from './create_lifecycle_rule_type_fac
|
|||
import { ISearchStartSearchSource } from '@kbn/data-plugin/common';
|
||||
import { SharePluginStart } from '@kbn/share-plugin/server';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings';
|
||||
|
||||
type RuleTestHelpers = ReturnType<typeof createRule>;
|
||||
|
||||
|
@ -138,6 +139,7 @@ function createRule(shouldWriteAlerts: boolean = true) {
|
|||
spaceId: 'spaceId',
|
||||
startedAt,
|
||||
state,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
})) ?? {}) as Record<string, any>);
|
||||
|
||||
previousStartedAt = startedAt;
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
DISABLE_FLAPPING_SETTINGS,
|
||||
} from '@kbn/alerting-plugin/common/rules_settings';
|
||||
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { getAlertsForNotification } from './get_alerts_for_notification';
|
||||
|
||||
describe('getAlertsForNotification', () => {
|
||||
|
@ -38,7 +43,8 @@ describe('getAlertsForNotification', () => {
|
|||
|
||||
test('should set pendingRecoveredCount to zero for all active alerts', () => {
|
||||
const trackedEvents = [alert4];
|
||||
expect(getAlertsForNotification(trackedEvents)).toMatchInlineSnapshot(`
|
||||
expect(getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, trackedEvents))
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"event": Object {
|
||||
|
@ -55,8 +61,9 @@ describe('getAlertsForNotification', () => {
|
|||
});
|
||||
|
||||
test('should not remove alerts if the num of recovered alerts is not at the limit', () => {
|
||||
const trackedEvents = [alert1, alert2, alert3];
|
||||
expect(getAlertsForNotification(trackedEvents)).toMatchInlineSnapshot(`
|
||||
const trackedEvents = cloneDeep([alert1, alert2, alert3]);
|
||||
expect(getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, trackedEvents))
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"event": Object {
|
||||
|
@ -82,4 +89,34 @@ describe('getAlertsForNotification', () => {
|
|||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('should reset counts and not modify alerts if flapping is disabled', () => {
|
||||
const trackedEvents = cloneDeep([alert1, alert2, alert3]);
|
||||
expect(getAlertsForNotification(DISABLE_FLAPPING_SETTINGS, trackedEvents))
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"event": Object {
|
||||
"kibana.alert.status": "recovered",
|
||||
},
|
||||
"flapping": true,
|
||||
"pendingRecoveredCount": 0,
|
||||
},
|
||||
Object {
|
||||
"event": Object {
|
||||
"kibana.alert.status": "recovered",
|
||||
},
|
||||
"flapping": false,
|
||||
"pendingRecoveredCount": 0,
|
||||
},
|
||||
Object {
|
||||
"event": Object {
|
||||
"kibana.alert.status": "recovered",
|
||||
},
|
||||
"flapping": true,
|
||||
"pendingRecoveredCount": 0,
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { MAX_FLAP_COUNT } from '@kbn/alerting-plugin/server/lib/flapping_utils';
|
||||
import { RulesSettingsFlappingProperties } from '@kbn/alerting-plugin/common/rules_settings';
|
||||
import {
|
||||
ALERT_END,
|
||||
ALERT_STATUS,
|
||||
|
@ -14,15 +14,21 @@ import {
|
|||
EVENT_ACTION,
|
||||
} from '@kbn/rule-data-utils';
|
||||
|
||||
export function getAlertsForNotification(trackedEventsToIndex: any[]) {
|
||||
export function getAlertsForNotification(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
trackedEventsToIndex: any[]
|
||||
) {
|
||||
return trackedEventsToIndex.map((trackedEvent) => {
|
||||
if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) {
|
||||
if (!flappingSettings.enabled || trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) {
|
||||
trackedEvent.pendingRecoveredCount = 0;
|
||||
} else if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED) {
|
||||
} else if (
|
||||
flappingSettings.enabled &&
|
||||
trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED
|
||||
) {
|
||||
if (trackedEvent.flapping) {
|
||||
const count = trackedEvent.pendingRecoveredCount || 0;
|
||||
trackedEvent.pendingRecoveredCount = count + 1;
|
||||
if (trackedEvent.pendingRecoveredCount < MAX_FLAP_COUNT) {
|
||||
if (trackedEvent.pendingRecoveredCount < flappingSettings.statusChangeThreshold) {
|
||||
trackedEvent.event[ALERT_STATUS] = ALERT_STATUS_ACTIVE;
|
||||
trackedEvent.event[EVENT_ACTION] = 'active';
|
||||
delete trackedEvent.event[ALERT_END];
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
DISABLE_FLAPPING_SETTINGS,
|
||||
} from '@kbn/alerting-plugin/common/rules_settings';
|
||||
import { getUpdatedFlappingHistory } from './get_updated_flapping_history';
|
||||
|
||||
describe('getUpdatedFlappingHistory', () => {
|
||||
|
@ -17,8 +21,17 @@ describe('getUpdatedFlappingHistory', () => {
|
|||
|
||||
test('sets flapping state to true if the alert is new', () => {
|
||||
const state = { wrapped: initialRuleState, trackedAlerts: {}, trackedAlertsRecovered: {} };
|
||||
expect(getUpdatedFlappingHistory('TEST_ALERT_0', state, true, false, false, []))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getUpdatedFlappingHistory(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'TEST_ALERT_0',
|
||||
state,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
[]
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
true,
|
||||
]
|
||||
|
@ -40,8 +53,17 @@ describe('getUpdatedFlappingHistory', () => {
|
|||
},
|
||||
trackedAlertsRecovered: {},
|
||||
};
|
||||
expect(getUpdatedFlappingHistory('TEST_ALERT_0', state, false, false, true, []))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getUpdatedFlappingHistory(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'TEST_ALERT_0',
|
||||
state,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
[]
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
false,
|
||||
]
|
||||
|
@ -64,8 +86,17 @@ describe('getUpdatedFlappingHistory', () => {
|
|||
trackedAlerts: {},
|
||||
};
|
||||
const recoveredIds = ['TEST_ALERT_0'];
|
||||
expect(getUpdatedFlappingHistory('TEST_ALERT_0', state, true, false, true, recoveredIds))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getUpdatedFlappingHistory(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'TEST_ALERT_0',
|
||||
state,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
recoveredIds
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
true,
|
||||
]
|
||||
|
@ -89,8 +120,17 @@ describe('getUpdatedFlappingHistory', () => {
|
|||
trackedAlertsRecovered: {},
|
||||
};
|
||||
const recoveredIds = ['TEST_ALERT_0'];
|
||||
expect(getUpdatedFlappingHistory('TEST_ALERT_0', state, false, true, false, recoveredIds))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getUpdatedFlappingHistory(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'TEST_ALERT_0',
|
||||
state,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
recoveredIds
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
true,
|
||||
]
|
||||
|
@ -98,7 +138,7 @@ describe('getUpdatedFlappingHistory', () => {
|
|||
expect(recoveredIds).toEqual(['TEST_ALERT_0']);
|
||||
});
|
||||
|
||||
test('sets flapping state to true on an alert that is still recovered', () => {
|
||||
test('sets flapping state to false on an alert that is still recovered', () => {
|
||||
const state = {
|
||||
wrapped: initialRuleState,
|
||||
trackedAlerts: {},
|
||||
|
@ -114,12 +154,49 @@ describe('getUpdatedFlappingHistory', () => {
|
|||
},
|
||||
};
|
||||
const recoveredIds = ['TEST_ALERT_0'];
|
||||
expect(getUpdatedFlappingHistory('TEST_ALERT_0', state, false, true, false, recoveredIds))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getUpdatedFlappingHistory(
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
'TEST_ALERT_0',
|
||||
state,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
recoveredIds
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
false,
|
||||
]
|
||||
`);
|
||||
expect(recoveredIds).toEqual(['TEST_ALERT_0']);
|
||||
});
|
||||
|
||||
test('does not set flapping state if flapping is not enabled', () => {
|
||||
const state = {
|
||||
wrapped: initialRuleState,
|
||||
trackedAlerts: {},
|
||||
trackedAlertsRecovered: {
|
||||
TEST_ALERT_0: {
|
||||
alertId: 'TEST_ALERT_0',
|
||||
alertUuid: 'TEST_ALERT_0_UUID',
|
||||
started: '2020-01-01T12:00:00.000Z',
|
||||
flappingHistory: [],
|
||||
flapping: false,
|
||||
pendingRecoveredCount: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(
|
||||
getUpdatedFlappingHistory(
|
||||
DISABLE_FLAPPING_SETTINGS,
|
||||
'TEST_ALERT_0',
|
||||
state,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
['TEST_ALERT_0']
|
||||
)
|
||||
).toMatchInlineSnapshot(`Array []`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
*/
|
||||
|
||||
import { RuleTypeState } from '@kbn/alerting-plugin/common';
|
||||
import { RulesSettingsFlappingProperties } from '@kbn/alerting-plugin/common/rules_settings';
|
||||
import { updateFlappingHistory } from '@kbn/alerting-plugin/server/lib';
|
||||
import { remove } from 'lodash';
|
||||
import { WrappedLifecycleRuleState } from './create_lifecycle_executor';
|
||||
|
||||
export function getUpdatedFlappingHistory<State extends RuleTypeState = never>(
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
alertId: string,
|
||||
state: WrappedLifecycleRuleState<State>,
|
||||
isNew: boolean,
|
||||
|
@ -20,31 +22,43 @@ export function getUpdatedFlappingHistory<State extends RuleTypeState = never>(
|
|||
) {
|
||||
// duplicating this logic to determine flapping at this level
|
||||
let flappingHistory: boolean[] = [];
|
||||
if (isRecovered) {
|
||||
if (state.trackedAlerts[alertId]) {
|
||||
// this alert has flapped from active to recovered
|
||||
flappingHistory = updateFlappingHistory(state.trackedAlerts[alertId].flappingHistory, true);
|
||||
} else if (state.trackedAlertsRecovered[alertId]) {
|
||||
// this alert is still recovered
|
||||
if (flappingSettings.enabled) {
|
||||
if (isRecovered) {
|
||||
if (state.trackedAlerts[alertId]) {
|
||||
// this alert has flapped from active to recovered
|
||||
flappingHistory = updateFlappingHistory(
|
||||
flappingSettings,
|
||||
state.trackedAlerts[alertId].flappingHistory,
|
||||
true
|
||||
);
|
||||
} else if (state.trackedAlertsRecovered[alertId]) {
|
||||
// this alert is still recovered
|
||||
flappingHistory = updateFlappingHistory(
|
||||
flappingSettings,
|
||||
state.trackedAlertsRecovered[alertId].flappingHistory,
|
||||
false
|
||||
);
|
||||
}
|
||||
} else if (isNew) {
|
||||
if (state.trackedAlertsRecovered[alertId]) {
|
||||
// this alert has flapped from recovered to active
|
||||
flappingHistory = updateFlappingHistory(
|
||||
flappingSettings,
|
||||
state.trackedAlertsRecovered[alertId].flappingHistory,
|
||||
true
|
||||
);
|
||||
remove(recoveredIds, (id) => id === alertId);
|
||||
} else {
|
||||
flappingHistory = updateFlappingHistory(flappingSettings, [], true);
|
||||
}
|
||||
} else if (isActive) {
|
||||
// this alert is still active
|
||||
flappingHistory = updateFlappingHistory(
|
||||
state.trackedAlertsRecovered[alertId].flappingHistory,
|
||||
flappingSettings,
|
||||
state.trackedAlerts[alertId].flappingHistory,
|
||||
false
|
||||
);
|
||||
}
|
||||
} else if (isNew) {
|
||||
if (state.trackedAlertsRecovered[alertId]) {
|
||||
// this alert has flapped from recovered to active
|
||||
flappingHistory = updateFlappingHistory(
|
||||
state.trackedAlertsRecovered[alertId].flappingHistory,
|
||||
true
|
||||
);
|
||||
remove(recoveredIds, (id) => id === alertId);
|
||||
} else {
|
||||
flappingHistory = updateFlappingHistory([], true);
|
||||
}
|
||||
} else if (isActive) {
|
||||
// this alert is still active
|
||||
flappingHistory = updateFlappingHistory(state.trackedAlerts[alertId].flappingHistory, false);
|
||||
}
|
||||
return flappingHistory;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { searchSourceCommonMock } from '@kbn/data-plugin/common/search/search_so
|
|||
import { Logger } from '@kbn/logging';
|
||||
import { SharePluginStart } from '@kbn/share-plugin/server';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings';
|
||||
|
||||
export const createDefaultAlertExecutorOptions = <
|
||||
Params extends RuleTypeParams = never,
|
||||
|
@ -87,4 +88,5 @@ export const createDefaultAlertExecutorOptions = <
|
|||
namespace: undefined,
|
||||
executionId: 'b33f65d7-6e8b-4aae-8d20-c93613deb33f',
|
||||
logger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import type { RuleExecutorServicesMock } from '@kbn/alerting-plugin/server/mocks';
|
||||
import { alertsMock } from '@kbn/alerting-plugin/server/mocks';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common';
|
||||
|
||||
import { getRuleMock } from '../../../routes/__mocks__/request_responses';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
|
@ -67,6 +68,7 @@ describe('legacyRules_notification_alert_type', () => {
|
|||
notifyWhen: null,
|
||||
},
|
||||
logger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
};
|
||||
|
||||
alert = legacyRulesNotificationAlertType({
|
||||
|
|
|
@ -15,7 +15,7 @@ import type {
|
|||
AlertInstanceState,
|
||||
RuleTypeState,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { parseDuration } from '@kbn/alerting-plugin/common';
|
||||
import { parseDuration, DISABLE_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common';
|
||||
import type { ExecutorType } from '@kbn/alerting-plugin/server/types';
|
||||
import type { Alert } from '@kbn/alerting-plugin/server';
|
||||
|
||||
|
@ -263,6 +263,7 @@ export const previewRulesRoute = async (
|
|||
startedAt: startedAt.toDate(),
|
||||
state: statePreview,
|
||||
logger,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
})) as { state: TState });
|
||||
|
||||
const errors = loggedStatusChanges
|
||||
|
|
|
@ -24,6 +24,7 @@ import { ActionGroupId, ConditionMetAlertInstanceId } from './constants';
|
|||
import { OnlyEsQueryRuleParams, OnlySearchSourceRuleParams } from './types';
|
||||
import { searchSourceInstanceMock } from '@kbn/data-plugin/common/search/search_source/mocks';
|
||||
import { Comparator } from '../../../common/comparator_types';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings';
|
||||
|
||||
const logger = loggingSystemMock.create().get();
|
||||
const coreSetup = coreMock.createSetup();
|
||||
|
@ -726,5 +727,6 @@ async function invokeExecutor({
|
|||
notifyWhen: null,
|
||||
},
|
||||
logger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { Params } from './rule_type_params';
|
|||
import { TIME_SERIES_BUCKET_SELECTOR_FIELD } from '@kbn/triggers-actions-ui-plugin/server';
|
||||
import { RuleExecutorServicesMock, alertsMock } from '@kbn/alerting-plugin/server/mocks';
|
||||
import { Comparator } from '../../../common/comparator_types';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings';
|
||||
|
||||
let fakeTimer: sinon.SinonFakeTimers;
|
||||
|
||||
|
@ -217,6 +218,7 @@ describe('ruleType', () => {
|
|||
notifyWhen: null,
|
||||
},
|
||||
logger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(alertServices.alertFactory.create).toHaveBeenCalledWith('all documents');
|
||||
|
@ -280,6 +282,7 @@ describe('ruleType', () => {
|
|||
notifyWhen: null,
|
||||
},
|
||||
logger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(customAlertServices.alertFactory.create).not.toHaveBeenCalled();
|
||||
|
@ -343,6 +346,7 @@ describe('ruleType', () => {
|
|||
notifyWhen: null,
|
||||
},
|
||||
logger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(customAlertServices.alertFactory.create).not.toHaveBeenCalled();
|
||||
|
@ -405,6 +409,7 @@ describe('ruleType', () => {
|
|||
notifyWhen: null,
|
||||
},
|
||||
logger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
});
|
||||
|
||||
expect(data.timeSeriesQuery).toHaveBeenCalledWith(
|
||||
|
|
|
@ -19,3 +19,4 @@ export * from './test_assertions';
|
|||
export { checkAAD } from './check_aad';
|
||||
export { getEventLog } from './get_event_log';
|
||||
export { createWaitForExecutionCount } from './wait_for_execution_count';
|
||||
export { resetRulesSettings } from './reset_rules_settings';
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 } from '@kbn/alerting-plugin/common';
|
||||
import { Superuser } from '../../security_and_spaces/scenarios';
|
||||
import { getUrlPrefix } from './space_test_utils';
|
||||
|
||||
export const resetRulesSettings = (supertest: any, space: string) => {
|
||||
return supertest
|
||||
.post(`${getUrlPrefix(space)}/internal/alerting/rules/settings/_flapping`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth(Superuser.username, Superuser.password)
|
||||
.send(DEFAULT_FLAPPING_SETTINGS)
|
||||
.expect(200);
|
||||
};
|
|
@ -8,7 +8,7 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common';
|
||||
import { UserAtSpaceScenarios } from '../../../scenarios';
|
||||
import { getUrlPrefix } from '../../../../common/lib';
|
||||
import { getUrlPrefix, resetRulesSettings } from '../../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@ -16,6 +16,16 @@ export default function getFlappingSettingsTests({ getService }: FtrProviderCont
|
|||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
|
||||
describe('getFlappingSettings', () => {
|
||||
beforeEach(async () => {
|
||||
await resetRulesSettings(supertestWithoutAuth, 'space1');
|
||||
await resetRulesSettings(supertestWithoutAuth, 'space2');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await resetRulesSettings(supertestWithoutAuth, 'space1');
|
||||
await resetRulesSettings(supertestWithoutAuth, 'space2');
|
||||
});
|
||||
|
||||
for (const scenario of UserAtSpaceScenarios) {
|
||||
const { user, space } = scenario;
|
||||
describe(scenario.id, () => {
|
||||
|
|
|
@ -6,19 +6,10 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common';
|
||||
import { UserAtSpaceScenarios, Superuser } from '../../../scenarios';
|
||||
import { getUrlPrefix } from '../../../../common/lib';
|
||||
import { getUrlPrefix, resetRulesSettings } from '../../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
|
||||
const resetRulesSettings = (supertestWithoutAuth: any, space: string) => {
|
||||
return supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(space)}/internal/alerting/rules/settings/_flapping`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth(Superuser.username, Superuser.password)
|
||||
.send(DEFAULT_FLAPPING_SETTINGS);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function updateFlappingSettingsTest({ getService }: FtrProviderContext) {
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
|
|
|
@ -9,7 +9,13 @@ import expect from '@kbn/expect';
|
|||
import { IValidatedEvent, nanosToMillis } from '@kbn/event-log-plugin/server';
|
||||
import { ESTestIndexTool } from '@kbn/alerting-api-integration-helpers';
|
||||
import { Spaces } from '../../../scenarios';
|
||||
import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../../common/lib';
|
||||
import {
|
||||
getUrlPrefix,
|
||||
getTestRuleData,
|
||||
ObjectRemover,
|
||||
getEventLog,
|
||||
resetRulesSettings,
|
||||
} from '../../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@ -23,10 +29,17 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
const objectRemover = new ObjectRemover(supertest);
|
||||
|
||||
beforeEach(async () => {
|
||||
await resetRulesSettings(supertest, Spaces.default.id);
|
||||
await resetRulesSettings(supertest, Spaces.space1.id);
|
||||
await esTestIndexTool.destroy();
|
||||
await esTestIndexTool.setup();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await resetRulesSettings(supertest, Spaces.default.id);
|
||||
await resetRulesSettings(supertest, Spaces.space1.id);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await objectRemover.removeAll();
|
||||
});
|
||||
|
@ -527,6 +540,16 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should generate expected events for flapping alerts that are mainly active', async () => {
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth('superuser', 'superuser')
|
||||
.send({
|
||||
enabled: true,
|
||||
lookBackWindow: 3,
|
||||
statusChangeThreshold: 2,
|
||||
})
|
||||
.expect(200);
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
|
@ -539,7 +562,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
.expect(200);
|
||||
|
||||
// pattern of when the alert should fire
|
||||
const instance = [true, false, true, false].concat(new Array(22).fill(true));
|
||||
const instance = [true, false, true, true, true, true, true];
|
||||
const pattern = {
|
||||
instance,
|
||||
};
|
||||
|
@ -579,11 +602,95 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
provider: 'alerting',
|
||||
actions: new Map([
|
||||
// make sure the counts of the # of events per type are as expected
|
||||
['execute-start', { gte: 25 }],
|
||||
['execute', { gte: 25 }],
|
||||
['execute-action', { equal: 25 }],
|
||||
['execute-start', { gte: 6 }],
|
||||
['execute', { gte: 6 }],
|
||||
['execute-action', { equal: 7 }],
|
||||
['new-instance', { equal: 1 }],
|
||||
['active-instance', { gte: 6 }],
|
||||
['recovered-instance', { equal: 1 }],
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
const flapping = events
|
||||
.filter(
|
||||
(event) =>
|
||||
event?.event?.action === 'active-instance' ||
|
||||
event?.event?.action === 'recovered-instance'
|
||||
)
|
||||
.map((event) => event?.kibana?.alert?.flapping);
|
||||
const result = [false, true, true, true, false, false, false, false];
|
||||
expect(flapping).to.eql(result);
|
||||
});
|
||||
|
||||
it('should generate expected events for flapping alerts that are mainly recovered', async () => {
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/internal/alerting/rules/settings/_flapping`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth('superuser', 'superuser')
|
||||
.send({
|
||||
enabled: true,
|
||||
lookBackWindow: 3,
|
||||
statusChangeThreshold: 2,
|
||||
})
|
||||
.expect(200);
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'MY action',
|
||||
connector_type_id: 'test.noop',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
// pattern of when the alert should fire
|
||||
const instance = [true, false, true, false, false, false, true];
|
||||
const pattern = {
|
||||
instance,
|
||||
};
|
||||
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
rule_type_id: 'test.patternFiring',
|
||||
schedule: { interval: '1s' },
|
||||
throttle: null,
|
||||
params: {
|
||||
pattern,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.eql(200);
|
||||
const alertId = response.body.id;
|
||||
objectRemover.add(space.id, alertId, 'rule', 'alerting');
|
||||
|
||||
// get the events we're expecting
|
||||
const events = await retry.try(async () => {
|
||||
return await getEventLog({
|
||||
getService,
|
||||
spaceId: space.id,
|
||||
type: 'alert',
|
||||
id: alertId,
|
||||
provider: 'alerting',
|
||||
actions: new Map([
|
||||
// make sure the counts of the # of events per type are as expected
|
||||
['execute-start', { gte: 6 }],
|
||||
['execute', { gte: 6 }],
|
||||
['execute-action', { equal: 6 }],
|
||||
['new-instance', { equal: 2 }],
|
||||
['active-instance', { gte: 25 }],
|
||||
['active-instance', { gte: 6 }],
|
||||
['recovered-instance', { equal: 2 }],
|
||||
]),
|
||||
});
|
||||
|
@ -596,96 +703,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
event?.event?.action === 'recovered-instance'
|
||||
)
|
||||
.map((event) => event?.kibana?.alert?.flapping);
|
||||
const result = [false, false, false]
|
||||
.concat(new Array(20).fill(true))
|
||||
.concat([false, false, false, false]);
|
||||
expect(flapping).to.eql(result);
|
||||
});
|
||||
|
||||
it('should generate expected events for flapping alerts that are mainly recovered', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'MY action',
|
||||
connector_type_id: 'test.noop',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
// pattern of when the alert should fire
|
||||
const instance = [true, false, true].concat(new Array(18).fill(false)).concat(true);
|
||||
const pattern = {
|
||||
instance,
|
||||
};
|
||||
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
rule_type_id: 'test.patternFiring',
|
||||
schedule: { interval: '1s' },
|
||||
throttle: null,
|
||||
params: {
|
||||
pattern,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.eql(200);
|
||||
const alertId = response.body.id;
|
||||
objectRemover.add(space.id, alertId, 'rule', 'alerting');
|
||||
|
||||
// get the events we're expecting
|
||||
const events = await retry.try(async () => {
|
||||
return await getEventLog({
|
||||
getService,
|
||||
spaceId: space.id,
|
||||
type: 'alert',
|
||||
id: alertId,
|
||||
provider: 'alerting',
|
||||
actions: new Map([
|
||||
// make sure the counts of the # of events per type are as expected
|
||||
['execute-start', { gte: 20 }],
|
||||
['execute', { gte: 20 }],
|
||||
['execute-action', { equal: 9 }],
|
||||
['new-instance', { equal: 3 }],
|
||||
['active-instance', { gte: 9 }],
|
||||
['recovered-instance', { equal: 3 }],
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
const flapping = events
|
||||
.filter(
|
||||
(event) =>
|
||||
event?.event?.action === 'active-instance' ||
|
||||
event?.event?.action === 'recovered-instance'
|
||||
)
|
||||
.map((event) => event?.kibana?.alert?.flapping);
|
||||
expect(flapping).to.eql([
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
]);
|
||||
expect(flapping).to.eql([false, true, true, true, true, true, true, true]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
RuleDataService,
|
||||
} from '@kbn/rule-registry-plugin/server';
|
||||
import { RuleExecutorOptions } from '@kbn/alerting-plugin/server';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings';
|
||||
import { get } from 'lodash';
|
||||
import type { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import {
|
||||
|
@ -171,6 +172,7 @@ export default function createGetSummarizedAlertsTest({ getService }: FtrProvide
|
|||
alertFactory: { create: sinon.stub() },
|
||||
shouldWriteAlerts: sinon.stub().returns(true),
|
||||
},
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
} as unknown as RuleExecutorOptions<
|
||||
MockRuleParams,
|
||||
WrappedLifecycleRuleState<{ shouldTriggerAlert: boolean }>,
|
||||
|
@ -329,6 +331,7 @@ export default function createGetSummarizedAlertsTest({ getService }: FtrProvide
|
|||
alertFactory: { create: sinon.stub() },
|
||||
shouldWriteAlerts: sinon.stub().returns(true),
|
||||
},
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
} as unknown as RuleExecutorOptions<
|
||||
MockRuleParams,
|
||||
WrappedLifecycleRuleState<{ shouldTriggerAlert: boolean }>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue