[Alerting] revert the revert of Enforces typing of Alert's ActionGroups (#87382)

The https://github.com/elastic/kibana/pull/86761 PR was reverted due to a small typing issue.

This PR reverts that revert and adds a commit to address the issue: 9e4ab2002c.
This commit is contained in:
Gidi Meir Morris 2021-01-06 10:38:33 +00:00 committed by GitHub
parent 8ead390813
commit b99ca969e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 1011 additions and 329 deletions

View file

@ -10,15 +10,16 @@ export const ALERTING_EXAMPLE_APP_ID = 'AlertingExample';
// always firing
export const DEFAULT_INSTANCES_TO_GENERATE = 5;
export interface AlwaysFiringThresholds {
small?: number;
medium?: number;
large?: number;
}
export interface AlwaysFiringParams extends AlertTypeParams {
instances?: number;
thresholds?: {
small?: number;
medium?: number;
large?: number;
};
thresholds?: AlwaysFiringThresholds;
}
export type AlwaysFiringActionGroupIds = keyof AlwaysFiringParams['thresholds'];
export type AlwaysFiringActionGroupIds = keyof AlwaysFiringThresholds;
// Astros
export enum Craft {

View file

@ -133,8 +133,10 @@ export const AlwaysFiringExpression: React.FunctionComponent<
};
interface TShirtSelectorProps {
actionGroup?: ActionGroupWithCondition<number>;
setTShirtThreshold: (actionGroup: ActionGroupWithCondition<number>) => void;
actionGroup?: ActionGroupWithCondition<number, AlwaysFiringActionGroupIds>;
setTShirtThreshold: (
actionGroup: ActionGroupWithCondition<number, AlwaysFiringActionGroupIds>
) => void;
}
const TShirtSelector = ({ actionGroup, setTShirtThreshold }: TShirtSelectorProps) => {
const [isOpen, setIsOpen] = useState(false);

View file

@ -11,6 +11,7 @@ import {
DEFAULT_INSTANCES_TO_GENERATE,
ALERTING_EXAMPLE_APP_ID,
AlwaysFiringParams,
AlwaysFiringActionGroupIds,
} from '../../common/constants';
type ActionGroups = 'small' | 'medium' | 'large';
@ -39,7 +40,8 @@ export const alertType: AlertType<
AlwaysFiringParams,
{ count?: number },
{ triggerdOnCycle: number },
never
never,
AlwaysFiringActionGroupIds
> = {
id: 'example.always-firing',
name: 'Always firing',

View file

@ -41,7 +41,10 @@ function getCraftFilter(craft: string) {
export const alertType: AlertType<
{ outerSpaceCapacity: number; craft: string; op: string },
{ peopleInSpace: number },
{ craft: string }
{ craft: string },
never,
'default',
'hasLandedBackOnEarth'
> = {
id: 'example.people-in-space',
name: 'People In Space Right Now',

View file

@ -142,8 +142,41 @@ This example receives server and threshold as parameters. It will read the CPU u
```typescript
import { schema } from '@kbn/config-schema';
import {
Alert,
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
} from 'x-pack/plugins/alerts/common';
...
server.newPlatform.setup.plugins.alerts.registerType({
interface MyAlertTypeParams extends AlertTypeParams {
server: string;
threshold: number;
}
interface MyAlertTypeState extends AlertTypeState {
lastChecked: number;
}
interface MyAlertTypeInstanceState extends AlertInstanceState {
cpuUsage: number;
}
interface MyAlertTypeInstanceContext extends AlertInstanceContext {
server: string;
hasCpuUsageIncreased: boolean;
}
type MyAlertTypeActionGroups = 'default' | 'warning';
const myAlertType: AlertType<
MyAlertTypeParams,
MyAlertTypeState,
MyAlertTypeInstanceState,
MyAlertTypeInstanceContext,
MyAlertTypeActionGroups
> = {
id: 'my-alert-type',
name: 'My alert type',
validate: {
@ -180,7 +213,7 @@ server.newPlatform.setup.plugins.alerts.registerType({
services,
params,
state,
}: AlertExecutorOptions) {
}: AlertExecutorOptions<MyAlertTypeParams, MyAlertTypeState, MyAlertTypeInstanceState, MyAlertTypeInstanceContext, MyAlertTypeActionGroups>) {
// Let's assume params is { server: 'server_1', threshold: 0.8 }
const { server, threshold } = params;
@ -219,7 +252,9 @@ server.newPlatform.setup.plugins.alerts.registerType({
};
},
producer: 'alerting',
});
};
server.newPlatform.setup.plugins.alerts.registerType(myAlertType);
```
This example only receives threshold as a parameter. It will read the CPU usage of all the servers and schedule individual actions if the reading for a server is greater than the threshold. This is a better implementation than above as only one query is performed for all the servers instead of one query per server.

View file

@ -5,19 +5,29 @@
*/
import { LicenseType } from '../../licensing/common/types';
import { RecoveredActionGroupId, DefaultActionGroupId } from './builtin_action_groups';
export interface AlertType {
export interface AlertType<
ActionGroupIds extends Exclude<string, RecoveredActionGroupId> = DefaultActionGroupId,
RecoveryActionGroupId extends string = RecoveredActionGroupId
> {
id: string;
name: string;
actionGroups: ActionGroup[];
recoveryActionGroup: ActionGroup;
actionGroups: Array<ActionGroup<ActionGroupIds>>;
recoveryActionGroup: ActionGroup<RecoveryActionGroupId>;
actionVariables: string[];
defaultActionGroupId: ActionGroup['id'];
defaultActionGroupId: ActionGroupIds;
producer: string;
minimumLicenseRequired: LicenseType;
}
export interface ActionGroup {
id: string;
export interface ActionGroup<ActionGroupIds extends string> {
id: ActionGroupIds;
name: string;
}
export type ActionGroupIdsOf<T> = T extends ActionGroup<infer groups>
? groups
: T extends Readonly<ActionGroup<infer groups>>
? groups
: never;

View file

@ -6,13 +6,27 @@
import { i18n } from '@kbn/i18n';
import { ActionGroup } from './alert_type';
export const RecoveredActionGroup: Readonly<ActionGroup> = {
export type DefaultActionGroupId = 'default';
export type RecoveredActionGroupId = typeof RecoveredActionGroup['id'];
export const RecoveredActionGroup: Readonly<ActionGroup<'recovered'>> = Object.freeze({
id: 'recovered',
name: i18n.translate('xpack.alerts.builtinActionGroups.recovered', {
defaultMessage: 'Recovered',
}),
};
});
export function getBuiltinActionGroups(customRecoveryGroup?: ActionGroup): ActionGroup[] {
return [customRecoveryGroup ?? Object.freeze(RecoveredActionGroup)];
export type ReservedActionGroups<RecoveryActionGroupId extends string> =
| RecoveryActionGroupId
| RecoveredActionGroupId;
export type WithoutReservedActionGroups<
ActionGroupIds extends string,
RecoveryActionGroupId extends string
> = ActionGroupIds extends ReservedActionGroups<RecoveryActionGroupId> ? never : ActionGroupIds;
export function getBuiltinActionGroups<RecoveryActionGroupId extends string>(
customRecoveryGroup?: ActionGroup<RecoveryActionGroupId>
): [ActionGroup<ReservedActionGroups<RecoveryActionGroupId>>] {
return [customRecoveryGroup ?? RecoveredActionGroup];
}

View file

@ -6,6 +6,7 @@
import sinon from 'sinon';
import { AlertInstance } from './alert_instance';
import { AlertInstanceState, AlertInstanceContext, DefaultActionGroupId } from '../../common';
let clock: sinon.SinonFakeTimers;
@ -17,12 +18,20 @@ afterAll(() => clock.restore());
describe('hasScheduledActions()', () => {
test('defaults to false', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
expect(alertInstance.hasScheduledActions()).toEqual(false);
});
test('returns true when scheduleActions is called', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
alertInstance.scheduleActions('default');
expect(alertInstance.hasScheduledActions()).toEqual(true);
});
@ -30,7 +39,11 @@ describe('hasScheduledActions()', () => {
describe('isThrottled', () => {
test(`should throttle when group didn't change and throttle period is still active`, () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
meta: {
lastScheduledActions: {
date: new Date(),
@ -44,7 +57,11 @@ describe('isThrottled', () => {
});
test(`shouldn't throttle when group didn't change and throttle period expired`, () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
meta: {
lastScheduledActions: {
date: new Date(),
@ -58,7 +75,7 @@ describe('isThrottled', () => {
});
test(`shouldn't throttle when group changes`, () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<never, never, 'default' | 'other-group'>({
meta: {
lastScheduledActions: {
date: new Date(),
@ -74,12 +91,20 @@ describe('isThrottled', () => {
describe('scheduledActionGroupOrSubgroupHasChanged()', () => {
test('should be false if no last scheduled and nothing scheduled', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(false);
});
test('should be false if group does not change', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
meta: {
lastScheduledActions: {
date: new Date(),
@ -92,7 +117,11 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => {
});
test('should be false if group and subgroup does not change', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
meta: {
lastScheduledActions: {
date: new Date(),
@ -106,7 +135,11 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => {
});
test('should be false if group does not change and subgroup goes from undefined to defined', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
meta: {
lastScheduledActions: {
date: new Date(),
@ -119,7 +152,11 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => {
});
test('should be false if group does not change and subgroup goes from defined to undefined', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
meta: {
lastScheduledActions: {
date: new Date(),
@ -133,13 +170,17 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => {
});
test('should be true if no last scheduled and has scheduled action', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
alertInstance.scheduleActions('default');
expect(alertInstance.scheduledActionGroupOrSubgroupHasChanged()).toEqual(true);
});
test('should be true if group does change', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<never, never, 'default' | 'penguin'>({
meta: {
lastScheduledActions: {
date: new Date(),
@ -152,7 +193,7 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => {
});
test('should be true if group does change and subgroup does change', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<never, never, 'default' | 'penguin'>({
meta: {
lastScheduledActions: {
date: new Date(),
@ -166,7 +207,11 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => {
});
test('should be true if group does not change and subgroup does change', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
meta: {
lastScheduledActions: {
date: new Date(),
@ -182,14 +227,22 @@ describe('scheduledActionGroupOrSubgroupHasChanged()', () => {
describe('getScheduledActionOptions()', () => {
test('defaults to undefined', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
expect(alertInstance.getScheduledActionOptions()).toBeUndefined();
});
});
describe('unscheduleActions()', () => {
test('makes hasScheduledActions() return false', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
alertInstance.scheduleActions('default');
expect(alertInstance.hasScheduledActions()).toEqual(true);
alertInstance.unscheduleActions();
@ -197,7 +250,11 @@ describe('unscheduleActions()', () => {
});
test('makes getScheduledActionOptions() return undefined', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
alertInstance.scheduleActions('default');
expect(alertInstance.getScheduledActionOptions()).toEqual({
actionGroup: 'default',
@ -212,14 +269,22 @@ describe('unscheduleActions()', () => {
describe('getState()', () => {
test('returns state passed to constructor', () => {
const state = { foo: true };
const alertInstance = new AlertInstance({ state });
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({ state });
expect(alertInstance.getState()).toEqual(state);
});
});
describe('scheduleActions()', () => {
test('makes hasScheduledActions() return true', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
state: { foo: true },
meta: {
lastScheduledActions: {
@ -233,7 +298,11 @@ describe('scheduleActions()', () => {
});
test('makes isThrottled() return true when throttled', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
state: { foo: true },
meta: {
lastScheduledActions: {
@ -247,7 +316,11 @@ describe('scheduleActions()', () => {
});
test('make isThrottled() return false when throttled expired', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
state: { foo: true },
meta: {
lastScheduledActions: {
@ -262,7 +335,11 @@ describe('scheduleActions()', () => {
});
test('makes getScheduledActionOptions() return given options', () => {
const alertInstance = new AlertInstance({ state: { foo: true }, meta: {} });
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({ state: { foo: true }, meta: {} });
alertInstance.replaceState({ otherField: true }).scheduleActions('default', { field: true });
expect(alertInstance.getScheduledActionOptions()).toEqual({
actionGroup: 'default',
@ -272,7 +349,11 @@ describe('scheduleActions()', () => {
});
test('cannot schdule for execution twice', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
alertInstance.scheduleActions('default', { field: true });
expect(() =>
alertInstance.scheduleActions('default', { field: false })
@ -284,7 +365,11 @@ describe('scheduleActions()', () => {
describe('scheduleActionsWithSubGroup()', () => {
test('makes hasScheduledActions() return true', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
state: { foo: true },
meta: {
lastScheduledActions: {
@ -300,7 +385,11 @@ describe('scheduleActionsWithSubGroup()', () => {
});
test('makes isThrottled() return true when throttled and subgroup is the same', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
state: { foo: true },
meta: {
lastScheduledActions: {
@ -317,7 +406,11 @@ describe('scheduleActionsWithSubGroup()', () => {
});
test('makes isThrottled() return true when throttled and last schedule had no subgroup', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
state: { foo: true },
meta: {
lastScheduledActions: {
@ -333,7 +426,11 @@ describe('scheduleActionsWithSubGroup()', () => {
});
test('makes isThrottled() return false when throttled and subgroup is the different', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
state: { foo: true },
meta: {
lastScheduledActions: {
@ -350,7 +447,11 @@ describe('scheduleActionsWithSubGroup()', () => {
});
test('make isThrottled() return false when throttled expired', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
state: { foo: true },
meta: {
lastScheduledActions: {
@ -367,7 +468,11 @@ describe('scheduleActionsWithSubGroup()', () => {
});
test('makes getScheduledActionOptions() return given options', () => {
const alertInstance = new AlertInstance({ state: { foo: true }, meta: {} });
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({ state: { foo: true }, meta: {} });
alertInstance
.replaceState({ otherField: true })
.scheduleActionsWithSubGroup('default', 'subgroup', { field: true });
@ -380,7 +485,11 @@ describe('scheduleActionsWithSubGroup()', () => {
});
test('cannot schdule for execution twice', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
alertInstance.scheduleActionsWithSubGroup('default', 'subgroup', { field: true });
expect(() =>
alertInstance.scheduleActionsWithSubGroup('default', 'subgroup', { field: false })
@ -390,7 +499,11 @@ describe('scheduleActionsWithSubGroup()', () => {
});
test('cannot schdule for execution twice with different subgroups', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
alertInstance.scheduleActionsWithSubGroup('default', 'subgroup', { field: true });
expect(() =>
alertInstance.scheduleActionsWithSubGroup('default', 'subgroup', { field: false })
@ -400,7 +513,11 @@ describe('scheduleActionsWithSubGroup()', () => {
});
test('cannot schdule for execution twice whether there are subgroups', () => {
const alertInstance = new AlertInstance();
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>();
alertInstance.scheduleActions('default', { field: true });
expect(() =>
alertInstance.scheduleActionsWithSubGroup('default', 'subgroup', { field: false })
@ -412,7 +529,11 @@ describe('scheduleActionsWithSubGroup()', () => {
describe('replaceState()', () => {
test('replaces previous state', () => {
const alertInstance = new AlertInstance({ state: { foo: true } });
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({ state: { foo: true } });
alertInstance.replaceState({ bar: true });
expect(alertInstance.getState()).toEqual({ bar: true });
alertInstance.replaceState({ baz: true });
@ -422,7 +543,11 @@ describe('replaceState()', () => {
describe('updateLastScheduledActions()', () => {
test('replaces previous lastScheduledActions', () => {
const alertInstance = new AlertInstance({ meta: {} });
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({ meta: {} });
alertInstance.updateLastScheduledActions('default');
expect(alertInstance.toJSON()).toEqual({
state: {},
@ -438,7 +563,11 @@ describe('updateLastScheduledActions()', () => {
describe('toJSON', () => {
test('only serializes state and meta', () => {
const alertInstance = new AlertInstance({
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>({
state: { foo: true },
meta: {
lastScheduledActions: {
@ -464,7 +593,11 @@ describe('toRaw', () => {
},
},
};
const alertInstance = new AlertInstance(raw);
const alertInstance = new AlertInstance<
AlertInstanceState,
AlertInstanceContext,
DefaultActionGroupId
>(raw);
expect(alertInstance.toRaw()).toEqual(raw);
});
});

View file

@ -9,15 +9,17 @@ import {
RawAlertInstance,
rawAlertInstance,
AlertInstanceContext,
DefaultActionGroupId,
} from '../../common';
import { parseDuration } from '../lib';
interface ScheduledExecutionOptions<
State extends AlertInstanceState,
Context extends AlertInstanceContext
Context extends AlertInstanceContext,
ActionGroupIds extends string = DefaultActionGroupId
> {
actionGroup: string;
actionGroup: ActionGroupIds;
subgroup?: string;
context: Context;
state: State;
@ -25,17 +27,19 @@ interface ScheduledExecutionOptions<
export type PublicAlertInstance<
State extends AlertInstanceState = AlertInstanceState,
Context extends AlertInstanceContext = AlertInstanceContext
Context extends AlertInstanceContext = AlertInstanceContext,
ActionGroupIds extends string = DefaultActionGroupId
> = Pick<
AlertInstance<State, Context>,
AlertInstance<State, Context, ActionGroupIds>,
'getState' | 'replaceState' | 'scheduleActions' | 'scheduleActionsWithSubGroup'
>;
export class AlertInstance<
State extends AlertInstanceState = AlertInstanceState,
Context extends AlertInstanceContext = AlertInstanceContext
Context extends AlertInstanceContext = AlertInstanceContext,
ActionGroupIds extends string = never
> {
private scheduledExecutionOptions?: ScheduledExecutionOptions<State, Context>;
private scheduledExecutionOptions?: ScheduledExecutionOptions<State, Context, ActionGroupIds>;
private meta: AlertInstanceMeta;
private state: State;
@ -97,14 +101,14 @@ export class AlertInstance<
private scheduledActionGroupIsUnchanged(
lastScheduledActions: NonNullable<AlertInstanceMeta['lastScheduledActions']>,
scheduledExecutionOptions: ScheduledExecutionOptions<State, Context>
scheduledExecutionOptions: ScheduledExecutionOptions<State, Context, ActionGroupIds>
) {
return lastScheduledActions.group === scheduledExecutionOptions.actionGroup;
}
private scheduledActionSubgroupIsUnchanged(
lastScheduledActions: NonNullable<AlertInstanceMeta['lastScheduledActions']>,
scheduledExecutionOptions: ScheduledExecutionOptions<State, Context>
scheduledExecutionOptions: ScheduledExecutionOptions<State, Context, ActionGroupIds>
) {
return lastScheduledActions.subgroup && scheduledExecutionOptions.subgroup
? lastScheduledActions.subgroup === scheduledExecutionOptions.subgroup
@ -128,7 +132,7 @@ export class AlertInstance<
return this.state;
}
scheduleActions(actionGroup: string, context: Context = {} as Context) {
scheduleActions(actionGroup: ActionGroupIds, context: Context = {} as Context) {
this.ensureHasNoScheduledActions();
this.scheduledExecutionOptions = {
actionGroup,
@ -139,7 +143,7 @@ export class AlertInstance<
}
scheduleActionsWithSubGroup(
actionGroup: string,
actionGroup: ActionGroupIds,
subgroup: string,
context: Context = {} as Context
) {
@ -164,7 +168,7 @@ export class AlertInstance<
return this;
}
updateLastScheduledActions(group: string, subgroup?: string) {
updateLastScheduledActions(group: ActionGroupIds, subgroup?: string) {
this.meta.lastScheduledActions = { group, subgroup, date: new Date() };
}

View file

@ -9,11 +9,12 @@ import { AlertInstance } from './alert_instance';
export function createAlertInstanceFactory<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
>(alertInstances: Record<string, AlertInstance<InstanceState, InstanceContext>>) {
return (id: string): AlertInstance<InstanceState, InstanceContext> => {
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string
>(alertInstances: Record<string, AlertInstance<InstanceState, InstanceContext, ActionGroupIds>>) {
return (id: string): AlertInstance<InstanceState, InstanceContext, ActionGroupIds> => {
if (!alertInstances[id]) {
alertInstances[id] = new AlertInstance<InstanceState, InstanceContext>();
alertInstances[id] = new AlertInstance<InstanceState, InstanceContext, ActionGroupIds>();
}
return alertInstances[id];

View file

@ -6,7 +6,7 @@
import { TaskRunnerFactory } from './task_runner';
import { AlertTypeRegistry, ConstructorOptions } from './alert_type_registry';
import { AlertType } from './types';
import { ActionGroup, AlertType } from './types';
import { taskManagerMock } from '../../task_manager/server/mocks';
import { ILicenseState } from './lib/license_state';
import { licenseStateMock } from './lib/license_state.mock';
@ -55,7 +55,7 @@ describe('has()', () => {
describe('register()', () => {
test('throws if AlertType Id contains invalid characters', () => {
const alertType: AlertType = {
const alertType: AlertType<never, never, never, never, 'default'> = {
id: 'test',
name: 'Test',
actionGroups: [
@ -87,7 +87,7 @@ describe('register()', () => {
});
test('throws if AlertType Id isnt a string', () => {
const alertType: AlertType = {
const alertType: AlertType<never, never, never, never, 'default'> = {
id: (123 as unknown) as string,
name: 'Test',
actionGroups: [
@ -109,7 +109,7 @@ describe('register()', () => {
});
test('throws if AlertType action groups contains reserved group id', () => {
const alertType: AlertType = {
const alertType: AlertType<never, never, never, never, 'default' | 'NotReserved'> = {
id: 'test',
name: 'Test',
actionGroups: [
@ -117,10 +117,14 @@ describe('register()', () => {
id: 'default',
name: 'Default',
},
{
/**
* The type system will ensure you can't use the `recovered` action group
* but we also want to ensure this at runtime
*/
({
id: 'recovered',
name: 'Recovered',
},
} as unknown) as ActionGroup<'NotReserved'>,
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
@ -137,7 +141,7 @@ describe('register()', () => {
});
test('allows an AlertType to specify a custom recovery group', () => {
const alertType: AlertType = {
const alertType: AlertType<never, never, never, never, 'default', 'backToAwesome'> = {
id: 'test',
name: 'Test',
actionGroups: [
@ -172,7 +176,14 @@ describe('register()', () => {
});
test('throws if the custom recovery group is contained in the AlertType action groups', () => {
const alertType: AlertType = {
const alertType: AlertType<
never,
never,
never,
never,
'default' | 'backToAwesome',
'backToAwesome'
> = {
id: 'test',
name: 'Test',
actionGroups: [
@ -204,7 +215,7 @@ describe('register()', () => {
});
test('registers the executor with the task manager', () => {
const alertType: AlertType = {
const alertType: AlertType<never, never, never, never, 'default'> = {
id: 'test',
name: 'Test',
actionGroups: [
@ -234,7 +245,7 @@ describe('register()', () => {
});
test('shallow clones the given alert type', () => {
const alertType: AlertType = {
const alertType: AlertType<never, never, never, never, 'default'> = {
id: 'test',
name: 'Test',
actionGroups: [
@ -475,8 +486,12 @@ describe('ensureAlertTypeEnabled', () => {
});
});
function alertTypeWithVariables(id: string, context: string, state: string): AlertType {
const baseAlert: AlertType = {
function alertTypeWithVariables<ActionGroupIds extends string>(
id: ActionGroupIds,
context: string,
state: string
): AlertType<never, never, never, never, ActionGroupIds> {
const baseAlert: AlertType<never, never, never, never, ActionGroupIds> = {
id,
name: `${id}-name`,
actionGroups: [],

View file

@ -19,7 +19,12 @@ import {
AlertInstanceState,
AlertInstanceContext,
} from './types';
import { RecoveredActionGroup, getBuiltinActionGroups } from '../common';
import {
RecoveredActionGroup,
getBuiltinActionGroups,
RecoveredActionGroupId,
ActionGroup,
} from '../common';
import { ILicenseState } from './lib/license_state';
import { getAlertTypeFeatureUsageName } from './lib/get_alert_type_feature_usage_name';
@ -69,15 +74,36 @@ export type NormalizedAlertType<
Params extends AlertTypeParams,
State extends AlertTypeState,
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
> = Omit<AlertType<Params, State, InstanceState, InstanceContext>, 'recoveryActionGroup'> &
Pick<Required<AlertType<Params, State, InstanceState, InstanceContext>>, 'recoveryActionGroup'>;
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
> = {
actionGroups: Array<ActionGroup<ActionGroupIds | RecoveryActionGroupId>>;
} & Omit<
AlertType<Params, State, InstanceState, InstanceContext, ActionGroupIds, RecoveryActionGroupId>,
'recoveryActionGroup' | 'actionGroups'
> &
Pick<
Required<
AlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>
>,
'recoveryActionGroup'
>;
export type UntypedNormalizedAlertType = NormalizedAlertType<
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string,
string
>;
export class AlertTypeRegistry {
@ -106,8 +132,19 @@ export class AlertTypeRegistry {
Params extends AlertTypeParams,
State extends AlertTypeState,
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
>(alertType: AlertType<Params, State, InstanceState, InstanceContext>) {
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
>(
alertType: AlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>
) {
if (this.has(alertType.id)) {
throw new Error(
i18n.translate('xpack.alerts.alertTypeRegistry.register.duplicateAlertTypeError', {
@ -124,18 +161,28 @@ export class AlertTypeRegistry {
Params,
State,
InstanceState,
InstanceContext
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>(alertType);
this.alertTypes.set(
alertIdSchema.validate(alertType.id),
normalizedAlertType as UntypedNormalizedAlertType
/** stripping the typing is required in order to store the AlertTypes in a Map */
(normalizedAlertType as unknown) as UntypedNormalizedAlertType
);
this.taskManager.registerTaskDefinitions({
[`alerting:${alertType.id}`]: {
title: alertType.name,
createTaskRunner: (context: RunContext) =>
this.taskRunnerFactory.create(normalizedAlertType as UntypedNormalizedAlertType, context),
this.taskRunnerFactory.create<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId | RecoveredActionGroupId
>(normalizedAlertType, context),
},
});
// No need to notify usage on basic alert types
@ -151,8 +198,19 @@ export class AlertTypeRegistry {
Params extends AlertTypeParams = AlertTypeParams,
State extends AlertTypeState = AlertTypeState,
InstanceState extends AlertInstanceState = AlertInstanceState,
InstanceContext extends AlertInstanceContext = AlertInstanceContext
>(id: string): NormalizedAlertType<Params, State, InstanceState, InstanceContext> {
InstanceContext extends AlertInstanceContext = AlertInstanceContext,
ActionGroupIds extends string = string,
RecoveryActionGroupId extends string = string
>(
id: string
): NormalizedAlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
> {
if (!this.has(id)) {
throw Boom.badRequest(
i18n.translate('xpack.alerts.alertTypeRegistry.get.missingAlertTypeError', {
@ -163,11 +221,18 @@ export class AlertTypeRegistry {
})
);
}
return this.alertTypes.get(id)! as NormalizedAlertType<
/**
* When we store the AlertTypes in the Map we strip the typing.
* This means that returning a typed AlertType in `get` is an inherently
* unsafe operation. Down casting to `unknown` is the only way to achieve this.
*/
return (this.alertTypes.get(id)! as unknown) as NormalizedAlertType<
Params,
State,
InstanceState,
InstanceContext
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>;
}
@ -217,15 +282,31 @@ function augmentActionGroupsWithReserved<
Params extends AlertTypeParams,
State extends AlertTypeState,
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
>(
alertType: AlertType<Params, State, InstanceState, InstanceContext>
): NormalizedAlertType<Params, State, InstanceState, InstanceContext> {
alertType: AlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>
): NormalizedAlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveredActionGroupId | RecoveryActionGroupId
> {
const reservedActionGroups = getBuiltinActionGroups(alertType.recoveryActionGroup);
const { id, actionGroups, recoveryActionGroup } = alertType;
const activeActionGroups = new Set(actionGroups.map((item) => item.id));
const intersectingReservedActionGroups = intersection(
const activeActionGroups = new Set<string>(actionGroups.map((item) => item.id));
const intersectingReservedActionGroups = intersection<string>(
[...activeActionGroups.values()],
reservedActionGroups.map((item) => item.id)
);

View file

@ -16,6 +16,7 @@ export {
ActionVariable,
AlertType,
ActionGroup,
ActionGroupIdsOf,
AlertingPlugin,
AlertExecutorOptions,
AlertActionParams,

View file

@ -56,7 +56,7 @@ describe('getLicenseCheckForAlertType', () => {
let license: Subject<ILicense>;
let licenseState: ILicenseState;
const mockNotifyUsage = jest.fn();
const alertType: AlertType = {
const alertType: AlertType<never, never, never, never, 'default', 'recovered'> = {
id: 'test',
name: 'Test',
actionGroups: [
@ -190,7 +190,7 @@ describe('ensureLicenseForAlertType()', () => {
let license: Subject<ILicense>;
let licenseState: ILicenseState;
const mockNotifyUsage = jest.fn();
const alertType: AlertType = {
const alertType: AlertType<never, never, never, never, string, string> = {
id: 'test',
name: 'Test',
actionGroups: [

View file

@ -13,7 +13,13 @@ import { LicensingPluginStart } from '../../../licensing/server';
import { ILicense, LicenseType } from '../../../licensing/common/types';
import { PLUGIN } from '../constants/plugin';
import { getAlertTypeFeatureUsageName } from './get_alert_type_feature_usage_name';
import { AlertType } from '../types';
import {
AlertType,
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
} from '../types';
import { AlertTypeDisabledError } from './errors/alert_type_disabled';
export type ILicenseState = PublicMethodsOf<LicenseState>;
@ -130,7 +136,23 @@ export class LicenseState {
}
}
public ensureLicenseForAlertType(alertType: AlertType) {
public ensureLicenseForAlertType<
Params extends AlertTypeParams,
State extends AlertTypeState,
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
>(
alertType: AlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>
) {
this.notifyUsage(alertType.name, alertType.minimumLicenseRequired);
const check = this.getLicenseCheckForAlertType(

View file

@ -58,7 +58,7 @@ describe('Alerting Plugin', () => {
describe('registerType()', () => {
let setup: PluginSetupContract;
const sampleAlertType: AlertType = {
const sampleAlertType: AlertType<never, never, never, never, 'default'> = {
id: 'test',
name: 'test',
minimumLicenseRequired: 'basic',

View file

@ -102,9 +102,18 @@ export interface PluginSetupContract {
Params extends AlertTypeParams = AlertTypeParams,
State extends AlertTypeState = AlertTypeState,
InstanceState extends AlertInstanceState = AlertInstanceState,
InstanceContext extends AlertInstanceContext = AlertInstanceContext
InstanceContext extends AlertInstanceContext = AlertInstanceContext,
ActionGroupIds extends string = never,
RecoveryActionGroupId extends string = never
>(
alertType: AlertType<Params, State, InstanceState, InstanceContext>
alertType: AlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>
): void;
}
@ -273,8 +282,19 @@ export class AlertingPlugin {
Params extends AlertTypeParams = AlertTypeParams,
State extends AlertTypeState = AlertTypeState,
InstanceState extends AlertInstanceState = AlertInstanceState,
InstanceContext extends AlertInstanceContext = AlertInstanceContext
>(alertType: AlertType<Params, State, InstanceState, InstanceContext>) {
InstanceContext extends AlertInstanceContext = AlertInstanceContext,
ActionGroupIds extends string = never,
RecoveryActionGroupId extends string = never
>(
alertType: AlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>
) {
if (!(alertType.minimumLicenseRequired in LICENSE_TYPE)) {
throw new Error(`"${alertType.minimumLicenseRequired}" is not a valid license type`);
}

View file

@ -15,7 +15,7 @@ import { eventLoggerMock } from '../../../event_log/server/event_logger.mock';
import { KibanaRequest } from 'kibana/server';
import { asSavedObjectExecutionSource } from '../../../actions/server';
import { InjectActionParamsOpts } from './inject_action_params';
import { UntypedNormalizedAlertType } from '../alert_type_registry';
import { NormalizedAlertType } from '../alert_type_registry';
import {
AlertTypeParams,
AlertTypeState,
@ -27,7 +27,14 @@ jest.mock('./inject_action_params', () => ({
injectActionParams: jest.fn(),
}));
const alertType: UntypedNormalizedAlertType = {
const alertType: NormalizedAlertType<
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
'default' | 'other-group',
'recovered'
> = {
id: 'test',
name: 'Test',
actionGroups: [
@ -53,7 +60,9 @@ const createExecutionHandlerParams: jest.Mocked<
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
'default' | 'other-group',
'recovered'
>
> = {
actionsPlugin: mockActionsPlugin,
@ -348,7 +357,9 @@ test('state attribute gets parameterized', async () => {
test(`logs an error when action group isn't part of actionGroups available for the alertType`, async () => {
const executionHandler = createExecutionHandler(createExecutionHandlerParams);
const result = await executionHandler({
actionGroup: 'invalid-group',
// we have to trick the compiler as this is an invalid type and this test checks whether we
// enforce this at runtime as well as compile time
actionGroup: 'invalid-group' as 'default' | 'other-group',
context: {},
state: {},
alertInstanceId: '2',

View file

@ -27,7 +27,9 @@ export interface CreateExecutionHandlerOptions<
Params extends AlertTypeParams,
State extends AlertTypeState,
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
> {
alertId: string;
alertName: string;
@ -36,26 +38,39 @@ export interface CreateExecutionHandlerOptions<
actions: AlertAction[];
spaceId: string;
apiKey: RawAlert['apiKey'];
alertType: NormalizedAlertType<Params, State, InstanceState, InstanceContext>;
alertType: NormalizedAlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>;
logger: Logger;
eventLogger: IEventLogger;
request: KibanaRequest;
alertParams: AlertTypeParams;
}
interface ExecutionHandlerOptions {
actionGroup: string;
interface ExecutionHandlerOptions<ActionGroupIds extends string> {
actionGroup: ActionGroupIds;
actionSubgroup?: string;
alertInstanceId: string;
context: AlertInstanceContext;
state: AlertInstanceState;
}
export type ExecutionHandler<ActionGroupIds extends string> = (
options: ExecutionHandlerOptions<ActionGroupIds>
) => Promise<void>;
export function createExecutionHandler<
Params extends AlertTypeParams,
State extends AlertTypeState,
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
>({
logger,
alertId,
@ -69,7 +84,14 @@ export function createExecutionHandler<
eventLogger,
request,
alertParams,
}: CreateExecutionHandlerOptions<Params, State, InstanceState, InstanceContext>) {
}: CreateExecutionHandlerOptions<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>): ExecutionHandler<ActionGroupIds | RecoveryActionGroupId> {
const alertTypeActionGroups = new Map(
alertType.actionGroups.map((actionGroup) => [actionGroup.id, actionGroup.name])
);
@ -79,7 +101,7 @@ export function createExecutionHandler<
context,
state,
alertInstanceId,
}: ExecutionHandlerOptions) => {
}: ExecutionHandlerOptions<ActionGroupIds | RecoveryActionGroupId>) => {
if (!alertTypeActionGroups.has(actionGroup)) {
logger.error(`Invalid action group "${actionGroup}" for alert "${alertType.id}".`);
return;

View file

@ -266,7 +266,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
executorServices
.alertInstanceFactory('1')
@ -426,7 +427,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
executorServices.alertInstanceFactory('1').scheduleActions('default');
}
@ -542,7 +544,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
executorServices.alertInstanceFactory('1').scheduleActions('default');
executorServices.alertInstanceFactory('2').scheduleActions('default');
@ -595,7 +598,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
executorServices.alertInstanceFactory('1').scheduleActions('default');
}
@ -696,7 +700,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
executorServices.alertInstanceFactory('1').scheduleActions('default');
}
@ -743,7 +748,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
executorServices
.alertInstanceFactory('1')
@ -798,7 +804,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
executorServices.alertInstanceFactory('1').scheduleActions('default');
}
@ -973,7 +980,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
executorServices.alertInstanceFactory('1').scheduleActions('default');
}
@ -1080,7 +1088,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
executorServices.alertInstanceFactory('1').scheduleActions('default');
}
@ -1178,7 +1187,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
executorServices.alertInstanceFactory('1').scheduleActions('default');
}
@ -1447,7 +1457,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
throw new Error('OMG');
}
@ -1822,7 +1833,8 @@ describe('Task Runner', () => {
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
string
>) => {
throw new Error('OMG');
}

View file

@ -10,7 +10,7 @@ import { addSpaceIdToPath } from '../../../spaces/server';
import { Logger, KibanaRequest } from '../../../../../src/core/server';
import { TaskRunnerContext } from './task_runner_factory';
import { ConcreteTaskInstance, throwUnrecoverableError } from '../../../task_manager/server';
import { createExecutionHandler } from './create_execution_handler';
import { createExecutionHandler, ExecutionHandler } from './create_execution_handler';
import { AlertInstance, createAlertInstanceFactory } from '../alert_instance';
import {
validateAlertTypeParams,
@ -44,6 +44,7 @@ import {
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
WithoutReservedActionGroups,
} from '../../common';
import { NormalizedAlertType } from '../alert_type_registry';
@ -64,16 +65,32 @@ export class TaskRunner<
Params extends AlertTypeParams,
State extends AlertTypeState,
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
> {
private context: TaskRunnerContext;
private logger: Logger;
private taskInstance: AlertTaskInstance;
private alertType: NormalizedAlertType<Params, State, InstanceState, InstanceContext>;
private alertType: NormalizedAlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>;
private readonly alertTypeRegistry: AlertTypeRegistry;
constructor(
alertType: NormalizedAlertType<Params, State, InstanceState, InstanceContext>,
alertType: NormalizedAlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>,
taskInstance: ConcreteTaskInstance,
context: TaskRunnerContext
) {
@ -144,7 +161,14 @@ export class TaskRunner<
actions: Alert<Params>['actions'],
alertParams: Params
) {
return createExecutionHandler({
return createExecutionHandler<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>({
alertId,
alertName,
tags,
@ -163,7 +187,7 @@ export class TaskRunner<
async executeAlertInstance(
alertInstanceId: string,
alertInstance: AlertInstance<InstanceState, InstanceContext>,
executionHandler: ReturnType<typeof createExecutionHandler>
executionHandler: ExecutionHandler<ActionGroupIds | RecoveryActionGroupId>
) {
const {
actionGroup,
@ -180,7 +204,7 @@ export class TaskRunner<
services: Services,
alert: SanitizedAlert<Params>,
params: Params,
executionHandler: ReturnType<typeof createExecutionHandler>,
executionHandler: ExecutionHandler<ActionGroupIds | RecoveryActionGroupId>,
spaceId: string,
event: Event
): Promise<AlertTaskState> {
@ -218,9 +242,11 @@ export class TaskRunner<
alertId,
services: {
...services,
alertInstanceFactory: createAlertInstanceFactory<InstanceState, InstanceContext>(
alertInstances
),
alertInstanceFactory: createAlertInstanceFactory<
InstanceState,
InstanceContext,
WithoutReservedActionGroups<ActionGroupIds, RecoveryActionGroupId>
>(alertInstances),
},
params,
state: alertTypeState as State,
@ -278,7 +304,7 @@ export class TaskRunner<
if (!muteAll) {
const mutedInstanceIdsSet = new Set(mutedInstanceIds);
scheduleActionsForRecoveredInstances({
scheduleActionsForRecoveredInstances<InstanceState, InstanceContext, RecoveryActionGroupId>({
recoveryActionGroup: this.alertType.recoveryActionGroup,
recoveredAlertInstances,
executionHandler,
@ -615,20 +641,30 @@ function generateNewAndRecoveredInstanceEvents<
interface ScheduleActionsForRecoveredInstancesParams<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
InstanceContext extends AlertInstanceContext,
RecoveryActionGroupId extends string
> {
logger: Logger;
recoveryActionGroup: ActionGroup;
recoveredAlertInstances: Dictionary<AlertInstance<InstanceState, InstanceContext>>;
executionHandler: ReturnType<typeof createExecutionHandler>;
recoveryActionGroup: ActionGroup<RecoveryActionGroupId>;
recoveredAlertInstances: Dictionary<
AlertInstance<InstanceState, InstanceContext, RecoveryActionGroupId>
>;
executionHandler: ExecutionHandler<RecoveryActionGroupId | RecoveryActionGroupId>;
mutedInstanceIdsSet: Set<string>;
alertLabel: string;
}
function scheduleActionsForRecoveredInstances<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
>(params: ScheduleActionsForRecoveredInstancesParams<InstanceState, InstanceContext>) {
InstanceContext extends AlertInstanceContext,
RecoveryActionGroupId extends string
>(
params: ScheduleActionsForRecoveredInstancesParams<
InstanceState,
InstanceContext,
RecoveryActionGroupId
>
) {
const {
logger,
recoveryActionGroup,
@ -660,18 +696,31 @@ function scheduleActionsForRecoveredInstances<
interface LogActiveAndRecoveredInstancesParams<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
> {
logger: Logger;
activeAlertInstances: Dictionary<AlertInstance<InstanceState, InstanceContext>>;
recoveredAlertInstances: Dictionary<AlertInstance<InstanceState, InstanceContext>>;
activeAlertInstances: Dictionary<AlertInstance<InstanceState, InstanceContext, ActionGroupIds>>;
recoveredAlertInstances: Dictionary<
AlertInstance<InstanceState, InstanceContext, RecoveryActionGroupId>
>;
alertLabel: string;
}
function logActiveAndRecoveredInstances<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
>(params: LogActiveAndRecoveredInstancesParams<InstanceState, InstanceContext>) {
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
>(
params: LogActiveAndRecoveredInstancesParams<
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>
) {
const { logger, activeAlertInstances, recoveredAlertInstances, alertLabel } = params;
const activeInstanceIds = Object.keys(activeAlertInstances);
const recoveredInstanceIds = Object.keys(recoveredAlertInstances);

View file

@ -13,11 +13,19 @@ import {
import { RunContext } from '../../../task_manager/server';
import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server';
import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server';
import { AlertTypeRegistry, GetServicesFunction, SpaceIdToNamespaceFunction } from '../types';
import {
AlertTypeParams,
AlertTypeRegistry,
GetServicesFunction,
SpaceIdToNamespaceFunction,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
} from '../types';
import { TaskRunner } from './task_runner';
import { IEventLogger } from '../../../event_log/server';
import { AlertsClient } from '../alerts_client';
import { UntypedNormalizedAlertType } from '../alert_type_registry';
import { NormalizedAlertType } from '../alert_type_registry';
export interface TaskRunnerContext {
logger: Logger;
@ -44,11 +52,35 @@ export class TaskRunnerFactory {
this.taskRunnerContext = taskRunnerContext;
}
public create(alertType: UntypedNormalizedAlertType, { taskInstance }: RunContext) {
public create<
Params extends AlertTypeParams,
State extends AlertTypeState,
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
>(
alertType: NormalizedAlertType<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>,
{ taskInstance }: RunContext
) {
if (!this.isInitialized) {
throw new Error('TaskRunnerFactory not initialized');
}
return new TaskRunner(alertType, taskInstance, this.taskRunnerContext!);
return new TaskRunner<
Params,
State,
InstanceState,
InstanceContext,
ActionGroupIds,
RecoveryActionGroupId
>(alertType, taskInstance, this.taskRunnerContext!);
}
}

View file

@ -29,6 +29,7 @@ import {
AlertExecutionStatusErrorReasons,
AlertsHealth,
AlertNotifyWhenType,
WithoutReservedActionGroups,
} from '../common';
import { LicenseType } from '../../licensing/server';
@ -58,21 +59,25 @@ export interface Services {
export interface AlertServices<
InstanceState extends AlertInstanceState = AlertInstanceState,
InstanceContext extends AlertInstanceContext = AlertInstanceContext
InstanceContext extends AlertInstanceContext = AlertInstanceContext,
ActionGroupIds extends string = never
> extends Services {
alertInstanceFactory: (id: string) => PublicAlertInstance<InstanceState, InstanceContext>;
alertInstanceFactory: (
id: string
) => PublicAlertInstance<InstanceState, InstanceContext, ActionGroupIds>;
}
export interface AlertExecutorOptions<
Params extends AlertTypeParams = never,
State extends AlertTypeState = never,
InstanceState extends AlertInstanceState = never,
InstanceContext extends AlertInstanceContext = never
InstanceContext extends AlertInstanceContext = never,
ActionGroupIds extends string = never
> {
alertId: string;
startedAt: Date;
previousStartedAt: Date | null;
services: AlertServices<InstanceState, InstanceContext>;
services: AlertServices<InstanceState, InstanceContext, ActionGroupIds>;
params: Params;
state: State;
spaceId: string;
@ -92,9 +97,10 @@ export type ExecutorType<
Params extends AlertTypeParams = never,
State extends AlertTypeState = never,
InstanceState extends AlertInstanceState = never,
InstanceContext extends AlertInstanceContext = never
InstanceContext extends AlertInstanceContext = never,
ActionGroupIds extends string = never
> = (
options: AlertExecutorOptions<Params, State, InstanceState, InstanceContext>
options: AlertExecutorOptions<Params, State, InstanceState, InstanceContext, ActionGroupIds>
) => Promise<State | void>;
export interface AlertTypeParamsValidator<Params extends AlertTypeParams> {
@ -104,17 +110,29 @@ export interface AlertType<
Params extends AlertTypeParams = never,
State extends AlertTypeState = never,
InstanceState extends AlertInstanceState = never,
InstanceContext extends AlertInstanceContext = never
InstanceContext extends AlertInstanceContext = never,
ActionGroupIds extends string = never,
RecoveryActionGroupId extends string = never
> {
id: string;
name: string;
validate?: {
params?: AlertTypeParamsValidator<Params>;
};
actionGroups: ActionGroup[];
defaultActionGroupId: ActionGroup['id'];
recoveryActionGroup?: ActionGroup;
executor: ExecutorType<Params, State, InstanceState, InstanceContext>;
actionGroups: Array<ActionGroup<ActionGroupIds>>;
defaultActionGroupId: ActionGroup<ActionGroupIds>['id'];
recoveryActionGroup?: ActionGroup<RecoveryActionGroupId>;
executor: ExecutorType<
Params,
State,
InstanceState,
InstanceContext,
/**
* Ensure that the reserved ActionGroups (such as `Recovered`) are not
* available for scheduling in the Executor
*/
WithoutReservedActionGroups<ActionGroupIds, RecoveryActionGroupId>
>;
producer: string;
actionVariables?: {
context?: ActionVariable[];

View file

@ -6,6 +6,7 @@
import { i18n } from '@kbn/i18n';
import { ValuesType } from 'utility-types';
import { ActionGroup } from '../../alerts/common';
import { ANOMALY_SEVERITY, ANOMALY_THRESHOLD } from '../../ml/common';
export enum AlertType {
@ -15,20 +16,31 @@ export enum AlertType {
TransactionDurationAnomaly = 'apm.transaction_duration_anomaly',
}
const THRESHOLD_MET_GROUP = {
id: 'threshold_met',
export const THRESHOLD_MET_GROUP_ID = 'threshold_met';
export type ThresholdMetActionGroupId = typeof THRESHOLD_MET_GROUP_ID;
const THRESHOLD_MET_GROUP: ActionGroup<ThresholdMetActionGroupId> = {
id: THRESHOLD_MET_GROUP_ID,
name: i18n.translate('xpack.apm.a.thresholdMet', {
defaultMessage: 'Threshold met',
}),
};
export const ALERT_TYPES_CONFIG = {
export const ALERT_TYPES_CONFIG: Record<
AlertType,
{
name: string;
actionGroups: Array<ActionGroup<ThresholdMetActionGroupId>>;
defaultActionGroupId: ThresholdMetActionGroupId;
minimumLicenseRequired: string;
producer: string;
}
> = {
[AlertType.ErrorCount]: {
name: i18n.translate('xpack.apm.errorCountAlert.name', {
defaultMessage: 'Error count threshold',
}),
actionGroups: [THRESHOLD_MET_GROUP],
defaultActionGroupId: 'threshold_met',
defaultActionGroupId: THRESHOLD_MET_GROUP_ID,
minimumLicenseRequired: 'basic',
producer: 'apm',
},
@ -37,7 +49,7 @@ export const ALERT_TYPES_CONFIG = {
defaultMessage: 'Transaction duration threshold',
}),
actionGroups: [THRESHOLD_MET_GROUP],
defaultActionGroupId: 'threshold_met',
defaultActionGroupId: THRESHOLD_MET_GROUP_ID,
minimumLicenseRequired: 'basic',
producer: 'apm',
},
@ -46,7 +58,7 @@ export const ALERT_TYPES_CONFIG = {
defaultMessage: 'Transaction duration anomaly',
}),
actionGroups: [THRESHOLD_MET_GROUP],
defaultActionGroupId: 'threshold_met',
defaultActionGroupId: THRESHOLD_MET_GROUP_ID,
minimumLicenseRequired: 'basic',
producer: 'apm',
},
@ -55,7 +67,7 @@ export const ALERT_TYPES_CONFIG = {
defaultMessage: 'Transaction error rate threshold',
}),
actionGroups: [THRESHOLD_MET_GROUP],
defaultActionGroupId: 'threshold_met',
defaultActionGroupId: THRESHOLD_MET_GROUP_ID,
minimumLicenseRequired: 'basic',
producer: 'apm',
},

View file

@ -4,14 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ThresholdMetActionGroupId } from '../../../common/alert_types';
import {
ESSearchRequest,
ESSearchResponse,
} from '../../../../../typings/elasticsearch';
import { AlertServices } from '../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../alerts/server';
export function alertingEsClient<TParams extends ESSearchRequest>(
services: AlertServices,
services: AlertServices<
AlertInstanceState,
AlertInstanceContext,
ThresholdMetActionGroupId
>,
params: TParams
): Promise<ESSearchResponse<unknown, TParams>> {
return services.callCluster('search', {

View file

@ -4,13 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema } from '@kbn/config-schema';
import { schema, TypeOf } from '@kbn/config-schema';
import { isEmpty } from 'lodash';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { APMConfig } from '../..';
import { AlertingPlugin } from '../../../../alerts/server';
import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types';
import {
AlertingPlugin,
AlertInstanceContext,
AlertInstanceState,
AlertTypeState,
} from '../../../../alerts/server';
import {
AlertType,
ALERT_TYPES_CONFIG,
ThresholdMetActionGroupId,
} from '../../../common/alert_types';
import {
PROCESSOR_EVENT,
SERVICE_ENVIRONMENT,
@ -41,7 +50,13 @@ export function registerErrorCountAlertType({
alerts,
config$,
}: RegisterAlertParams) {
alerts.registerType({
alerts.registerType<
TypeOf<typeof paramsSchema>,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
ThresholdMetActionGroupId
>({
id: AlertType.ErrorCount,
name: alertTypeConfig.name,
actionGroups: alertTypeConfig.actionGroups,

View file

@ -10,6 +10,7 @@ import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_m
import { toMetricOpt } from '../../../../common/snapshot_metric_i18n';
import { AlertStates, InventoryMetricConditions } from './types';
import {
ActionGroup,
AlertInstanceContext,
AlertInstanceState,
RecoveredActionGroup,
@ -27,6 +28,7 @@ import {
stateToAlertMessage,
} from '../common/messages';
import { evaluateCondition } from './evaluate_condition';
import { InventoryMetricThresholdAllowedActionGroups } from './register_inventory_metric_threshold_alert_type';
interface InventoryMetricThresholdParams {
criteria: InventoryMetricConditions[];
@ -46,7 +48,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
Record<string, any>,
Record<string, any>,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
InventoryMetricThresholdAllowedActionGroups
>) => {
const {
criteria,
@ -115,18 +118,25 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
}
if (reason) {
const actionGroupId =
nextState === AlertStates.OK ? RecoveredActionGroup.id : FIRED_ACTIONS.id;
alertInstance.scheduleActions(actionGroupId, {
group: item,
alertState: stateToAlertMessage[nextState],
reason,
timestamp: moment().toISOString(),
value: mapToConditionsLookup(results, (result) =>
formatMetric(result[item].metric, result[item].currentValue)
),
threshold: mapToConditionsLookup(criteria, (c) => c.threshold),
metric: mapToConditionsLookup(criteria, (c) => c.metric),
});
nextState === AlertStates.OK ? RecoveredActionGroup.id : FIRED_ACTIONS_ID;
alertInstance.scheduleActions(
/**
* TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on
* the RecoveredActionGroup isn't allowed
*/
(actionGroupId as unknown) as InventoryMetricThresholdAllowedActionGroups,
{
group: item,
alertState: stateToAlertMessage[nextState],
reason,
timestamp: moment().toISOString(),
value: mapToConditionsLookup(results, (result) =>
formatMetric(result[item].metric, result[item].currentValue)
),
threshold: mapToConditionsLookup(criteria, (c) => c.threshold),
metric: mapToConditionsLookup(criteria, (c) => c.metric),
}
);
}
alertInstance.replaceState({
@ -160,8 +170,9 @@ const mapToConditionsLookup = (
{}
);
export const FIRED_ACTIONS = {
id: 'metrics.invenotry_threshold.fired',
export const FIRED_ACTIONS_ID = 'metrics.invenotry_threshold.fired';
export const FIRED_ACTIONS: ActionGroup<typeof FIRED_ACTIONS_ID> = {
id: FIRED_ACTIONS_ID,
name: i18n.translate('xpack.infra.metrics.alerting.inventory.threshold.fired', {
defaultMessage: 'Fired',
}),

View file

@ -9,6 +9,7 @@ import { AlertType, AlertInstanceState, AlertInstanceContext } from '../../../..
import {
createInventoryMetricThresholdExecutor,
FIRED_ACTIONS,
FIRED_ACTIONS_ID,
} from './inventory_metric_threshold_executor';
import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, Comparator } from './types';
import { InfraBackendLibs } from '../../infra_types';
@ -22,6 +23,7 @@ import {
metricActionVariableDescription,
thresholdActionVariableDescription,
} from '../common/messages';
import { RecoveredActionGroupId } from '../../../../../alerts/common';
const condition = schema.object({
threshold: schema.arrayOf(schema.number()),
@ -40,6 +42,8 @@ const condition = schema.object({
),
});
export type InventoryMetricThresholdAllowedActionGroups = typeof FIRED_ACTIONS_ID;
export const registerMetricInventoryThresholdAlertType = (
libs: InfraBackendLibs
): AlertType<
@ -49,7 +53,9 @@ export const registerMetricInventoryThresholdAlertType = (
Record<string, any>,
Record<string, any>,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
InventoryMetricThresholdAllowedActionGroups,
RecoveredActionGroupId
> => ({
id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
name: i18n.translate('xpack.infra.metrics.inventory.alertName', {
@ -69,7 +75,7 @@ export const registerMetricInventoryThresholdAlertType = (
{ unknowns: 'allow' }
),
},
defaultActionGroupId: FIRED_ACTIONS.id,
defaultActionGroupId: FIRED_ACTIONS_ID,
actionGroups: [FIRED_ACTIONS],
producer: 'infrastructure',
minimumLicenseRequired: 'basic',

View file

@ -13,6 +13,8 @@ import {
AlertTypeState,
AlertInstanceContext,
AlertInstanceState,
ActionGroup,
ActionGroupIdsOf,
} from '../../../../../alerts/server';
import {
AlertStates,
@ -37,12 +39,18 @@ import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds';
import { decodeOrThrow } from '../../../../common/runtime_types';
import { UNGROUPED_FACTORY_KEY } from '../common/utils';
type LogThresholdAlertServices = AlertServices<AlertInstanceState, AlertInstanceContext>;
type LogThresholdActionGroups = ActionGroupIdsOf<typeof FIRED_ACTIONS>;
type LogThresholdAlertServices = AlertServices<
AlertInstanceState,
AlertInstanceContext,
LogThresholdActionGroups
>;
type LogThresholdAlertExecutorOptions = AlertExecutorOptions<
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
LogThresholdActionGroups
>;
const COMPOSITE_GROUP_SIZE = 40;
@ -344,9 +352,9 @@ export const processGroupByRatioResults = (
};
type AlertInstanceUpdater = (
alertInstance: AlertInstance,
alertInstance: AlertInstance<AlertInstanceState, AlertInstanceContext, LogThresholdActionGroups>,
state: AlertStates,
actions?: Array<{ actionGroup: string; context: AlertInstanceContext }>
actions?: Array<{ actionGroup: LogThresholdActionGroups; context: AlertInstanceContext }>
) => void;
export const updateAlertInstance: AlertInstanceUpdater = (alertInstance, state, actions) => {
@ -653,8 +661,9 @@ const createConditionsMessageForCriteria = (criteria: CountCriteria) => {
// When the Alerting plugin implements support for multiple action groups, add additional
// action groups here to send different messages, e.g. a recovery notification
export const FIRED_ACTIONS = {
id: 'logs.threshold.fired',
export const LogsThresholdFiredActionGroupId = 'logs.threshold.fired';
export const FIRED_ACTIONS: ActionGroup<'logs.threshold.fired'> = {
id: LogsThresholdFiredActionGroupId,
name: i18n.translate('xpack.infra.logs.alerting.threshold.fired', {
defaultMessage: 'Fired',
}),

View file

@ -4,7 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { PluginSetupContract } from '../../../../../alerts/server';
import {
PluginSetupContract,
AlertTypeParams,
AlertTypeState,
AlertInstanceContext,
AlertInstanceState,
ActionGroupIdsOf,
} from '../../../../../alerts/server';
import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor';
import {
LOG_DOCUMENT_COUNT_ALERT_TYPE_ID,
@ -79,7 +86,13 @@ export async function registerLogThresholdAlertType(
);
}
alertingPlugin.registerType({
alertingPlugin.registerType<
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
ActionGroupIdsOf<typeof FIRED_ACTIONS>
>({
id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID,
name: i18n.translate('xpack.infra.logs.alertName', {
defaultMessage: 'Log threshold',

View file

@ -10,6 +10,7 @@ import {
AlertInstanceState,
AlertInstanceContext,
AlertExecutorOptions,
ActionGroupIdsOf,
} from '../../../../../alerts/server';
import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api/metrics_explorer';
import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor';
@ -33,7 +34,8 @@ export type MetricThresholdAlertType = AlertType<
Record<string, any>,
Record<string, any>,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
ActionGroupIdsOf<typeof FIRED_ACTIONS>
>;
export type MetricThresholdAlertExecutorOptions = AlertExecutorOptions<
/**
@ -42,7 +44,8 @@ export type MetricThresholdAlertExecutorOptions = AlertExecutorOptions<
Record<string, any>,
Record<string, any>,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
ActionGroupIdsOf<typeof FIRED_ACTIONS>
>;
export function registerMetricThresholdAlertType(libs: InfraBackendLibs): MetricThresholdAlertType {

View file

@ -93,7 +93,7 @@ export class BaseAlert {
this.scopedLogger = Globals.app.getLogger(alertOptions.id!);
}
public getAlertType(): AlertType {
public getAlertType(): AlertType<never, never, never, never, 'default'> {
const { id, name, actionVariables } = this.alertOptions;
return {
id,
@ -108,8 +108,11 @@ export class BaseAlert {
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: (options: AlertExecutorOptions & { state: ExecutedState }): Promise<any> =>
this.execute(options),
executor: (
options: AlertExecutorOptions<never, never, AlertInstanceState, never, 'default'> & {
state: ExecutedState;
}
): Promise<any> => this.execute(options),
producer: 'monitoring',
actionVariables: {
context: actionVariables,
@ -238,7 +241,9 @@ export class BaseAlert {
services,
params,
state,
}: AlertExecutorOptions & { state: ExecutedState }): Promise<any> {
}: AlertExecutorOptions<never, never, AlertInstanceState, never, 'default'> & {
state: ExecutedState;
}): Promise<any> {
this.scopedLogger.debug(
`Executing alert with params: ${JSON.stringify(params)} and state: ${JSON.stringify(state)}`
);
@ -333,7 +338,7 @@ export class BaseAlert {
protected async processData(
data: AlertData[],
clusters: AlertCluster[],
services: AlertServices,
services: AlertServices<AlertInstanceState, never, 'default'>,
state: ExecutedState
) {
const currentUTC = +new Date();
@ -387,7 +392,7 @@ export class BaseAlert {
protected async processLegacyData(
data: AlertData[],
clusters: AlertCluster[],
services: AlertServices,
services: AlertServices<AlertInstanceState, never, 'default'>,
state: ExecutedState
) {
const currentUTC = +new Date();

View file

@ -105,7 +105,7 @@ export const isNotificationAlertExecutor = (
};
export type NotificationAlertTypeDefinition = Omit<
AlertType<AlertTypeParams, AlertTypeState, AlertInstanceState, AlertInstanceContext>,
AlertType<AlertTypeParams, AlertTypeState, AlertInstanceState, AlertInstanceContext, 'default'>,
'executor'
> & {
executor: ({

View file

@ -8,7 +8,11 @@ import { flow, omit } from 'lodash/fp';
import set from 'set-value';
import { Logger } from '../../../../../../../src/core/server';
import { AlertServices } from '../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../../alerts/server';
import { RuleAlertAction } from '../../../../common/detection_engine/types';
import { RuleTypeParams, RefreshTypes } from '../types';
import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create';
@ -20,7 +24,7 @@ interface BulkCreateMlSignalsParams {
actions: RuleAlertAction[];
someResult: AnomalyResults;
ruleParams: RuleTypeParams;
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
logger: Logger;
id: string;
signalsIndex: string;

View file

@ -12,7 +12,11 @@ import {
TimestampOverrideOrUndefined,
} from '../../../../common/detection_engine/schemas/common/schemas';
import { Logger } from '../../../../../../../src/core/server';
import { AlertServices } from '../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../../alerts/server';
import { RuleAlertAction } from '../../../../common/detection_engine/types';
import { RuleTypeParams, RefreshTypes } from '../types';
import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create';
@ -24,7 +28,7 @@ interface BulkCreateThresholdSignalsParams {
actions: RuleAlertAction[];
someResult: SignalSearchResponse;
ruleParams: RuleTypeParams;
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
inputIndexPattern: string[];
logger: Logger;
id: string;

View file

@ -12,7 +12,11 @@ import {
} from '../../../../common/detection_engine/schemas/common/schemas';
import { singleSearchAfter } from './single_search_after';
import { AlertServices } from '../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../../alerts/server';
import { Logger } from '../../../../../../../src/core/server';
import { SignalSearchResponse } from './types';
import { BuildRuleMessage } from './rule_messages';
@ -21,7 +25,7 @@ interface FindThresholdSignalsParams {
from: string;
to: string;
inputIndexPattern: string[];
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
logger: Logger;
filter: unknown;
threshold: Threshold;

View file

@ -15,7 +15,11 @@ import {
Language,
} from '../../../../common/detection_engine/schemas/common/schemas';
import { ExceptionListItemSchema } from '../../../../../lists/common/schemas';
import { AlertServices } from '../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../../alerts/server';
import { PartialFilter } from '../types';
import { BadRequestError } from '../errors/bad_request_error';
import { QueryFilter } from './types';
@ -26,7 +30,7 @@ interface GetFilterArgs {
language: LanguageOrUndefined;
query: QueryOrUndefined;
savedId: SavedIdOrUndefined;
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
index: IndexOrUndefined;
lists: ExceptionListItemSchema[];
}

View file

@ -5,10 +5,14 @@
*/
import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants';
import { AlertServices } from '../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../../alerts/server';
export const getInputIndex = async (
services: AlertServices,
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>,
version: string,
inputIndex: string[] | null | undefined
): Promise<string[]> => {

View file

@ -5,8 +5,9 @@
*/
import { i18n } from '@kbn/i18n';
import { ActionGroup } from '../../../../../alerts/common';
export const siemRuleActionGroups = [
export const siemRuleActionGroups: Array<ActionGroup<'default'>> = [
{
id: 'default',
name: i18n.translate(

View file

@ -6,7 +6,11 @@
import { countBy, isEmpty, get } from 'lodash';
import { performance } from 'perf_hooks';
import { AlertServices } from '../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../../alerts/server';
import { SignalSearchResponse, BulkResponse, SignalHit, WrappedSignalHit } from './types';
import { RuleAlertAction } from '../../../../common/detection_engine/types';
import { RuleTypeParams, RefreshTypes } from '../types';
@ -19,7 +23,7 @@ import { isEventTypeSignal } from './build_event_type_signal';
interface SingleBulkCreateParams {
filteredEvents: SignalSearchResponse;
ruleParams: RuleTypeParams;
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
logger: Logger;
id: string;
signalsIndex: string;
@ -222,7 +226,7 @@ export const singleBulkCreate = async ({
export const bulkInsertSignals = async (
signals: WrappedSignalHit[],
logger: Logger,
services: AlertServices,
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>,
refresh: RefreshTypes
): Promise<BulkInsertSignalsResponse> => {
// index documents after creating an ID based on the

View file

@ -5,7 +5,11 @@
*/
import { performance } from 'perf_hooks';
import { AlertServices } from '../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../../alerts/server';
import { Logger } from '../../../../../../../src/core/server';
import { SignalSearchResponse } from './types';
import { BuildRuleMessage } from './rule_messages';
@ -22,7 +26,7 @@ interface SingleSearchAfterParams {
index: string[];
from: string;
to: string;
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
logger: Logger;
pageSize: number;
sortOrder?: SortOrderOrUndefined;

View file

@ -20,7 +20,11 @@ import {
ItemsPerSearch,
} from '../../../../../common/detection_engine/schemas/types/threat_mapping';
import { PartialFilter, RuleTypeParams } from '../../types';
import { AlertServices } from '../../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../../../alerts/server';
import { ExceptionListItemSchema } from '../../../../../../lists/common/schemas';
import { ILegacyScopedClusterClient, Logger } from '../../../../../../../../src/core/server';
import { RuleAlertAction } from '../../../../../common/detection_engine/types';
@ -38,7 +42,7 @@ export interface CreateThreatSignalsOptions {
filters: PartialFilter[];
language: LanguageOrUndefined;
savedId: string | undefined;
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
exceptionItems: ExceptionListItemSchema[];
gap: Duration | null;
previousStartedAt: Date | null;
@ -77,7 +81,7 @@ export interface CreateThreatSignalOptions {
filters: PartialFilter[];
language: LanguageOrUndefined;
savedId: string | undefined;
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
exceptionItems: ExceptionListItemSchema[];
gap: Duration | null;
previousStartedAt: Date | null;

View file

@ -7,7 +7,11 @@
import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas';
import { singleSearchAfter } from './single_search_after';
import { AlertServices } from '../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../../alerts/server';
import { Logger } from '../../../../../../../src/core/server';
import { SignalSearchResponse } from './types';
import { BuildRuleMessage } from './rule_messages';
@ -16,7 +20,7 @@ interface FindPreviousThresholdSignalsParams {
from: string;
to: string;
indexPattern: string[];
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
logger: Logger;
ruleId: string;
bucketByField: string;

View file

@ -10,7 +10,11 @@ import { Filter } from 'src/plugins/data/common';
import { ESFilter } from '../../../../../../typings/elasticsearch';
import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas';
import { AlertServices } from '../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
} from '../../../../../alerts/server';
import { Logger } from '../../../../../../../src/core/server';
import { ThresholdQueryBucket } from './types';
import { BuildRuleMessage } from './rule_messages';
@ -20,7 +24,7 @@ interface GetThresholdBucketFiltersParams {
from: string;
to: string;
indexPattern: string[];
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
logger: Logger;
ruleId: string;
bucketByField: string;

View file

@ -141,7 +141,13 @@ export type RuleExecutorOptions = AlertExecutorOptions<
// since we are only increasing the strictness of params.
export const isAlertExecutor = (
obj: SignalRuleAlertTypeDefinition
): obj is AlertType<RuleTypeParams, AlertTypeState, AlertInstanceState, AlertInstanceContext> => {
): obj is AlertType<
RuleTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
'default'
> => {
return true;
};
@ -149,7 +155,8 @@ export type SignalRuleAlertTypeDefinition = AlertType<
RuleTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
'default'
>;
export interface Ancestor {
@ -224,7 +231,7 @@ export interface SearchAfterAndBulkCreateParams {
gap: moment.Duration | null;
previousStartedAt: Date | null | undefined;
ruleParams: RuleTypeParams;
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
listClient: ListClient;
exceptionsList: ExceptionListItemSchema[];
logger: Logger;

View file

@ -10,7 +10,12 @@ import dateMath from '@elastic/datemath';
import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas';
import { Logger, SavedObjectsClientContract } from '../../../../../../../src/core/server';
import { AlertServices, parseDuration } from '../../../../../alerts/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertServices,
parseDuration,
} from '../../../../../alerts/server';
import { ExceptionListClient, ListClient, ListPluginSetup } from '../../../../../lists/server';
import { ExceptionListItemSchema } from '../../../../../lists/common/schemas';
import { ListArray } from '../../../../common/detection_engine/schemas/types/lists';
@ -52,7 +57,10 @@ export const shorthandMap = {
},
};
export const checkPrivileges = async (services: AlertServices, indices: string[]) =>
export const checkPrivileges = async (
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>,
indices: string[]
) =>
services.callCluster('transport.request', {
path: '/_security/user/_has_privileges',
method: 'POST',
@ -154,7 +162,7 @@ export const getListsClient = ({
lists: ListPluginSetup | undefined;
spaceId: string;
updatedByUser: string | null;
services: AlertServices;
services: AlertServices<AlertInstanceState, AlertInstanceContext, 'default'>;
savedObjectClient: SavedObjectsClientContract;
}): {
listClient: ListClient;

View file

@ -20,6 +20,7 @@ import { Query } from '../../../../../../src/plugins/data/common/query';
export const GEO_CONTAINMENT_ID = '.geo-containment';
export const ActionGroupId = 'Tracked entity contained';
export const RecoveryActionGroupId = 'notGeoContained';
const actionVariableContextEntityIdLabel = i18n.translate(
'xpack.stackAlerts.geoContainment.actionVariableContextEntityIdLabel',
@ -141,7 +142,9 @@ export type GeoContainmentAlertType = AlertType<
GeoContainmentParams,
GeoContainmentState,
GeoContainmentInstanceState,
GeoContainmentInstanceContext
GeoContainmentInstanceContext,
typeof ActionGroupId,
typeof RecoveryActionGroupId
>;
export function getAlertType(logger: Logger): GeoContainmentAlertType {
@ -161,7 +164,7 @@ export function getAlertType(logger: Logger): GeoContainmentAlertType {
name: alertTypeName,
actionGroups: [{ id: ActionGroupId, name: actionGroupName }],
recoveryActionGroup: {
id: 'notGeoContained',
id: RecoveryActionGroupId,
name: i18n.translate('xpack.stackAlerts.geoContainment.notGeoContained', {
defaultMessage: 'No longer contained',
}),

View file

@ -100,7 +100,8 @@ export function getActiveEntriesAndGenerateAlerts(
currLocationMap: Map<string, LatestEntityLocation>,
alertInstanceFactory: AlertServices<
GeoContainmentInstanceState,
GeoContainmentInstanceContext
GeoContainmentInstanceContext,
typeof ActionGroupId
>['alertInstanceFactory'],
shapesIdsNamesMap: Record<string, unknown>,
currIntervalEndTime: Date

View file

@ -12,6 +12,8 @@ import {
GeoContainmentInstanceState,
GeoContainmentInstanceContext,
getAlertType,
ActionGroupId,
RecoveryActionGroupId,
} from './alert_type';
interface RegisterParams {
@ -25,6 +27,8 @@ export function register(params: RegisterParams) {
GeoContainmentParams,
GeoContainmentState,
GeoContainmentInstanceState,
GeoContainmentInstanceContext
GeoContainmentInstanceContext,
typeof ActionGroupId,
typeof RecoveryActionGroupId
>(getAlertType(logger));
}

View file

@ -209,7 +209,8 @@ export type GeoThresholdAlertType = AlertType<
GeoThresholdParams,
GeoThresholdState,
GeoThresholdInstanceState,
GeoThresholdInstanceContext
GeoThresholdInstanceContext,
typeof ActionGroupId
>;
export function getAlertType(logger: Logger): GeoThresholdAlertType {
const alertTypeName = i18n.translate('xpack.stackAlerts.geoThreshold.alertTypeTitle', {

View file

@ -42,7 +42,7 @@ export const ComparatorFnNames = new Set(ComparatorFns.keys());
export function getAlertType(
logger: Logger,
data: Promise<StackAlertsStartDeps['triggersActionsUi']['data']>
): AlertType<Params, {}, {}, ActionContext> {
): AlertType<Params, {}, {}, ActionContext, typeof ActionGroupId> {
const alertTypeName = i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeTitle', {
defaultMessage: 'Index threshold',
});
@ -148,7 +148,9 @@ export function getAlertType(
producer: STACK_ALERTS_FEATURE_ID,
};
async function executor(options: AlertExecutorOptions<Params, {}, {}, ActionContext>) {
async function executor(
options: AlertExecutorOptions<Params, {}, {}, ActionContext, typeof ActionGroupId>
) {
const { alertId, name, services, params } = options;
const compareFn = ComparatorFns.get(params.thresholdComparator);

View file

@ -39,7 +39,7 @@ import { ActionGroup, AlertActionParam } from '../../../../../alerts/common';
import { useKibana } from '../../../common/lib/kibana';
import { DefaultActionParamsGetter } from '../../lib/get_defaults_for_action_params';
export interface ActionGroupWithMessageVariables extends ActionGroup {
export interface ActionGroupWithMessageVariables extends ActionGroup<string> {
omitOptionalMessageVariables?: boolean;
defaultActionMessage?: string;
}

View file

@ -11,6 +11,7 @@ import { Alert, ActionType, AlertTypeModel, AlertType } from '../../../../types'
import { EuiTitle, EuiBadge, EuiFlexItem, EuiSwitch, EuiButtonEmpty, EuiText } from '@elastic/eui';
import { ViewInApp } from './view_in_app';
import {
ActionGroup,
AlertExecutionStatusErrorReasons,
ALERTS_FEATURE_ID,
} from '../../../../../../alerts/common';
@ -47,7 +48,7 @@ const mockAlertApis = {
const authorizedConsumers = {
[ALERTS_FEATURE_ID]: { read: true, all: true },
};
const recoveryActionGroup = { id: 'recovered', name: 'Recovered' };
const recoveryActionGroup: ActionGroup<'recovered'> = { id: 'recovered', name: 'Recovered' };
describe('alert_details', () => {
// mock Api handlers

View file

@ -231,7 +231,7 @@ const INACTIVE_LABEL = i18n.translate(
function getActionGroupName(alertType: AlertType, actionGroupId?: string): string | undefined {
actionGroupId = actionGroupId || alertType.defaultActionGroupId;
const actionGroup = alertType?.actionGroups?.find(
(group: ActionGroup) => group.id === actionGroupId
(group: ActionGroup<string>) => group.id === actionGroupId
);
return actionGroup?.name;
}

View file

@ -61,7 +61,7 @@ describe('alert_conditions', () => {
const ConditionForm = ({
actionGroup,
}: {
actionGroup?: ActionGroupWithCondition<{ someProp: string }>;
actionGroup?: ActionGroupWithCondition<{ someProp: string }, string>;
}) => {
return (
<EuiDescriptionList>
@ -113,7 +113,7 @@ describe('alert_conditions', () => {
const ConditionForm = ({
actionGroup,
}: {
actionGroup?: ActionGroupWithCondition<{ someProp: string }>;
actionGroup?: ActionGroupWithCondition<{ someProp: string }, string>;
}) => {
return (
<EuiDescriptionList>
@ -165,7 +165,7 @@ describe('alert_conditions', () => {
const ConditionForm = ({
actionGroup,
}: {
actionGroup?: ActionGroupWithCondition<{ someProp: string }>;
actionGroup?: ActionGroupWithCondition<{ someProp: string }, string>;
}) => {
return (
<EuiDescriptionList>
@ -218,8 +218,10 @@ describe('alert_conditions', () => {
actionGroup,
someCallbackProp,
}: {
actionGroup?: ActionGroupWithCondition<{ someProp: string }>;
someCallbackProp: (actionGroup: ActionGroupWithCondition<{ someProp: string }>) => void;
actionGroup?: ActionGroupWithCondition<{ someProp: string }, string>;
someCallbackProp: (
actionGroup: ActionGroupWithCondition<{ someProp: string }, string>
) => void;
}) => {
if (!actionGroup) {
return <div />;

View file

@ -11,7 +11,10 @@ import { ActionGroup, getBuiltinActionGroups } from '../../../../../alerts/commo
const BUILT_IN_ACTION_GROUPS: Set<string> = new Set(getBuiltinActionGroups().map(({ id }) => id));
export type ActionGroupWithCondition<T> = ActionGroup &
export type ActionGroupWithCondition<
T,
ActionGroupIds extends string
> = ActionGroup<ActionGroupIds> &
(
| // allow isRequired=false with or without conditions
{
@ -25,22 +28,26 @@ export type ActionGroupWithCondition<T> = ActionGroup &
}
);
export interface AlertConditionsProps<ConditionProps> {
export interface AlertConditionsProps<ConditionProps, ActionGroupIds extends string> {
headline?: string;
actionGroups: Array<ActionGroupWithCondition<ConditionProps>>;
onInitializeConditionsFor?: (actionGroup: ActionGroupWithCondition<ConditionProps>) => void;
onResetConditionsFor?: (actionGroup: ActionGroupWithCondition<ConditionProps>) => void;
actionGroups: Array<ActionGroupWithCondition<ConditionProps, ActionGroupIds>>;
onInitializeConditionsFor?: (
actionGroup: ActionGroupWithCondition<ConditionProps, ActionGroupIds>
) => void;
onResetConditionsFor?: (
actionGroup: ActionGroupWithCondition<ConditionProps, ActionGroupIds>
) => void;
includeBuiltInActionGroups?: boolean;
}
export const AlertConditions = <ConditionProps extends any>({
export const AlertConditions = <ConditionProps extends any, ActionGroupIds extends string>({
headline,
actionGroups,
onInitializeConditionsFor,
onResetConditionsFor,
includeBuiltInActionGroups = false,
children,
}: PropsWithChildren<AlertConditionsProps<ConditionProps>>) => {
}: PropsWithChildren<AlertConditionsProps<ConditionProps, ActionGroupIds>>) => {
const [withConditions, withoutConditions] = partition(
includeBuiltInActionGroups
? actionGroups

View file

@ -9,8 +9,8 @@ import { EuiFormRow, EuiButtonIcon, EuiTitle } from '@elastic/eui';
import { AlertConditionsProps, ActionGroupWithCondition } from './alert_conditions';
export type AlertConditionsGroupProps<ConditionProps> = {
actionGroup?: ActionGroupWithCondition<ConditionProps>;
} & Pick<AlertConditionsProps<ConditionProps>, 'onResetConditionsFor'>;
actionGroup?: ActionGroupWithCondition<ConditionProps, string>;
} & Pick<AlertConditionsProps<ConditionProps, string>, 'onResetConditionsFor'>;
export const AlertConditionsGroup = <ConditionProps extends unknown>({
actionGroup,

View file

@ -155,9 +155,11 @@ export const OPTIONAL_ACTION_VARIABLES = ['context'] as const;
export type ActionVariables = AsActionVariables<typeof REQUIRED_ACTION_VARIABLES[number]> &
Partial<AsActionVariables<typeof OPTIONAL_ACTION_VARIABLES[number]>>;
export interface AlertType
extends Pick<
CommonAlertType,
export interface AlertType<
ActionGroupIds extends string = string,
RecoveryActionGroupId extends string = string
> extends Pick<
CommonAlertType<ActionGroupIds, RecoveryActionGroupId>,
| 'id'
| 'name'
| 'actionGroups'
@ -184,7 +186,8 @@ export interface AlertTableItem extends Alert {
export interface AlertTypeParamsExpressionProps<
Params extends AlertTypeParams = AlertTypeParams,
MetaData = Record<string, any>
MetaData = Record<string, any>,
ActionGroupIds extends string = string
> {
alertParams: Params;
alertInterval: string;
@ -196,7 +199,7 @@ export interface AlertTypeParamsExpressionProps<
) => void;
errors: IErrorObject;
defaultActionGroupId: string;
actionGroups: ActionGroup[];
actionGroups: Array<ActionGroup<ActionGroupIds>>;
metadata?: MetaData;
charts: ChartsPluginSetup;
data: DataPublicPluginStart;

View file

@ -4,14 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
interface ActionGroupDefinition {
id: string;
name: string;
}
import { ActionGroup } from '../../../alerts/common';
type ActionGroupDefinitions = Record<string, ActionGroupDefinition>;
export const ACTION_GROUP_DEFINITIONS: ActionGroupDefinitions = {
export const ACTION_GROUP_DEFINITIONS: {
MONITOR_STATUS: ActionGroup<'xpack.uptime.alerts.actionGroups.monitorStatus'>;
TLS: ActionGroup<'xpack.uptime.alerts.actionGroups.tls'>;
DURATION_ANOMALY: ActionGroup<'xpack.uptime.alerts.actionGroups.durationAnomaly'>;
} = {
MONITOR_STATUS: {
id: 'xpack.uptime.alerts.actionGroups.monitorStatus',
name: 'Uptime Down Monitor',

View file

@ -874,7 +874,13 @@ describe('status check alert', () => {
});
describe('alert factory', () => {
let alert: AlertType<AlertTypeParams, AlertTypeState, AlertInstanceState, AlertInstanceContext>;
let alert: AlertType<
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
'xpack.uptime.alerts.actionGroups.monitorStatus'
>;
beforeEach(() => {
const { server, libs, plugins } = bootstrapDependencies();

View file

@ -7,6 +7,7 @@
import { KibanaRequest, SavedObjectsClientContract } from 'kibana/server';
import moment from 'moment';
import { schema } from '@kbn/config-schema';
import { ActionGroupIdsOf } from '../../../../alerts/common';
import { updateState } from './common';
import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts';
import { commonStateTranslations, durationAnomalyTranslations } from './translations';
@ -20,6 +21,7 @@ import { getLatestMonitor } from '../requests/get_latest_monitor';
import { uptimeAlertWrapper } from './uptime_alert_wrapper';
const { DURATION_ANOMALY } = ACTION_GROUP_DEFINITIONS;
export type ActionGroupIds = ActionGroupIdsOf<typeof DURATION_ANOMALY>;
export const getAnomalySummary = (anomaly: AnomaliesTableRecord, monitorInfo: Ping) => {
return {
@ -61,8 +63,12 @@ const getAnomalies = async (
);
};
export const durationAnomalyAlertFactory: UptimeAlertTypeFactory = (_server, _libs, plugins) =>
uptimeAlertWrapper({
export const durationAnomalyAlertFactory: UptimeAlertTypeFactory<ActionGroupIds> = (
_server,
_libs,
plugins
) =>
uptimeAlertWrapper<ActionGroupIds>({
id: 'xpack.uptime.alerts.durationAnomaly',
name: durationAnomalyTranslations.alertFactoryName,
validate: {

View file

@ -5,12 +5,15 @@
*/
import { UptimeAlertTypeFactory } from './types';
import { statusCheckAlertFactory } from './status_check';
import { tlsAlertFactory } from './tls';
import { durationAnomalyAlertFactory } from './duration_anomaly';
export const uptimeAlertTypeFactories: UptimeAlertTypeFactory[] = [
statusCheckAlertFactory,
tlsAlertFactory,
import { statusCheckAlertFactory, ActionGroupIds as statusCheckActionGroup } from './status_check';
import { tlsAlertFactory, ActionGroupIds as tlsActionGroup } from './tls';
import {
durationAnomalyAlertFactory,
];
ActionGroupIds as durationAnomalyActionGroup,
} from './duration_anomaly';
export const uptimeAlertTypeFactories: [
UptimeAlertTypeFactory<statusCheckActionGroup>,
UptimeAlertTypeFactory<tlsActionGroup>,
UptimeAlertTypeFactory<durationAnomalyActionGroup>
] = [statusCheckAlertFactory, tlsAlertFactory, durationAnomalyAlertFactory];

View file

@ -7,6 +7,7 @@
import { schema } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import Mustache from 'mustache';
import { ActionGroupIdsOf } from '../../../../alerts/common';
import { UptimeAlertTypeFactory } from './types';
import { esKuery } from '../../../../../../src/plugins/data/server';
import { JsonObject } from '../../../../../../src/plugins/kibana_utils/common';
@ -28,6 +29,7 @@ import { getUptimeIndexPattern, IndexPatternTitleAndFields } from '../requests/g
import { UMServerLibs, UptimeESClient } from '../lib';
const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS;
export type ActionGroupIds = ActionGroupIdsOf<typeof MONITOR_STATUS>;
const getMonIdByLoc = (monitorId: string, location: string) => {
return monitorId + '-' + location;
@ -178,8 +180,8 @@ const getInstanceId = (monitorInfo: Ping, monIdByLoc: string) => {
return `${urlText}_${monIdByLoc}`;
};
export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) =>
uptimeAlertWrapper({
export const statusCheckAlertFactory: UptimeAlertTypeFactory<ActionGroupIds> = (_server, libs) =>
uptimeAlertWrapper<ActionGroupIds>({
id: 'xpack.uptime.alerts.monitorStatus',
name: i18n.translate('xpack.uptime.alerts.monitorStatus', {
defaultMessage: 'Uptime monitor status',

View file

@ -14,8 +14,10 @@ import { Cert, CertResult } from '../../../common/runtime_types';
import { commonStateTranslations, tlsTranslations } from './translations';
import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs';
import { uptimeAlertWrapper } from './uptime_alert_wrapper';
import { ActionGroupIdsOf } from '../../../../alerts/common';
const { TLS } = ACTION_GROUP_DEFINITIONS;
export type ActionGroupIds = ActionGroupIdsOf<typeof TLS>;
const DEFAULT_SIZE = 20;
@ -82,8 +84,8 @@ export const getCertSummary = (
};
};
export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) =>
uptimeAlertWrapper({
export const tlsAlertFactory: UptimeAlertTypeFactory<ActionGroupIds> = (_server, libs) =>
uptimeAlertWrapper<ActionGroupIds>({
id: 'xpack.uptime.alerts.tls',
name: tlsTranslations.alertFactoryName,
validate: {

View file

@ -10,7 +10,7 @@ import { AlertType, AlertInstanceState, AlertInstanceContext } from '../../../..
export type UptimeAlertTypeParam = Record<string, any>;
export type UptimeAlertTypeState = Record<string, any>;
export type UptimeAlertTypeFactory = (
export type UptimeAlertTypeFactory<ActionGroupIds extends string> = (
server: UptimeCoreSetup,
libs: UMServerLibs,
plugins: UptimeCorePlugins
@ -18,5 +18,6 @@ export type UptimeAlertTypeFactory = (
UptimeAlertTypeParam,
UptimeAlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
ActionGroupIds
>;

View file

@ -15,8 +15,8 @@ import { DynamicSettings } from '../../../common/runtime_types';
import { createUptimeESClient, UptimeESClient } from '../lib';
import { UptimeAlertTypeFactory, UptimeAlertTypeParam, UptimeAlertTypeState } from './types';
export interface UptimeAlertType
extends Omit<ReturnType<UptimeAlertTypeFactory>, 'executor' | 'producer'> {
export interface UptimeAlertType<ActionGroupIds extends string>
extends Omit<ReturnType<UptimeAlertTypeFactory<ActionGroupIds>>, 'executor' | 'producer'> {
executor: ({
options,
uptimeEsClient,
@ -26,7 +26,8 @@ export interface UptimeAlertType
UptimeAlertTypeParam,
UptimeAlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
ActionGroupIds
>;
uptimeEsClient: UptimeESClient;
dynamicSettings: DynamicSettings;
@ -34,7 +35,9 @@ export interface UptimeAlertType
}) => Promise<UptimeAlertTypeState | void>;
}
export const uptimeAlertWrapper = (uptimeAlert: UptimeAlertType) => ({
export const uptimeAlertWrapper = <ActionGroupIds extends string>(
uptimeAlert: UptimeAlertType<ActionGroupIds>
) => ({
...uptimeAlert,
producer: 'uptime',
executor: async (
@ -42,7 +45,8 @@ export const uptimeAlertWrapper = (uptimeAlert: UptimeAlertType) => ({
UptimeAlertTypeParam,
UptimeAlertTypeState,
AlertInstanceState,
AlertInstanceContext
AlertInstanceContext,
ActionGroupIds
>
) => {
const {

View file

@ -61,7 +61,13 @@ function getAlwaysFiringAlertType() {
interface InstanceContext extends AlertInstanceContext {
instanceContextValue: boolean;
}
const result: AlertType<ParamsType & AlertTypeParams, State, InstanceState, InstanceContext> = {
const result: AlertType<
ParamsType & AlertTypeParams,
State,
InstanceState,
InstanceContext,
'default' | 'other'
> = {
id: 'test.always-firing',
name: 'Test: Always Firing',
actionGroups: [
@ -149,7 +155,7 @@ function getCumulativeFiringAlertType() {
interface InstanceState extends AlertInstanceState {
instanceStateValue: boolean;
}
const result: AlertType<{}, State, InstanceState, {}> = {
const result: AlertType<{}, State, InstanceState, {}, 'default' | 'other'> = {
id: 'test.cumulative-firing',
name: 'Test: Cumulative Firing',
actionGroups: [
@ -189,7 +195,7 @@ function getNeverFiringAlertType() {
interface State extends AlertTypeState {
globalStateValue: boolean;
}
const result: AlertType<ParamsType, State, {}, {}> = {
const result: AlertType<ParamsType, State, {}, {}, 'default'> = {
id: 'test.never-firing',
name: 'Test: Never firing',
actionGroups: [
@ -229,7 +235,7 @@ function getFailingAlertType() {
reference: schema.string(),
});
type ParamsType = TypeOf<typeof paramsSchema>;
const result: AlertType<ParamsType, {}, {}, {}> = {
const result: AlertType<ParamsType, {}, {}, {}, 'default'> = {
id: 'test.failing',
name: 'Test: Failing',
validate: {
@ -271,7 +277,7 @@ function getAuthorizationAlertType(core: CoreSetup<FixtureStartDeps>) {
reference: schema.string(),
});
type ParamsType = TypeOf<typeof paramsSchema>;
const result: AlertType<ParamsType, {}, {}, {}> = {
const result: AlertType<ParamsType, {}, {}, {}, 'default'> = {
id: 'test.authorization',
name: 'Test: Authorization',
actionGroups: [
@ -358,7 +364,7 @@ function getValidationAlertType() {
param1: schema.string(),
});
type ParamsType = TypeOf<typeof paramsSchema>;
const result: AlertType<ParamsType, {}, {}, {}> = {
const result: AlertType<ParamsType, {}, {}, {}, 'default'> = {
id: 'test.validation',
name: 'Test: Validation',
actionGroups: [
@ -390,7 +396,7 @@ function getPatternFiringAlertType() {
interface State extends AlertTypeState {
patternIndex?: number;
}
const result: AlertType<ParamsType, State, {}, {}> = {
const result: AlertType<ParamsType, State, {}, {}, 'default'> = {
id: 'test.patternFiring',
name: 'Test: Firing on a Pattern',
actionGroups: [{ id: 'default', name: 'Default' }],
@ -454,7 +460,7 @@ export function defineAlertTypes(
core: CoreSetup<FixtureStartDeps>,
{ alerts }: Pick<FixtureSetupDeps, 'alerts'>
) {
const noopAlertType: AlertType = {
const noopAlertType: AlertType<{}, {}, {}, {}, 'default'> = {
id: 'test.noop',
name: 'Test: Noop',
actionGroups: [{ id: 'default', name: 'Default' }],
@ -463,7 +469,7 @@ export function defineAlertTypes(
minimumLicenseRequired: 'basic',
async executor() {},
};
const goldNoopAlertType: AlertType = {
const goldNoopAlertType: AlertType<{}, {}, {}, {}, 'default'> = {
id: 'test.gold.noop',
name: 'Test: Noop',
actionGroups: [{ id: 'default', name: 'Default' }],
@ -472,7 +478,7 @@ export function defineAlertTypes(
minimumLicenseRequired: 'gold',
async executor() {},
};
const onlyContextVariablesAlertType: AlertType = {
const onlyContextVariablesAlertType: AlertType<{}, {}, {}, {}, 'default'> = {
id: 'test.onlyContextVariables',
name: 'Test: Only Context Variables',
actionGroups: [{ id: 'default', name: 'Default' }],
@ -484,7 +490,7 @@ export function defineAlertTypes(
},
async executor() {},
};
const onlyStateVariablesAlertType: AlertType = {
const onlyStateVariablesAlertType: AlertType<{}, {}, {}, {}, 'default'> = {
id: 'test.onlyStateVariables',
name: 'Test: Only State Variables',
actionGroups: [{ id: 'default', name: 'Default' }],
@ -496,7 +502,7 @@ export function defineAlertTypes(
minimumLicenseRequired: 'basic',
async executor() {},
};
const throwAlertType: AlertType = {
const throwAlertType: AlertType<{}, {}, {}, {}, 'default'> = {
id: 'test.throw',
name: 'Test: Throw',
actionGroups: [
@ -512,7 +518,7 @@ export function defineAlertTypes(
throw new Error('this alert is intended to fail');
},
};
const longRunningAlertType: AlertType = {
const longRunningAlertType: AlertType<{}, {}, {}, {}, 'default'> = {
id: 'test.longRunning',
name: 'Test: Long Running',
actionGroups: [

View file

@ -6,13 +6,13 @@
import { CoreSetup } from 'src/core/server';
import { FixtureStartDeps, FixtureSetupDeps } from './plugin';
import { AlertType, AlertExecutorOptions } from '../../../../../../../plugins/alerts/server';
import { AlertType } from '../../../../../../../plugins/alerts/server';
export function defineAlertTypes(
core: CoreSetup<FixtureStartDeps>,
{ alerts }: Pick<FixtureSetupDeps, 'alerts'>
) {
const noopRestrictedAlertType: AlertType = {
const noopRestrictedAlertType: AlertType<{}, {}, {}, {}, 'default', 'restrictedRecovered'> = {
id: 'test.restricted-noop',
name: 'Test: Restricted Noop',
actionGroups: [{ id: 'default', name: 'Default' }],
@ -20,16 +20,16 @@ export function defineAlertTypes(
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: { id: 'restrictedRecovered', name: 'Restricted Recovery' },
async executor({ services, params, state }: AlertExecutorOptions) {},
async executor() {},
};
const noopUnrestrictedAlertType: AlertType = {
const noopUnrestrictedAlertType: AlertType<{}, {}, {}, {}, 'default'> = {
id: 'test.unrestricted-noop',
name: 'Test: Unrestricted Noop',
actionGroups: [{ id: 'default', name: 'Default' }],
producer: 'alertsRestrictedFixture',
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
async executor({ services, params, state }: AlertExecutorOptions) {},
async executor() {},
};
alerts.registerType(noopRestrictedAlertType);
alerts.registerType(noopUnrestrictedAlertType);

View file

@ -17,7 +17,7 @@ export interface AlertingExampleDeps {
features: FeaturesPluginSetup;
}
export const noopAlertType: AlertType = {
export const noopAlertType: AlertType<{}, {}, {}, {}, 'default'> = {
id: 'test.noop',
name: 'Test: Noop',
actionGroups: [{ id: 'default', name: 'Default' }],
@ -33,7 +33,9 @@ export const alwaysFiringAlertType: AlertType<
globalStateValue: boolean;
groupInSeriesIndex: number;
},
{ instanceStateValue: boolean; globalStateValue: boolean; groupInSeriesIndex: number }
{ instanceStateValue: boolean; globalStateValue: boolean; groupInSeriesIndex: number },
never,
'default' | 'other'
> = {
id: 'test.always-firing',
name: 'Always Firing',
@ -61,7 +63,7 @@ export const alwaysFiringAlertType: AlertType<
},
};
export const failingAlertType: AlertType = {
export const failingAlertType: AlertType<never, never, never, never, 'default' | 'other'> = {
id: 'test.failing',
name: 'Test: Failing',
actionGroups: [