mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
Onboard alerting tasks to use stateSchemaByVersion for task state validation of the framework fields (#162425)
Resolves https://github.com/elastic/kibana/issues/159343 In this PR, I'm preparing the alerting rule task types for serverless by defining an explicit task state schema for the framework level fields. This schema is used to validate the task's state before saving but also when reading. In the scenario an older Kibana node runs a task after a newer Kibana node has stored additional task state, the unknown state properties will be dropped. Additionally, this will prompt developers to be aware that adding required fields to the task state is a breaking change that must be handled with care. (see https://github.com/elastic/kibana/issues/155764). The PR also includes the following changes: - Modifying the `@kbn/alerting-state-types` package so the types are generated from `config-schema`, instead of `io-ts`. The schema is re-used for exporting a new `stateSchemaByVersion` property. - Removing `DateFromString` in favour of strings everywhere (config-schema doesn't support this conversion) - Add a v1 `up` migration that will ensure the types match the TypeScript interface on any existing alerting task. The migration assumes any type of data could exist and dropping parts that don't match the type expectation. The TypeScript interface uses `schema.maybe()` in a lot of places (as `io-ts` did), so safe if ever data gets dropped. - Cleanup the `alerting/common/**` exports to reduce bundle size. Because the `@kbn/alerting-state-types` package grew. - Since the new TypeScript interfaces / types are `ReadOnly<...>`, I created some `Mutable...` types for places that needed it (in order to avoid code refactoring). ## To verify Stack Monitoring: - To make TypeScript happy with the new ReadOnly `RawAlertInstance` type, I removed some redundant code and solved the issue. Security Solution: - Changes to the `alertInstanceFactoryStub` set the alert's date to a `string` instead of a `Date` value. Note: The HTTP API response converted `Date` objects to `string`, so the HTTP API response will look the same with this change. Response Ops: - In a fresh Kibana install, create alerting rules and ensure they run. - In a 8.9 version, create some rules, upgrade to this branch and ensure they still run. - Compare the `io-ts` definition with the new `config-schema`. They should match 1:1. - Look for ways the migration code could fail. Note: The main changes are within the following areas: - `x-pack/plugins/alerting/server/rule_type_registry.ts` - `x-pack/packages/kbn-alerting-state-types/*` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
55fb29716e
commit
82f551c3d4
46 changed files with 619 additions and 458 deletions
|
@ -5,20 +5,22 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export type {
|
||||
ThrottledActions,
|
||||
LastScheduledActions,
|
||||
AlertInstanceMeta,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
RawAlertInstance,
|
||||
} from './src/alert_instance';
|
||||
export { rawAlertInstance } from './src/alert_instance';
|
||||
|
||||
export { DateFromString } from './src/date_from_string';
|
||||
export type { AlertInstanceContext } from './src/alert_instance';
|
||||
|
||||
export type { TrackedLifecycleAlertState, WrappedLifecycleRuleState } from './src/lifecycle_state';
|
||||
export { wrappedStateRt } from './src/lifecycle_state';
|
||||
|
||||
export type { RuleTaskState, RuleTaskParams } from './src/rule_task_instance';
|
||||
export { ActionsCompletion, ruleStateSchema, ruleParamsSchema } from './src/rule_task_instance';
|
||||
export type { RuleTaskParams } from './src/rule_task_instance';
|
||||
export { ActionsCompletion, ruleParamsSchema } from './src/rule_task_instance';
|
||||
|
||||
export type {
|
||||
LatestTaskStateSchema as RuleTaskState,
|
||||
MutableLatestTaskStateSchema as MutableRuleTaskState,
|
||||
LatestRawAlertInstanceSchema as RawAlertInstance,
|
||||
LatestAlertInstanceMetaSchema as AlertInstanceMeta,
|
||||
MutableLatestAlertInstanceMetaSchema as MutableAlertInstanceMeta,
|
||||
LatestAlertInstanceStateSchema as AlertInstanceState,
|
||||
LatestThrottledActionSchema as ThrottledActions,
|
||||
LatestLastScheduledActionsSchema as LastScheduledActions,
|
||||
} from './src/task_state';
|
||||
export { stateSchemaByVersion, emptyState as emptyTaskState } from './src/task_state';
|
||||
|
|
|
@ -6,50 +6,6 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { DateFromString } from './date_from_string';
|
||||
|
||||
const actionSchema = t.type({
|
||||
date: DateFromString,
|
||||
});
|
||||
|
||||
export const throttledActionSchema = t.record(t.string, actionSchema);
|
||||
export type ThrottledActions = t.TypeOf<typeof throttledActionSchema>;
|
||||
|
||||
const lastScheduledActionsSchema = t.intersection([
|
||||
t.partial({
|
||||
subgroup: t.string,
|
||||
}),
|
||||
t.type({
|
||||
group: t.string,
|
||||
date: DateFromString,
|
||||
}),
|
||||
t.partial({ actions: throttledActionSchema }),
|
||||
]);
|
||||
|
||||
export type LastScheduledActions = t.TypeOf<typeof lastScheduledActionsSchema>;
|
||||
|
||||
const metaSchema = t.partial({
|
||||
lastScheduledActions: lastScheduledActionsSchema,
|
||||
// an array used to track changes in alert state, the order is based on the rule executions (oldest to most recent)
|
||||
// true - alert has changed from active/recovered
|
||||
// false - the status has remained either active or recovered
|
||||
flappingHistory: t.array(t.boolean),
|
||||
// flapping flag that indicates whether the alert is flapping
|
||||
flapping: t.boolean,
|
||||
maintenanceWindowIds: t.array(t.string),
|
||||
pendingRecoveredCount: t.number,
|
||||
uuid: t.string,
|
||||
});
|
||||
export type AlertInstanceMeta = t.TypeOf<typeof metaSchema>;
|
||||
|
||||
const stateSchema = t.record(t.string, t.unknown);
|
||||
export type AlertInstanceState = t.TypeOf<typeof stateSchema>;
|
||||
|
||||
const contextSchema = t.record(t.string, t.unknown);
|
||||
export type AlertInstanceContext = t.TypeOf<typeof contextSchema>;
|
||||
|
||||
export const rawAlertInstance = t.partial({
|
||||
state: stateSchema,
|
||||
meta: metaSchema,
|
||||
});
|
||||
export type RawAlertInstance = t.TypeOf<typeof rawAlertInstance>;
|
||||
|
|
|
@ -1,29 +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 { DateFromString } from './date_from_string';
|
||||
import { right, isLeft } from 'fp-ts/lib/Either';
|
||||
|
||||
describe('DateFromString', () => {
|
||||
test('validated and parses a string into a Date', () => {
|
||||
const date = new Date(1973, 10, 30);
|
||||
expect(DateFromString.decode(date.toISOString())).toEqual(right(date));
|
||||
});
|
||||
|
||||
test('validated and returns a failure for an actual Date', () => {
|
||||
const date = new Date(1973, 10, 30);
|
||||
expect(isLeft(DateFromString.decode(date))).toEqual(true);
|
||||
});
|
||||
|
||||
test('validated and returns a failure for an invalid Date string', () => {
|
||||
expect(isLeft(DateFromString.decode('1234-23-45'))).toEqual(true);
|
||||
});
|
||||
|
||||
test('validated and returns a failure for a null value', () => {
|
||||
expect(isLeft(DateFromString.decode(null))).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -1,27 +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 * as t from 'io-ts';
|
||||
import { either } from 'fp-ts/lib/Either';
|
||||
|
||||
// represents a Date from an ISO string
|
||||
export const DateFromString = new t.Type<Date, string, unknown>(
|
||||
'DateFromString',
|
||||
// detect the type
|
||||
(value): value is Date => value instanceof Date,
|
||||
(valueToDecode, context) =>
|
||||
either.chain(
|
||||
// validate this is a string
|
||||
t.string.validate(valueToDecode, context),
|
||||
// decode
|
||||
(value) => {
|
||||
const decoded = new Date(value);
|
||||
return isNaN(decoded.getTime()) ? t.failure(valueToDecode, context) : t.success(decoded);
|
||||
}
|
||||
),
|
||||
(valueToEncode) => valueToEncode.toISOString()
|
||||
);
|
|
@ -6,27 +6,12 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { throttledActionSchema, rawAlertInstance } from './alert_instance';
|
||||
import { DateFromString } from './date_from_string';
|
||||
|
||||
export enum ActionsCompletion {
|
||||
COMPLETE = 'complete',
|
||||
PARTIAL = 'partial',
|
||||
}
|
||||
|
||||
export const ruleStateSchema = t.partial({
|
||||
alertTypeState: t.record(t.string, t.unknown),
|
||||
// tracks the active alerts
|
||||
alertInstances: t.record(t.string, rawAlertInstance),
|
||||
// tracks the recovered alerts for flapping purposes
|
||||
alertRecoveredInstances: t.record(t.string, rawAlertInstance),
|
||||
previousStartedAt: t.union([t.null, DateFromString]),
|
||||
summaryActions: throttledActionSchema,
|
||||
});
|
||||
|
||||
// This is serialized in the rule task document
|
||||
export type RuleTaskState = t.TypeOf<typeof ruleStateSchema>;
|
||||
|
||||
export const ruleParamsSchema = t.intersection([
|
||||
t.type({
|
||||
alertId: t.string,
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { type TypeOf } from '@kbn/config-schema';
|
||||
import * as v1 from './v1';
|
||||
|
||||
export const stateSchemaByVersion = {
|
||||
1: v1.versionDefinition,
|
||||
};
|
||||
|
||||
const latest = v1;
|
||||
/**
|
||||
* WARNING: Do not modify the code below when doing a new version.
|
||||
* Update the "latest" variable instead.
|
||||
*/
|
||||
const latestTaskStateSchema = latest.versionDefinition.schema;
|
||||
export type LatestTaskStateSchema = TypeOf<typeof latestTaskStateSchema>;
|
||||
export type LatestRawAlertInstanceSchema = TypeOf<typeof latest.rawAlertInstanceSchema>;
|
||||
export type LatestAlertInstanceMetaSchema = TypeOf<typeof latest.metaSchema>;
|
||||
export type LatestAlertInstanceStateSchema = TypeOf<typeof latest.alertStateSchema>;
|
||||
export type LatestThrottledActionSchema = TypeOf<typeof latest.throttledActionSchema>;
|
||||
export type LatestLastScheduledActionsSchema = TypeOf<typeof latest.lastScheduledActionsSchema>;
|
||||
|
||||
export const emptyState: LatestTaskStateSchema = {
|
||||
alertTypeState: {},
|
||||
alertInstances: {},
|
||||
alertRecoveredInstances: {},
|
||||
previousStartedAt: null,
|
||||
summaryActions: {},
|
||||
};
|
||||
|
||||
type Mutable<T> = {
|
||||
-readonly [k in keyof T]: Mutable<T[k]>;
|
||||
};
|
||||
export type MutableLatestTaskStateSchema = Mutable<LatestTaskStateSchema>;
|
||||
export type MutableLatestAlertInstanceMetaSchema = Mutable<LatestAlertInstanceMetaSchema>;
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { isPlainObject } from 'lodash';
|
||||
|
||||
export function isJSONObject(obj: unknown): obj is Record<string, unknown> {
|
||||
return isPlainObject(obj);
|
||||
}
|
||||
|
||||
export function isString(value: unknown): value is string {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
export function isBoolean(value: unknown): value is boolean {
|
||||
return typeof value === 'boolean';
|
||||
}
|
||||
|
||||
export function isNumber(value: unknown): value is number {
|
||||
return typeof value === 'number';
|
||||
}
|
||||
|
||||
export function isStringArray(value: unknown): value is string[] {
|
||||
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
||||
}
|
||||
|
||||
export function isBooleanArray(value: unknown): value is boolean[] {
|
||||
return Array.isArray(value) && value.every((item) => typeof item === 'boolean');
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { upMigration } from './migration';
|
||||
import { versionSchema } from './schema';
|
||||
|
||||
export {
|
||||
versionSchema,
|
||||
throttledActionSchema,
|
||||
rawAlertInstanceSchema,
|
||||
metaSchema,
|
||||
alertStateSchema,
|
||||
lastScheduledActionsSchema,
|
||||
} from './schema';
|
||||
|
||||
export const versionDefinition = {
|
||||
up: upMigration,
|
||||
schema: versionSchema,
|
||||
};
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* 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 {
|
||||
migrateThrottledActions,
|
||||
migrateLastScheduledActions,
|
||||
migrateMeta,
|
||||
migrateAlertInstances,
|
||||
upMigration,
|
||||
} from './migration';
|
||||
|
||||
describe('migrateThrottledActions', () => {
|
||||
it('should return undefined if input is not an object', () => {
|
||||
const result = migrateThrottledActions(null);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the migrated throttledActions object', () => {
|
||||
const input = {
|
||||
key1: { date: '2023-07-31T12:00:00Z' },
|
||||
key2: { date: '2023-07-30T12:00:00Z' },
|
||||
key3: 'notAnObject',
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
key1: { date: '2023-07-31T12:00:00Z' },
|
||||
key2: { date: '2023-07-30T12:00:00Z' },
|
||||
};
|
||||
|
||||
const result = migrateThrottledActions(input);
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('migrateLastScheduledActions', () => {
|
||||
it('should return undefined if input is not a valid lastScheduledActions object', () => {
|
||||
const result = migrateLastScheduledActions({ group: 'group1' }); // Missing 'date' property
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the migrated lastScheduledActions object', () => {
|
||||
const input = {
|
||||
group: 'group1',
|
||||
subgroup: 'subgroup1',
|
||||
date: '2023-07-31T12:00:00Z',
|
||||
actions: {
|
||||
key1: { date: '2023-07-31T12:00:00Z' },
|
||||
key2: { date: '2023-07-30T12:00:00Z' },
|
||||
},
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
group: 'group1',
|
||||
subgroup: 'subgroup1',
|
||||
date: '2023-07-31T12:00:00Z',
|
||||
actions: {
|
||||
key1: { date: '2023-07-31T12:00:00Z' },
|
||||
key2: { date: '2023-07-30T12:00:00Z' },
|
||||
},
|
||||
};
|
||||
|
||||
const result = migrateLastScheduledActions(input);
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('migrateMeta', () => {
|
||||
it('should return undefined if input is not an object', () => {
|
||||
const result = migrateMeta(null);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the migrated meta object', () => {
|
||||
const input = {
|
||||
lastScheduledActions: {
|
||||
group: 'group1',
|
||||
date: '2023-07-31T12:00:00Z',
|
||||
},
|
||||
flappingHistory: [true, false, true],
|
||||
flapping: true,
|
||||
maintenanceWindowIds: ['id1', 'id2'],
|
||||
pendingRecoveredCount: 3,
|
||||
uuid: 'abc123',
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
lastScheduledActions: {
|
||||
group: 'group1',
|
||||
date: '2023-07-31T12:00:00Z',
|
||||
},
|
||||
flappingHistory: [true, false, true],
|
||||
flapping: true,
|
||||
maintenanceWindowIds: ['id1', 'id2'],
|
||||
pendingRecoveredCount: 3,
|
||||
uuid: 'abc123',
|
||||
};
|
||||
|
||||
const result = migrateMeta(input);
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('migrateAlertInstances', () => {
|
||||
it('should return undefined if input is not an object', () => {
|
||||
const result = migrateAlertInstances(null);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the migrated alertInstances object', () => {
|
||||
const input = {
|
||||
instance1: {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'group1',
|
||||
date: '2023-07-31T12:00:00Z',
|
||||
},
|
||||
flappingHistory: [true, false, true],
|
||||
flapping: true,
|
||||
maintenanceWindowIds: ['id1', 'id2'],
|
||||
pendingRecoveredCount: 3,
|
||||
uuid: 'abc123',
|
||||
},
|
||||
state: { key: 'value' },
|
||||
},
|
||||
instance2: {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'group2',
|
||||
date: '2023-07-30T12:00:00Z',
|
||||
},
|
||||
},
|
||||
},
|
||||
instance3: 'notAnObject',
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
instance1: {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'group1',
|
||||
date: '2023-07-31T12:00:00Z',
|
||||
},
|
||||
flappingHistory: [true, false, true],
|
||||
flapping: true,
|
||||
maintenanceWindowIds: ['id1', 'id2'],
|
||||
pendingRecoveredCount: 3,
|
||||
uuid: 'abc123',
|
||||
},
|
||||
state: { key: 'value' },
|
||||
},
|
||||
instance2: {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'group2',
|
||||
date: '2023-07-30T12:00:00Z',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = migrateAlertInstances(input);
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('upMigration', () => {
|
||||
it('should return the migrated state object', () => {
|
||||
const inputState = {
|
||||
alertTypeState: {},
|
||||
alertInstances: {
|
||||
instance1: {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'group1',
|
||||
date: '2023-07-31T12:00:00Z',
|
||||
},
|
||||
flappingHistory: [true, false, true],
|
||||
flapping: true,
|
||||
maintenanceWindowIds: ['id1', 'id2'],
|
||||
pendingRecoveredCount: 3,
|
||||
uuid: 'abc123',
|
||||
},
|
||||
state: { key: 'value' },
|
||||
},
|
||||
},
|
||||
alertRecoveredInstances: {},
|
||||
previousStartedAt: '2023-07-30T12:00:00Z',
|
||||
summaryActions: {
|
||||
action1: { date: '2023-07-31T12:00:00Z' },
|
||||
},
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
alertTypeState: {},
|
||||
alertInstances: {
|
||||
instance1: {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'group1',
|
||||
date: '2023-07-31T12:00:00Z',
|
||||
},
|
||||
flappingHistory: [true, false, true],
|
||||
flapping: true,
|
||||
maintenanceWindowIds: ['id1', 'id2'],
|
||||
pendingRecoveredCount: 3,
|
||||
uuid: 'abc123',
|
||||
},
|
||||
state: { key: 'value' },
|
||||
},
|
||||
},
|
||||
alertRecoveredInstances: {},
|
||||
previousStartedAt: '2023-07-30T12:00:00Z',
|
||||
summaryActions: {
|
||||
action1: { date: '2023-07-31T12:00:00Z' },
|
||||
},
|
||||
};
|
||||
|
||||
const result = upMigration(inputState);
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 { type TypeOf } from '@kbn/config-schema';
|
||||
import { isJSONObject, isString, isBoolean, isNumber, isStringArray, isBooleanArray } from '../lib';
|
||||
import {
|
||||
versionSchema,
|
||||
throttledActionSchema,
|
||||
rawAlertInstanceSchema,
|
||||
metaSchema,
|
||||
lastScheduledActionsSchema,
|
||||
} from './schema';
|
||||
|
||||
type VersionSchema = TypeOf<typeof versionSchema>;
|
||||
type ThrottledActionsSchema = TypeOf<typeof throttledActionSchema>;
|
||||
type LastScheduledActionsSchema = TypeOf<typeof lastScheduledActionsSchema>;
|
||||
type RawAlertInstanceSchema = TypeOf<typeof rawAlertInstanceSchema>;
|
||||
|
||||
export function migrateThrottledActions(
|
||||
throttledActions: unknown
|
||||
): ThrottledActionsSchema | undefined {
|
||||
if (!isJSONObject(throttledActions)) {
|
||||
return;
|
||||
}
|
||||
return Object.keys(throttledActions).reduce((acc, key) => {
|
||||
const val = throttledActions[key];
|
||||
if (isJSONObject(val) && isString(val.date)) {
|
||||
acc[key] = {
|
||||
date: val.date,
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {} as TypeOf<typeof throttledActionSchema>);
|
||||
}
|
||||
|
||||
export function migrateLastScheduledActions(
|
||||
lastScheduledActions: unknown
|
||||
): LastScheduledActionsSchema | undefined {
|
||||
if (
|
||||
!isJSONObject(lastScheduledActions) ||
|
||||
!isString(lastScheduledActions.group) ||
|
||||
!isString(lastScheduledActions.date)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
subgroup: isString(lastScheduledActions.subgroup) ? lastScheduledActions.subgroup : undefined,
|
||||
group: lastScheduledActions.group,
|
||||
date: lastScheduledActions.date,
|
||||
actions: migrateThrottledActions(lastScheduledActions.actions),
|
||||
};
|
||||
}
|
||||
|
||||
export function migrateMeta(meta: unknown): TypeOf<typeof metaSchema> | undefined {
|
||||
if (!isJSONObject(meta)) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
lastScheduledActions: migrateLastScheduledActions(meta.lastScheduledActions),
|
||||
flappingHistory: isBooleanArray(meta.flappingHistory) ? meta.flappingHistory : undefined,
|
||||
flapping: isBoolean(meta.flapping) ? meta.flapping : undefined,
|
||||
maintenanceWindowIds: isStringArray(meta.maintenanceWindowIds)
|
||||
? meta.maintenanceWindowIds
|
||||
: undefined,
|
||||
pendingRecoveredCount: isNumber(meta.pendingRecoveredCount)
|
||||
? meta.pendingRecoveredCount
|
||||
: undefined,
|
||||
uuid: isString(meta.uuid) ? meta.uuid : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function migrateAlertInstances(
|
||||
alertInstances: unknown
|
||||
): Record<string, RawAlertInstanceSchema> | undefined {
|
||||
if (!isJSONObject(alertInstances)) {
|
||||
return;
|
||||
}
|
||||
return Object.keys(alertInstances).reduce((acc, key) => {
|
||||
const val = alertInstances[key];
|
||||
if (isJSONObject(val)) {
|
||||
acc[key] = {
|
||||
meta: migrateMeta(val.meta),
|
||||
state: isJSONObject(val.state) ? val.state : undefined,
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, RawAlertInstanceSchema>);
|
||||
}
|
||||
|
||||
export const upMigration = (state: Record<string, unknown>): VersionSchema => {
|
||||
return {
|
||||
alertTypeState: isJSONObject(state.alertTypeState) ? state.alertTypeState : undefined,
|
||||
alertInstances: migrateAlertInstances(state.alertInstances),
|
||||
alertRecoveredInstances: migrateAlertInstances(state.alertRecoveredInstances),
|
||||
previousStartedAt: isString(state.previousStartedAt) ? state.previousStartedAt : undefined,
|
||||
summaryActions: migrateThrottledActions(state.summaryActions),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
|
||||
const actionSchema = schema.object({ date: schema.string() });
|
||||
export const throttledActionSchema = schema.recordOf(schema.string(), actionSchema);
|
||||
// TODO: Add schema by rule type for alert state
|
||||
// https://github.com/elastic/kibana/issues/159344
|
||||
export const alertStateSchema = schema.recordOf(schema.string(), schema.maybe(schema.any()));
|
||||
// TODO: Add schema by rule type for rule state
|
||||
// https://github.com/elastic/kibana/issues/159344
|
||||
const ruleStateSchema = schema.recordOf(schema.string(), schema.maybe(schema.any()));
|
||||
|
||||
export const lastScheduledActionsSchema = schema.object({
|
||||
subgroup: schema.maybe(schema.string()),
|
||||
group: schema.string(),
|
||||
date: schema.string(),
|
||||
actions: schema.maybe(throttledActionSchema),
|
||||
});
|
||||
|
||||
export const metaSchema = schema.object({
|
||||
lastScheduledActions: schema.maybe(lastScheduledActionsSchema),
|
||||
// an array used to track changes in alert state, the order is based on the rule executions (oldest to most recent)
|
||||
// true - alert has changed from active/recovered
|
||||
// false - the status has remained either active or recovered
|
||||
flappingHistory: schema.maybe(schema.arrayOf(schema.boolean())),
|
||||
// flapping flag that indicates whether the alert is flapping
|
||||
flapping: schema.maybe(schema.boolean()),
|
||||
maintenanceWindowIds: schema.maybe(schema.arrayOf(schema.string())),
|
||||
pendingRecoveredCount: schema.maybe(schema.number()),
|
||||
uuid: schema.maybe(schema.string()),
|
||||
});
|
||||
|
||||
export const rawAlertInstanceSchema = schema.object({
|
||||
meta: schema.maybe(metaSchema),
|
||||
state: schema.maybe(alertStateSchema),
|
||||
});
|
||||
|
||||
export const versionSchema = schema.object({
|
||||
alertTypeState: schema.maybe(ruleStateSchema),
|
||||
// tracks the active alerts
|
||||
alertInstances: schema.maybe(schema.recordOf(schema.string(), rawAlertInstanceSchema)),
|
||||
// tracks the recovered alerts for flapping purposes
|
||||
alertRecoveredInstances: schema.maybe(schema.recordOf(schema.string(), rawAlertInstanceSchema)),
|
||||
previousStartedAt: schema.maybe(schema.nullable(schema.string())),
|
||||
summaryActions: schema.maybe(throttledActionSchema),
|
||||
});
|
|
@ -13,5 +13,5 @@
|
|||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": []
|
||||
"kbn_references": ["@kbn/config-schema"]
|
||||
}
|
||||
|
|
|
@ -25,14 +25,6 @@ export type {
|
|||
RuleTaskState,
|
||||
RuleTaskParams,
|
||||
} from '@kbn/alerting-state-types';
|
||||
export {
|
||||
rawAlertInstance,
|
||||
DateFromString,
|
||||
wrappedStateRt,
|
||||
ActionsCompletion,
|
||||
ruleStateSchema,
|
||||
ruleParamsSchema,
|
||||
} from '@kbn/alerting-state-types';
|
||||
export * from './alert_summary';
|
||||
export * from './builtin_action_groups';
|
||||
export * from './bulk_edit';
|
||||
|
|
|
@ -44,7 +44,7 @@ describe('isThrottled', () => {
|
|||
const alert = new Alert<AlertInstanceState, AlertInstanceContext, DefaultActionGroupId>('1', {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -58,10 +58,10 @@ describe('isThrottled', () => {
|
|||
const alert = new Alert<AlertInstanceState, AlertInstanceContext, DefaultActionGroupId>('1', {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
actions: {
|
||||
'slack:alert:1h': { date: new Date() },
|
||||
'slack:alert:1h': { date: new Date().toISOString() },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -77,7 +77,7 @@ describe('isThrottled', () => {
|
|||
const alert = new Alert<AlertInstanceState, AlertInstanceContext, DefaultActionGroupId>('1', {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -91,7 +91,7 @@ describe('isThrottled', () => {
|
|||
const alert = new Alert<never, never, 'default' | 'other-group'>('1', {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -105,10 +105,10 @@ describe('isThrottled', () => {
|
|||
const alert = new Alert<never, never, 'default' | 'other-group'>('1', {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
actions: {
|
||||
'111-111': { date: new Date() },
|
||||
'111-111': { date: new Date().toISOString() },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -122,10 +122,10 @@ describe('isThrottled', () => {
|
|||
const alert = new Alert<never, never, 'default' | 'other-group'>('1', {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date('2020-01-01'),
|
||||
date: new Date('2020-01-01').toISOString(),
|
||||
group: 'default',
|
||||
actions: {
|
||||
'111-111': { date: new Date() },
|
||||
'111-111': { date: new Date().toISOString() },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -141,10 +141,10 @@ describe('isThrottled', () => {
|
|||
const alert = new Alert<never, never, 'default' | 'other-group'>('1', {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
actions: {
|
||||
'111-111': { date: new Date() },
|
||||
'111-111': { date: new Date().toISOString() },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -165,7 +165,7 @@ describe('scheduledActionGroupHasChanged()', () => {
|
|||
const alert = new Alert<AlertInstanceState, AlertInstanceContext, DefaultActionGroupId>('1', {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -184,7 +184,7 @@ describe('scheduledActionGroupHasChanged()', () => {
|
|||
const alert = new Alert<never, never, 'default' | 'penguin'>('1', {
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -274,7 +274,7 @@ describe('scheduleActions()', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -288,7 +288,7 @@ describe('scheduleActions()', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -302,7 +302,7 @@ describe('scheduleActions()', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -396,10 +396,10 @@ describe('updateLastScheduledActions()', () => {
|
|||
flappingHistory: [],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
actions: {
|
||||
'slack:alert:1h': { date: new Date() },
|
||||
'slack:alert:1h': { date: new Date().toISOString() },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -429,7 +429,7 @@ describe('getContext()', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -442,7 +442,7 @@ describe('getContext()', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -458,7 +458,7 @@ describe('hasContext()', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -472,7 +472,7 @@ describe('hasContext()', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -486,7 +486,7 @@ describe('hasContext()', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -503,7 +503,7 @@ describe('toJSON', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
flappingHistory: [false, true],
|
||||
|
@ -520,7 +520,7 @@ describe('toJSON', () => {
|
|||
},
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: expect.any(Date),
|
||||
date: expect.any(String),
|
||||
group: 'default',
|
||||
},
|
||||
uuid: expect.any(String),
|
||||
|
@ -538,7 +538,7 @@ describe('toRaw', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
flappingHistory: [false, true, true],
|
||||
|
@ -557,7 +557,7 @@ describe('toRaw', () => {
|
|||
state: { foo: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
flappingHistory: [false, true, true],
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { MutableAlertInstanceMeta } from '@kbn/alerting-state-types';
|
||||
import { AlertHit, CombinedSummarizedAlerts } from '../types';
|
||||
import {
|
||||
AlertInstanceMeta,
|
||||
AlertInstanceState,
|
||||
RawAlertInstance,
|
||||
rawAlertInstance,
|
||||
AlertInstanceContext,
|
||||
DefaultActionGroupId,
|
||||
LastScheduledActions,
|
||||
|
@ -52,7 +52,7 @@ export class Alert<
|
|||
ActionGroupIds extends string = never
|
||||
> {
|
||||
private scheduledExecutionOptions?: ScheduledExecutionOptions<State, Context, ActionGroupIds>;
|
||||
private meta: AlertInstanceMeta;
|
||||
private meta: MutableAlertInstanceMeta;
|
||||
private state: State;
|
||||
private context: Context;
|
||||
private readonly id: string;
|
||||
|
@ -111,11 +111,13 @@ export class Alert<
|
|||
this.meta.lastScheduledActions.actions[uuid] ||
|
||||
this.meta.lastScheduledActions.actions[actionHash]; // actionHash must be removed once all the hash identifiers removed from the task state
|
||||
const lastTriggerDate = actionInState?.date;
|
||||
return !!(lastTriggerDate && lastTriggerDate.getTime() + throttleMills > Date.now());
|
||||
return !!(
|
||||
lastTriggerDate && new Date(lastTriggerDate).getTime() + throttleMills > Date.now()
|
||||
);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return this.meta.lastScheduledActions.date.getTime() + throttleMills > Date.now();
|
||||
return new Date(this.meta.lastScheduledActions.date).getTime() + throttleMills > Date.now();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -202,7 +204,7 @@ export class Alert<
|
|||
if (!this.meta.lastScheduledActions) {
|
||||
this.meta.lastScheduledActions = {} as LastScheduledActions;
|
||||
}
|
||||
const date = new Date();
|
||||
const date = new Date().toISOString();
|
||||
this.meta.lastScheduledActions.group = group;
|
||||
this.meta.lastScheduledActions.date = date;
|
||||
|
||||
|
@ -224,7 +226,7 @@ export class Alert<
|
|||
* Used to serialize alert instance state
|
||||
*/
|
||||
toJSON() {
|
||||
return rawAlertInstance.encode(this.toRaw());
|
||||
return this.toRaw();
|
||||
}
|
||||
|
||||
toRaw(recovered: boolean = false): RawAlertInstance {
|
||||
|
|
|
@ -49,7 +49,10 @@ describe('createAlertFactory()', () => {
|
|||
test('reuses existing alerts', () => {
|
||||
const alert = new Alert('1', {
|
||||
state: { foo: true },
|
||||
meta: { lastScheduledActions: { group: 'default', date: new Date() }, uuid: 'uuid-previous' },
|
||||
meta: {
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'uuid-previous',
|
||||
},
|
||||
});
|
||||
const alertFactory = createAlertFactory({
|
||||
alerts: {
|
||||
|
@ -65,7 +68,7 @@ describe('createAlertFactory()', () => {
|
|||
uuid: 'uuid-previous',
|
||||
flappingHistory: [],
|
||||
lastScheduledActions: {
|
||||
date: expect.any(Date),
|
||||
date: expect.any(String),
|
||||
group: 'default',
|
||||
},
|
||||
},
|
||||
|
@ -100,7 +103,10 @@ describe('createAlertFactory()', () => {
|
|||
test('gets alert if it exists, returns null if it does not', () => {
|
||||
const alert = new Alert('1', {
|
||||
state: { foo: true },
|
||||
meta: { lastScheduledActions: { group: 'default', date: new Date() }, uuid: 'uuid-previous' },
|
||||
meta: {
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'uuid-previous',
|
||||
},
|
||||
});
|
||||
const alertFactory = createAlertFactory({
|
||||
alerts: {
|
||||
|
|
|
@ -177,7 +177,7 @@ describe('Alerts Client', () => {
|
|||
meta: {
|
||||
flapping: false,
|
||||
flappingHistory: [true, false],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'abc',
|
||||
},
|
||||
}),
|
||||
|
@ -186,7 +186,7 @@ describe('Alerts Client', () => {
|
|||
meta: {
|
||||
flapping: false,
|
||||
flappingHistory: [true, false, false],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'def',
|
||||
},
|
||||
}),
|
||||
|
@ -245,7 +245,7 @@ describe('Alerts Client', () => {
|
|||
meta: {
|
||||
flapping: false,
|
||||
flappingHistory: [true, false],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: id,
|
||||
},
|
||||
});
|
||||
|
@ -285,7 +285,7 @@ describe('Alerts Client', () => {
|
|||
meta: {
|
||||
flapping: false,
|
||||
flappingHistory: [true, false],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'abc',
|
||||
},
|
||||
}),
|
||||
|
@ -540,7 +540,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'abc',
|
||||
},
|
||||
},
|
||||
|
@ -800,7 +800,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'abc',
|
||||
},
|
||||
},
|
||||
|
@ -810,7 +810,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true, false],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'def',
|
||||
},
|
||||
},
|
||||
|
@ -1779,7 +1779,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'abc',
|
||||
},
|
||||
},
|
||||
|
@ -1789,7 +1789,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true, false],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'def',
|
||||
},
|
||||
},
|
||||
|
@ -1827,7 +1827,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'abc',
|
||||
},
|
||||
},
|
||||
|
@ -1837,7 +1837,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true, false],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'def',
|
||||
},
|
||||
},
|
||||
|
@ -2050,7 +2050,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'abc',
|
||||
},
|
||||
},
|
||||
|
@ -2227,7 +2227,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'abc',
|
||||
},
|
||||
},
|
||||
|
@ -2415,7 +2415,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'abc',
|
||||
},
|
||||
},
|
||||
|
@ -2505,7 +2505,7 @@ describe('Alerts Client', () => {
|
|||
flapping: false,
|
||||
flappingHistory: [true],
|
||||
maintenanceWindowIds: [],
|
||||
lastScheduledActions: { group: 'default', date: new Date() },
|
||||
lastScheduledActions: { group: 'default', date: new Date().toISOString() },
|
||||
uuid: 'abc',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -110,7 +110,7 @@ const testAlert2 = {
|
|||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'default',
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
uuid: 'def',
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { eventLoggerMock } from '@kbn/event-log-plugin/server/event_logger.mock';
|
||||
import { IEvent, SAVED_OBJECT_REL_PRIMARY } from '@kbn/event-log-plugin/server';
|
||||
import { ActionsCompletion } from '@kbn/alerting-state-types';
|
||||
import {
|
||||
AlertingEventLogger,
|
||||
RuleContextOpts,
|
||||
|
@ -19,7 +20,6 @@ import {
|
|||
} from './alerting_event_logger';
|
||||
import { UntypedNormalizedRuleType } from '../../rule_type_registry';
|
||||
import {
|
||||
ActionsCompletion,
|
||||
RecoveredActionGroup,
|
||||
RuleExecutionStatusErrorReasons,
|
||||
RuleExecutionStatusWarningReasons,
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ActionsCompletion } from '@kbn/alerting-state-types';
|
||||
import { lastRunFromState } from './last_run_status';
|
||||
import { ActionsCompletion } from '../../common';
|
||||
import { RuleRunMetrics } from './rule_run_metrics_store';
|
||||
import { RuleResultServiceResults, RuleResultService } from '../monitoring/rule_result_service';
|
||||
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ActionsCompletion } from '@kbn/alerting-state-types';
|
||||
import { RuleTaskStateAndMetrics } from '../task_runner/types';
|
||||
import { getReasonFromError } from './error_with_reason';
|
||||
import { getEsErrorMessage } from './errors';
|
||||
import { ActionsCompletion, RuleLastRunOutcomeOrderMap, RuleLastRunOutcomes } from '../../common';
|
||||
import { RuleLastRunOutcomeOrderMap, RuleLastRunOutcomes } from '../../common';
|
||||
import {
|
||||
RuleLastRunOutcomeValues,
|
||||
RuleExecutionStatusWarningReasons,
|
||||
|
|
|
@ -6,11 +6,8 @@
|
|||
*/
|
||||
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import {
|
||||
ActionsCompletion,
|
||||
RuleExecutionStatusErrorReasons,
|
||||
RuleExecutionStatusWarningReasons,
|
||||
} from '../types';
|
||||
import { ActionsCompletion } from '@kbn/alerting-state-types';
|
||||
import { RuleExecutionStatusErrorReasons, RuleExecutionStatusWarningReasons } from '../types';
|
||||
import {
|
||||
executionStatusFromState,
|
||||
executionStatusFromError,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { ActionsCompletion } from '@kbn/alerting-state-types';
|
||||
import {
|
||||
RuleExecutionStatus,
|
||||
RuleExecutionStatusValues,
|
||||
|
@ -16,7 +17,7 @@ import {
|
|||
} from '../types';
|
||||
import { getReasonFromError } from './error_with_reason';
|
||||
import { getEsErrorMessage } from './errors';
|
||||
import { ActionsCompletion, RuleExecutionStatuses } from '../../common';
|
||||
import { RuleExecutionStatuses } from '../../common';
|
||||
import { translations } from '../constants/translations';
|
||||
import { RuleTaskStateAndMetrics } from '../task_runner/types';
|
||||
import { RuleRunMetrics } from './rule_run_metrics_store';
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ActionsCompletion } from '@kbn/alerting-state-types';
|
||||
import { RuleRunMetricsStore } from './rule_run_metrics_store';
|
||||
import { ActionsCompletion } from '../types';
|
||||
|
||||
describe('RuleRunMetricsStore', () => {
|
||||
const ruleRunMetricsStore = new RuleRunMetricsStore();
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { set } from '@kbn/safer-lodash-set';
|
||||
import { ActionsCompletion } from '../types';
|
||||
import { ActionsCompletion } from '@kbn/alerting-state-types';
|
||||
import { ActionsConfigMap } from './get_actions_config_map';
|
||||
import { SearchMetrics } from './types';
|
||||
|
||||
|
|
|
@ -1,29 +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 { DateFromString } from './types';
|
||||
import { right, isLeft } from 'fp-ts/lib/Either';
|
||||
|
||||
describe('DateFromString', () => {
|
||||
test('validated and parses a string into a Date', () => {
|
||||
const date = new Date(1973, 10, 30);
|
||||
expect(DateFromString.decode(date.toISOString())).toEqual(right(date));
|
||||
});
|
||||
|
||||
test('validated and returns a failure for an actual Date', () => {
|
||||
const date = new Date(1973, 10, 30);
|
||||
expect(isLeft(DateFromString.decode(date))).toEqual(true);
|
||||
});
|
||||
|
||||
test('validated and returns a failure for an invalid Date string', () => {
|
||||
expect(isLeft(DateFromString.decode('1234-23-45'))).toEqual(true);
|
||||
});
|
||||
|
||||
test('validated and returns a failure for a null value', () => {
|
||||
expect(isLeft(DateFromString.decode(null))).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -5,29 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { either } from 'fp-ts/lib/Either';
|
||||
import { Rule } from '../types';
|
||||
import { RuleRunMetrics } from './rule_run_metrics_store';
|
||||
|
||||
// represents a Date from an ISO string
|
||||
export const DateFromString = new t.Type<Date, string, unknown>(
|
||||
'DateFromString',
|
||||
// detect the type
|
||||
(value): value is Date => value instanceof Date,
|
||||
(valueToDecode, context) =>
|
||||
either.chain(
|
||||
// validate this is a string
|
||||
t.string.validate(valueToDecode, context),
|
||||
// decode
|
||||
(value) => {
|
||||
const decoded = new Date(value);
|
||||
return isNaN(decoded.getTime()) ? t.failure(valueToDecode, context) : t.success(decoded);
|
||||
}
|
||||
),
|
||||
(valueToEncode) => valueToEncode.toISOString()
|
||||
);
|
||||
|
||||
export type RuleInfo = Pick<Rule, 'name' | 'alertTypeId' | 'id'> & { spaceId: string };
|
||||
|
||||
export interface LogSearchMetricsOpts {
|
||||
|
|
|
@ -32,7 +32,7 @@ describe('getRuleStateRoute', () => {
|
|||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'first_group',
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -37,7 +37,7 @@ describe('getAlertStateRoute', () => {
|
|||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'first_group',
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -17,7 +17,6 @@ import { inMemoryMetricsMock } from './monitoring/in_memory_metrics.mock';
|
|||
import { alertsServiceMock } from './alerts_service/alerts_service.mock';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { RecoveredActionGroupId } from '../common';
|
||||
import { rawRuleSchema } from './raw_rule_schema';
|
||||
|
||||
const logger = loggingSystemMock.create().get();
|
||||
let mockedLicenseState: jest.Mocked<ILicenseState>;
|
||||
|
@ -437,17 +436,12 @@ describe('Create Lifecycle', () => {
|
|||
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
|
||||
registry.register(ruleType);
|
||||
expect(taskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1);
|
||||
expect(taskManager.registerTaskDefinitions.mock.calls[0]).toEqual([
|
||||
{
|
||||
'alerting:test': {
|
||||
createTaskRunner: expect.any(Function),
|
||||
paramsSchema: expect.any(Object),
|
||||
indirectParamsSchema: rawRuleSchema,
|
||||
timeout: '20m',
|
||||
title: 'Test',
|
||||
},
|
||||
expect(taskManager.registerTaskDefinitions.mock.calls[0][0]).toMatchObject({
|
||||
'alerting:test': {
|
||||
timeout: '20m',
|
||||
title: 'Test',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('shallow clones the given rule type', () => {
|
||||
|
|
|
@ -13,6 +13,7 @@ import { intersection } from 'lodash';
|
|||
import { Logger } from '@kbn/core/server';
|
||||
import { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
|
||||
import { RunContext, TaskManagerSetupContract } from '@kbn/task-manager-plugin/server';
|
||||
import { stateSchemaByVersion } from '@kbn/alerting-state-types';
|
||||
import { rawRuleSchema } from './raw_rule_schema';
|
||||
import { TaskRunnerFactory } from './task_runner';
|
||||
import {
|
||||
|
@ -279,10 +280,12 @@ export class RuleTypeRegistry {
|
|||
/** stripping the typing is required in order to store the RuleTypes in a Map */
|
||||
normalizedRuleType as unknown as UntypedNormalizedRuleType
|
||||
);
|
||||
|
||||
this.taskManager.registerTaskDefinitions({
|
||||
[`alerting:${ruleType.id}`]: {
|
||||
title: ruleType.name,
|
||||
timeout: ruleType.ruleTaskTimeout,
|
||||
stateSchemaByVersion,
|
||||
createTaskRunner: (context: RunContext) =>
|
||||
this.taskRunnerFactory.create<
|
||||
Params,
|
||||
|
@ -302,6 +305,7 @@ export class RuleTypeRegistry {
|
|||
indirectParamsSchema: rawRuleSchema,
|
||||
},
|
||||
});
|
||||
|
||||
if (this.alertsService && ruleType.alerts) {
|
||||
this.alertsService.register(ruleType.alerts as IRuleTypeAlerts);
|
||||
}
|
||||
|
|
|
@ -41,8 +41,7 @@ const alert: SanitizedRule<{
|
|||
};
|
||||
|
||||
describe('Alert Task Instance', () => {
|
||||
test(`validates that a TaskInstance has valid Alert Task State`, () => {
|
||||
const lastScheduledActionsDate = new Date();
|
||||
test(`passes-through the state object`, () => {
|
||||
const taskInstance: ConcreteTaskInstance = {
|
||||
id: uuidv4(),
|
||||
attempts: 0,
|
||||
|
@ -52,129 +51,7 @@ describe('Alert Task Instance', () => {
|
|||
scheduledAt: new Date(),
|
||||
startedAt: new Date(),
|
||||
retryAt: new Date(Date.now() + 5 * 60 * 1000),
|
||||
state: {
|
||||
alertTypeState: {
|
||||
some: 'value',
|
||||
},
|
||||
alertInstances: {
|
||||
first_instance: {
|
||||
state: {},
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'first_group',
|
||||
date: lastScheduledActionsDate.toISOString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
second_instance: {},
|
||||
},
|
||||
},
|
||||
taskType: 'alerting:test',
|
||||
params: {
|
||||
alertId: '1',
|
||||
},
|
||||
ownerId: null,
|
||||
};
|
||||
|
||||
const alertTaskInsatnce: AlertTaskInstance = taskInstanceToAlertTaskInstance(taskInstance);
|
||||
|
||||
expect(alertTaskInsatnce).toEqual({
|
||||
...taskInstance,
|
||||
state: {
|
||||
alertTypeState: {
|
||||
some: 'value',
|
||||
},
|
||||
alertInstances: {
|
||||
first_instance: {
|
||||
state: {},
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'first_group',
|
||||
date: lastScheduledActionsDate,
|
||||
},
|
||||
},
|
||||
},
|
||||
second_instance: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test(`throws if state is invalid`, () => {
|
||||
const taskInstance: ConcreteTaskInstance = {
|
||||
id: '215ee69b-1df9-428e-ab1a-ccf274f8fa5b',
|
||||
attempts: 0,
|
||||
status: TaskStatus.Running,
|
||||
version: '123',
|
||||
runAt: new Date(),
|
||||
scheduledAt: new Date(),
|
||||
startedAt: new Date(),
|
||||
retryAt: new Date(Date.now() + 5 * 60 * 1000),
|
||||
state: {
|
||||
alertTypeState: {
|
||||
some: 'value',
|
||||
},
|
||||
alertInstances: {
|
||||
first_instance: 'invalid',
|
||||
second_instance: {},
|
||||
},
|
||||
},
|
||||
taskType: 'alerting:test',
|
||||
params: {
|
||||
alertId: '1',
|
||||
},
|
||||
ownerId: null,
|
||||
};
|
||||
|
||||
expect(() => taskInstanceToAlertTaskInstance(taskInstance)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Task \\"215ee69b-1df9-428e-ab1a-ccf274f8fa5b\\" has invalid state at .alertInstances.first_instance"`
|
||||
);
|
||||
});
|
||||
|
||||
test(`throws with Alert id when alert is present and state is invalid`, () => {
|
||||
const taskInstance: ConcreteTaskInstance = {
|
||||
id: '215ee69b-1df9-428e-ab1a-ccf274f8fa5b',
|
||||
attempts: 0,
|
||||
status: TaskStatus.Running,
|
||||
version: '123',
|
||||
runAt: new Date(),
|
||||
scheduledAt: new Date(),
|
||||
startedAt: new Date(),
|
||||
retryAt: new Date(Date.now() + 5 * 60 * 1000),
|
||||
state: {
|
||||
alertTypeState: {
|
||||
some: 'value',
|
||||
},
|
||||
alertInstances: {
|
||||
first_instance: 'invalid',
|
||||
second_instance: {},
|
||||
},
|
||||
},
|
||||
taskType: 'alerting:test',
|
||||
params: {
|
||||
alertId: '1',
|
||||
},
|
||||
ownerId: null,
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
taskInstanceToAlertTaskInstance(taskInstance, alert)
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Task \\"215ee69b-1df9-428e-ab1a-ccf274f8fa5b\\" (underlying Alert \\"alert-123\\") has invalid state at .alertInstances.first_instance"`
|
||||
);
|
||||
});
|
||||
|
||||
test(`allows an initial empty state`, () => {
|
||||
const taskInstance: ConcreteTaskInstance = {
|
||||
id: uuidv4(),
|
||||
attempts: 0,
|
||||
status: TaskStatus.Running,
|
||||
version: '123',
|
||||
runAt: new Date(),
|
||||
scheduledAt: new Date(),
|
||||
startedAt: new Date(),
|
||||
retryAt: new Date(Date.now() + 5 * 60 * 1000),
|
||||
state: {},
|
||||
state: { foo: true },
|
||||
taskType: 'alerting:test',
|
||||
params: {
|
||||
alertId: '1',
|
||||
|
|
|
@ -9,14 +9,8 @@ import * as t from 'io-ts';
|
|||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server';
|
||||
import {
|
||||
SanitizedRule,
|
||||
RuleTaskState,
|
||||
ruleParamsSchema,
|
||||
ruleStateSchema,
|
||||
RuleTaskParams,
|
||||
RuleTypeParams,
|
||||
} from '../../common';
|
||||
import { ruleParamsSchema } from '@kbn/alerting-state-types';
|
||||
import { SanitizedRule, RuleTaskState, RuleTaskParams, RuleTypeParams } from '../../common';
|
||||
|
||||
export interface AlertTaskInstance extends ConcreteTaskInstance {
|
||||
state: RuleTaskState;
|
||||
|
@ -42,15 +36,6 @@ export function taskInstanceToAlertTaskInstance<Params extends RuleTypeParams>(
|
|||
);
|
||||
}, t.identity)
|
||||
),
|
||||
state: pipe(
|
||||
ruleStateSchema.decode(taskInstance.state),
|
||||
fold((e: t.Errors) => {
|
||||
throw new Error(
|
||||
`Task "${taskInstance.id}" ${
|
||||
alert ? `(underlying Alert "${alert.id}") ` : ''
|
||||
}has invalid state at ${enumerateErrorFields(e)}`
|
||||
);
|
||||
}, t.identity)
|
||||
),
|
||||
state: taskInstance.state as RuleTaskState,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,10 +13,10 @@ import {
|
|||
renderActionParameterTemplatesDefault,
|
||||
} from '@kbn/actions-plugin/server/mocks';
|
||||
import { KibanaRequest } from '@kbn/core/server';
|
||||
import { ActionsCompletion } from '@kbn/alerting-state-types';
|
||||
import { InjectActionParamsOpts, injectActionParams } from './inject_action_params';
|
||||
import { NormalizedRuleType } from '../rule_type_registry';
|
||||
import {
|
||||
ActionsCompletion,
|
||||
ThrottledActions,
|
||||
RuleTypeParams,
|
||||
RuleTypeState,
|
||||
|
@ -166,7 +166,7 @@ const generateAlert = ({
|
|||
meta: {
|
||||
maintenanceWindowIds,
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: lastScheduledActionsGroup,
|
||||
actions: throttledActions,
|
||||
},
|
||||
|
@ -188,7 +188,7 @@ const generateRecoveredAlert = ({ id, state }: { id: number; state?: AlertInstan
|
|||
state: state || { test: true },
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
group: 'recovered',
|
||||
actions: {},
|
||||
},
|
||||
|
@ -792,7 +792,7 @@ describe('Execution Handler', () => {
|
|||
await executionHandler.run(
|
||||
generateAlert({
|
||||
id: 1,
|
||||
throttledActions: { '111-111': { date: new Date(DATE_1970) } },
|
||||
throttledActions: { '111-111': { date: new Date(DATE_1970).toISOString() } },
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -1016,7 +1016,7 @@ describe('Execution Handler', () => {
|
|||
expect(result).toEqual({
|
||||
throttledSummaryActions: {
|
||||
'111-111': {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import { getRuleDetailsRoute, triggersActionsRoute } from '@kbn/rule-data-utils'
|
|||
import { asSavedObjectExecutionSource } from '@kbn/actions-plugin/server';
|
||||
import { isEphemeralTaskRejectedDueToCapacityError } from '@kbn/task-manager-plugin/server';
|
||||
import { ExecuteOptions as EnqueueExecutionOptions } from '@kbn/actions-plugin/server/create_execute_function';
|
||||
import { ActionsCompletion } from '@kbn/alerting-state-types';
|
||||
import { ActionsClient } from '@kbn/actions-plugin/server/actions_client';
|
||||
import { chunk } from 'lodash';
|
||||
import { GetSummarizedAlertsParams, IAlertsClient } from '../alerts_client/types';
|
||||
|
@ -24,7 +25,6 @@ import { transformActionParams, transformSummaryActionParams } from './transform
|
|||
import { Alert } from '../alert';
|
||||
import { NormalizedRuleType } from '../rule_type_registry';
|
||||
import {
|
||||
ActionsCompletion,
|
||||
AlertInstanceContext,
|
||||
AlertInstanceState,
|
||||
RuleAction,
|
||||
|
@ -259,7 +259,7 @@ export class ExecutionHandler<
|
|||
});
|
||||
|
||||
if (isActionOnInterval(action)) {
|
||||
throttledSummaryActions[action.uuid!] = { date: new Date() };
|
||||
throttledSummaryActions[action.uuid!] = { date: new Date().toISOString() };
|
||||
}
|
||||
|
||||
logActions.push({
|
||||
|
|
|
@ -383,7 +383,7 @@ export const generateRunnerResult = ({
|
|||
...(state && { alertInstances }),
|
||||
...(state && { alertRecoveredInstances }),
|
||||
...(state && { alertTypeState: {} }),
|
||||
...(state && { previousStartedAt: new Date('1970-01-01T00:00:00.000Z') }),
|
||||
...(state && { previousStartedAt: new Date('1970-01-01T00:00:00.000Z').toISOString() }),
|
||||
...(state && { summaryActions }),
|
||||
},
|
||||
hasError,
|
||||
|
@ -440,7 +440,7 @@ export const generateAlertInstance = (
|
|||
meta: {
|
||||
uuid: expect.any(String),
|
||||
lastScheduledActions: {
|
||||
date: new Date(DATE_1970),
|
||||
date: new Date(DATE_1970).toISOString(),
|
||||
group: 'default',
|
||||
...(actions && { actions }),
|
||||
},
|
||||
|
|
|
@ -146,21 +146,21 @@ describe('rule_action_helper', () => {
|
|||
const result = getSummaryActionsFromTaskState({
|
||||
actions: [mockSummaryAction],
|
||||
summaryActions: {
|
||||
'111-111': { date: new Date('01.01.2020') },
|
||||
'222-222': { date: new Date('01.01.2020') },
|
||||
'111-111': { date: new Date('01.01.2020').toISOString() },
|
||||
'222-222': { date: new Date('01.01.2020').toISOString() },
|
||||
},
|
||||
});
|
||||
expect(result).toEqual({ '111-111': { date: new Date('01.01.2020') } });
|
||||
expect(result).toEqual({ '111-111': { date: new Date('01.01.2020').toISOString() } });
|
||||
});
|
||||
|
||||
test('should replace hash with uuid', () => {
|
||||
const result = getSummaryActionsFromTaskState({
|
||||
actions: [mockSummaryAction],
|
||||
summaryActions: {
|
||||
'slack:summary:1d': { date: new Date('01.01.2020') },
|
||||
'slack:summary:1d': { date: new Date('01.01.2020').toISOString() },
|
||||
},
|
||||
});
|
||||
expect(result).toEqual({ '111-111': { date: new Date('01.01.2020') } });
|
||||
expect(result).toEqual({ '111-111': { date: new Date('01.01.2020').toISOString() } });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -180,7 +180,7 @@ describe('rule_action_helper', () => {
|
|||
jest.useRealTimers();
|
||||
});
|
||||
const logger = { debug: jest.fn() } as unknown as Logger;
|
||||
const throttledSummaryActions = { '111-111': { date: new Date('2020-01-01T00:00:00.000Z') } };
|
||||
const throttledSummaryActions = { '111-111': { date: '2020-01-01T00:00:00.000Z' } };
|
||||
|
||||
test('should return false if the action does not have throttle filed', () => {
|
||||
const result = isSummaryActionThrottled({
|
||||
|
@ -227,7 +227,7 @@ describe('rule_action_helper', () => {
|
|||
test('should return false if the action is not in the task instance', () => {
|
||||
const result = isSummaryActionThrottled({
|
||||
action: mockSummaryAction,
|
||||
throttledSummaryActions: { '123-456': { date: new Date('2020-01-01T00:00:00.000Z') } },
|
||||
throttledSummaryActions: { '123-456': { date: '2020-01-01T00:00:00.000Z' } },
|
||||
logger,
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
|
@ -237,7 +237,7 @@ describe('rule_action_helper', () => {
|
|||
jest.advanceTimersByTime(3600000 * 2);
|
||||
const result = isSummaryActionThrottled({
|
||||
action: mockSummaryAction,
|
||||
throttledSummaryActions: { '123-456': { date: new Date('2020-01-01T00:00:00.000Z') } },
|
||||
throttledSummaryActions: { '123-456': { date: '2020-01-01T00:00:00.000Z' } },
|
||||
logger,
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
|
|
|
@ -58,7 +58,7 @@ export const isSummaryActionThrottled = ({
|
|||
logger.debug(`Action'${action?.actionTypeId}:${action?.id}', has an invalid throttle interval`);
|
||||
}
|
||||
|
||||
const throttled = throttledAction.date.getTime() + throttleMills > Date.now();
|
||||
const throttled = new Date(throttledAction.date).getTime() + throttleMills > Date.now();
|
||||
|
||||
if (throttled) {
|
||||
logger.debug(
|
||||
|
|
|
@ -1565,7 +1565,7 @@ describe('Task Runner', () => {
|
|||
generateEnqueueFunctionInput({ isBulk, id: '1', foo: true })
|
||||
);
|
||||
expect(result.state.summaryActions).toEqual({
|
||||
'111-111': { date: new Date(DATE_1970) },
|
||||
'111-111': { date: new Date(DATE_1970).toISOString() },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -1835,9 +1835,7 @@ describe('Task Runner', () => {
|
|||
|
||||
const runnerResult = await taskRunner.run();
|
||||
|
||||
expect(runnerResult.state.previousStartedAt).toEqual(
|
||||
new Date(originalAlertSate.previousStartedAt)
|
||||
);
|
||||
expect(runnerResult.state.previousStartedAt).toEqual(originalAlertSate.previousStartedAt);
|
||||
expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -2745,7 +2743,7 @@ describe('Task Runner', () => {
|
|||
meta: {
|
||||
uuid: expect.any(String),
|
||||
lastScheduledActions: {
|
||||
date: new Date(DATE_1970),
|
||||
date: new Date(DATE_1970).toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
flappingHistory: [true],
|
||||
|
@ -2915,7 +2913,7 @@ describe('Task Runner', () => {
|
|||
meta: {
|
||||
uuid: expect.any(String),
|
||||
lastScheduledActions: {
|
||||
date: new Date(DATE_1970),
|
||||
date: new Date(DATE_1970).toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
flappingHistory: [true],
|
||||
|
@ -2932,7 +2930,7 @@ describe('Task Runner', () => {
|
|||
meta: {
|
||||
uuid: expect.any(String),
|
||||
lastScheduledActions: {
|
||||
date: new Date(DATE_1970),
|
||||
date: new Date(DATE_1970).toISOString(),
|
||||
group: 'default',
|
||||
},
|
||||
flappingHistory: [true],
|
||||
|
|
|
@ -856,7 +856,7 @@ export class TaskRunner<
|
|||
): RuleTaskState => {
|
||||
return {
|
||||
...omit(runStateWithMetrics, ['metrics']),
|
||||
previousStartedAt: startedAt,
|
||||
previousStartedAt: startedAt?.toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -191,14 +191,16 @@ export class BaseRule {
|
|||
return accum;
|
||||
}
|
||||
const alertInstance: RawAlertInstance = states.alertInstances[instanceId];
|
||||
const filteredAlertInstance = this.filterAlertInstance(alertInstance, filters);
|
||||
const { state, ...filteredAlertInstance } = this.filterAlertInstance(
|
||||
alertInstance,
|
||||
filters
|
||||
);
|
||||
if (filteredAlertInstance) {
|
||||
accum[instanceId] = filteredAlertInstance as RawAlertInstance;
|
||||
if (filteredAlertInstance.state) {
|
||||
accum[instanceId].state = {
|
||||
alertStates: (filteredAlertInstance.state as AlertInstanceState).alertStates,
|
||||
};
|
||||
}
|
||||
accum[instanceId] = {
|
||||
...filteredAlertInstance,
|
||||
// Only keep "alertStates" within the state
|
||||
...(state ? { state: { alertStates: state.alertStates } } : {}),
|
||||
} as RawAlertInstance;
|
||||
}
|
||||
return accum;
|
||||
},
|
||||
|
|
|
@ -29,19 +29,19 @@ export const alertInstanceFactoryStub = <
|
|||
replaceState(state: TInstanceState) {
|
||||
return new Alert<TInstanceState, TInstanceContext, TActionGroupIds>('', {
|
||||
state: {} as TInstanceState,
|
||||
meta: { lastScheduledActions: { group: 'default', date: new Date() } },
|
||||
meta: { lastScheduledActions: { group: 'default', date: new Date().toISOString() } },
|
||||
});
|
||||
},
|
||||
scheduleActions(actionGroup: TActionGroupIds, alertcontext: TInstanceContext) {
|
||||
return new Alert<TInstanceState, TInstanceContext, TActionGroupIds>('', {
|
||||
state: {} as TInstanceState,
|
||||
meta: { lastScheduledActions: { group: 'default', date: new Date() } },
|
||||
meta: { lastScheduledActions: { group: 'default', date: new Date().toISOString() } },
|
||||
});
|
||||
},
|
||||
setContext(alertContext: TInstanceContext) {
|
||||
return new Alert<TInstanceState, TInstanceContext, TActionGroupIds>('', {
|
||||
state: {} as TInstanceState,
|
||||
meta: { lastScheduledActions: { group: 'default', date: new Date() } },
|
||||
meta: { lastScheduledActions: { group: 'default', date: new Date().toISOString() } },
|
||||
});
|
||||
},
|
||||
getContext() {
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from '@kbn/core/server';
|
||||
import type {
|
||||
RuleTaskState,
|
||||
MutableRuleTaskState,
|
||||
TrackedLifecycleAlertState,
|
||||
WrappedLifecycleRuleState,
|
||||
} from '@kbn/alerting-state-types';
|
||||
|
@ -253,7 +254,7 @@ function addAlertUUID(doc: SavedObjectUnsanitizedDoc<SerializedConcreteTaskInsta
|
|||
|
||||
// mutates alerts passed in
|
||||
function addAlertUUIDsToAlerts(
|
||||
alerts: RuleTaskState['alertInstances'] | undefined,
|
||||
alerts: MutableRuleTaskState['alertInstances'] | undefined,
|
||||
alertToTrackedMap: Map<string, TrackedLifecycleAlertState>,
|
||||
currentUUIDs: Map<string, string>
|
||||
): void {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { migrationMocks } from '@kbn/core/server/mocks';
|
|||
import { SavedObjectsUtils } from '@kbn/core-saved-objects-utils-server';
|
||||
import type {
|
||||
RuleTaskState,
|
||||
MutableRuleTaskState,
|
||||
WrappedLifecycleRuleState,
|
||||
RawAlertInstance,
|
||||
} from '@kbn/alerting-state-types';
|
||||
|
@ -73,7 +74,7 @@ describe('successful migrations for 8.8.0', () => {
|
|||
});
|
||||
|
||||
function checkMetaInRuleTaskState(
|
||||
actual: RuleTaskState,
|
||||
actual: MutableRuleTaskState,
|
||||
original: RuleTaskState,
|
||||
wrappedUUIDs?: Map<string, string>
|
||||
) {
|
||||
|
|
|
@ -82,7 +82,7 @@ describe('loadRuleState', () => {
|
|||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'first_group',
|
||||
date: new Date('2020-02-09T23:15:41.941Z'),
|
||||
date: '2020-02-09T23:15:41.941Z',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { Errors, identity } from 'io-ts';
|
||||
import { ruleStateSchema } from '@kbn/alerting-plugin/common';
|
||||
import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common';
|
||||
import { RuleTaskState } from '../../../types';
|
||||
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants';
|
||||
|
@ -33,17 +29,9 @@ export async function loadRuleState({
|
|||
http: HttpSetup;
|
||||
ruleId: string;
|
||||
}): Promise<RuleTaskState> {
|
||||
return await http
|
||||
return (await http
|
||||
.get<AsApiContract<RuleTaskState> | EmptyHttpResponse>(
|
||||
`${INTERNAL_BASE_ALERTING_API_PATH}/rule/${ruleId}/state`
|
||||
)
|
||||
.then((state) => (state ? rewriteBodyRes(state) : {}))
|
||||
.then((state: RuleTaskState) => {
|
||||
return pipe(
|
||||
ruleStateSchema.decode(state),
|
||||
fold((e: Errors) => {
|
||||
throw new Error(`Rule "${ruleId}" has invalid state`);
|
||||
}, identity)
|
||||
);
|
||||
});
|
||||
.then((state) => (state ? rewriteBodyRes(state) : {}))) as RuleTaskState;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue