From 1433c88cc42f469a0cab290e9d95b8b678112944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Mon, 17 Aug 2020 09:43:23 -0400 Subject: [PATCH] Make the alerts plugin support generics (#72716) (#75078) * Initial work * Expand generic support to alert instances * Convert index threshold to use generics * Make fixture alert types use generics * Make alert instance related types use unknown * Fix typecheck failures * Cleanup + add instance generic support to registry.get API * Shallow clone * Rename some TS variables * Fix failing api integration tests * Change code for easier review and keep more history * Fix Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- .../index_threshold/action_context.ts | 4 +- .../alert_types/index_threshold/alert_type.ts | 9 +- x-pack/plugins/alerts/common/alert.ts | 9 +- .../plugins/alerts/common/alert_instance.ts | 3 + .../server/alert_instance/alert_instance.ts | 32 ++- .../alerts/server/alert_type_registry.ts | 28 +- x-pack/plugins/alerts/server/index.ts | 5 +- .../task_runner/create_execution_handler.ts | 14 +- .../task_runner/transform_action_params.ts | 13 +- x-pack/plugins/alerts/server/types.ts | 51 ++-- .../monitoring/server/alerts/types.d.ts | 3 +- .../detection_engine/notifications/types.ts | 8 +- .../lib/detection_engine/signals/types.ts | 4 +- .../server/lib/alerts/uptime_alert_wrapper.ts | 4 +- .../plugins/alerts/server/alert_types.ts | 241 ++++++++++++------ 15 files changed, 288 insertions(+), 140 deletions(-) diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts index c3a132bc609d6..294a831c93459 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { Params } from './alert_type_params'; -import { AlertExecutorOptions } from '../../../../alerts/server'; +import { AlertExecutorOptions, AlertInstanceContext } from '../../../../alerts/server'; // alert type context provided to actions @@ -19,7 +19,7 @@ export interface ActionContext extends BaseActionContext { message: string; } -export interface BaseActionContext { +export interface BaseActionContext extends AlertInstanceContext { // the aggType used in the alert // the value of the aggField, if used, otherwise 'all documents' group: string; diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts index c0522c08a7b96..01c2c2feb5b9a 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { AlertType, AlertExecutorOptions } from '../../types'; import { Params, ParamsSchema } from './alert_type_params'; -import { BaseActionContext, addMessages } from './action_context'; +import { ActionContext, BaseActionContext, addMessages } from './action_context'; import { TimeSeriesQuery } from './lib/time_series_query'; import { Service } from '../../types'; import { BUILT_IN_ALERTS_FEATURE_ID } from '../../../common'; @@ -19,7 +19,7 @@ const ActionGroupId = 'threshold met'; const ComparatorFns = getComparatorFns(); export const ComparatorFnNames = new Set(ComparatorFns.keys()); -export function getAlertType(service: Service): AlertType { +export function getAlertType(service: Service): AlertType { const { logger } = service; const alertTypeName = i18n.translate('xpack.alertingBuiltins.indexThreshold.alertTypeTitle', { @@ -118,9 +118,8 @@ export function getAlertType(service: Service): AlertType { producer: BUILT_IN_ALERTS_FEATURE_ID, }; - async function executor(options: AlertExecutorOptions) { - const { alertId, name, services } = options; - const params: Params = options.params as Params; + async function executor(options: AlertExecutorOptions) { + const { alertId, name, services, params } = options; const compareFn = ComparatorFns.get(params.thresholdComparator); if (compareFn == null) { diff --git a/x-pack/plugins/alerts/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts index b410b6aa0187e..3ff7ed742e810 100644 --- a/x-pack/plugins/alerts/common/alert.ts +++ b/x-pack/plugins/alerts/common/alert.ts @@ -6,6 +6,11 @@ import { SavedObjectAttributes } from 'kibana/server'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AlertTypeState = Record; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AlertTypeParams = Record; + export interface IntervalSchedule extends SavedObjectAttributes { interval: string; } @@ -28,9 +33,7 @@ export interface Alert { consumer: string; schedule: IntervalSchedule; actions: AlertAction[]; - // This will have to remain `any` until we can extend Alert Executors with generics - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params: Record; + params: AlertTypeParams; scheduledTaskId?: string; createdBy: string | null; updatedBy: string | null; diff --git a/x-pack/plugins/alerts/common/alert_instance.ts b/x-pack/plugins/alerts/common/alert_instance.ts index a6852f06efd34..0253a5c6919d6 100644 --- a/x-pack/plugins/alerts/common/alert_instance.ts +++ b/x-pack/plugins/alerts/common/alert_instance.ts @@ -17,6 +17,9 @@ export type AlertInstanceMeta = t.TypeOf; const stateSchema = t.record(t.string, t.unknown); export type AlertInstanceState = t.TypeOf; +const contextSchema = t.record(t.string, t.unknown); +export type AlertInstanceContext = t.TypeOf; + export const rawAlertInstance = t.partial({ state: stateSchema, meta: metaSchema, diff --git a/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts b/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts index 4d106178f86fb..661fb75f81c00 100644 --- a/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts +++ b/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts @@ -8,24 +8,26 @@ import { AlertInstanceState, RawAlertInstance, rawAlertInstance, + AlertInstanceContext, } from '../../common'; -import { State, Context } from '../types'; import { parseDuration } from '../lib'; -interface ScheduledExecutionOptions { - actionGroup: string; - context: Context; - state: State; -} export type AlertInstances = Record; -export class AlertInstance { - private scheduledExecutionOptions?: ScheduledExecutionOptions; +export class AlertInstance< + State extends AlertInstanceState = AlertInstanceState, + Context extends AlertInstanceContext = AlertInstanceContext +> { + private scheduledExecutionOptions?: { + actionGroup: string; + context: Context; + state: State; + }; private meta: AlertInstanceMeta; - private state: AlertInstanceState; + private state: State; - constructor({ state = {}, meta = {} }: RawAlertInstance = {}) { - this.state = state; + constructor({ state, meta = {} }: RawAlertInstance = {}) { + this.state = (state || {}) as State; this.meta = meta; } @@ -62,11 +64,15 @@ export class AlertInstance { return this.state; } - scheduleActions(actionGroup: string, context: Context = {}) { + scheduleActions(actionGroup: string, context?: Context) { if (this.hasScheduledActions()) { throw new Error('Alert instance execution has already been scheduled, cannot schedule twice'); } - this.scheduledExecutionOptions = { actionGroup, context, state: this.state }; + this.scheduledExecutionOptions = { + actionGroup, + context: (context || {}) as Context, + state: this.state, + }; return this; } diff --git a/x-pack/plugins/alerts/server/alert_type_registry.ts b/x-pack/plugins/alerts/server/alert_type_registry.ts index 19d3bf13bd66d..7f34803b05a81 100644 --- a/x-pack/plugins/alerts/server/alert_type_registry.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.ts @@ -10,7 +10,13 @@ import { schema } from '@kbn/config-schema'; import typeDetect from 'type-detect'; import { RunContext, TaskManagerSetupContract } from '../../task_manager/server'; import { TaskRunnerFactory } from './task_runner'; -import { AlertType } from './types'; +import { + AlertType, + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, +} from './types'; interface ConstructorOptions { taskManager: TaskManagerSetupContract; @@ -59,7 +65,12 @@ export class AlertTypeRegistry { return this.alertTypes.has(id); } - public register(alertType: AlertType) { + public register< + Params extends AlertTypeParams = AlertTypeParams, + State extends AlertTypeState = AlertTypeState, + InstanceState extends AlertInstanceState = AlertInstanceState, + InstanceContext extends AlertInstanceContext = AlertInstanceContext + >(alertType: AlertType) { if (this.has(alertType.id)) { throw new Error( i18n.translate('xpack.alerts.alertTypeRegistry.register.duplicateAlertTypeError', { @@ -71,18 +82,23 @@ export class AlertTypeRegistry { ); } alertType.actionVariables = normalizedActionVariables(alertType.actionVariables); - this.alertTypes.set(alertIdSchema.validate(alertType.id), { ...alertType }); + this.alertTypes.set(alertIdSchema.validate(alertType.id), { ...alertType } as AlertType); this.taskManager.registerTaskDefinitions({ [`alerting:${alertType.id}`]: { title: alertType.name, type: `alerting:${alertType.id}`, createTaskRunner: (context: RunContext) => - this.taskRunnerFactory.create(alertType, context), + this.taskRunnerFactory.create({ ...alertType } as AlertType, context), }, }); } - public get(id: string): AlertType { + public get< + Params extends AlertTypeParams = AlertTypeParams, + State extends AlertTypeState = AlertTypeState, + InstanceState extends AlertInstanceState = AlertInstanceState, + InstanceContext extends AlertInstanceContext = AlertInstanceContext + >(id: string): AlertType { if (!this.has(id)) { throw Boom.badRequest( i18n.translate('xpack.alerts.alertTypeRegistry.get.missingAlertTypeError', { @@ -93,7 +109,7 @@ export class AlertTypeRegistry { }) ); } - return this.alertTypes.get(id)!; + return this.alertTypes.get(id)! as AlertType; } public list(): Set { diff --git a/x-pack/plugins/alerts/server/index.ts b/x-pack/plugins/alerts/server/index.ts index 515de771e7d6b..4c192a896c0c3 100644 --- a/x-pack/plugins/alerts/server/index.ts +++ b/x-pack/plugins/alerts/server/index.ts @@ -17,8 +17,11 @@ export { AlertExecutorOptions, AlertActionParams, AlertServices, - State, + AlertTypeState, + AlertTypeParams, PartialAlert, + AlertInstanceState, + AlertInstanceContext, } from './types'; export { PluginSetupContract, PluginStartContract } from './plugin'; export { FindResult } from './alerts_client'; diff --git a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts index c21d81779e5e0..bf074e2c60ee3 100644 --- a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts @@ -5,12 +5,18 @@ */ import { map } from 'lodash'; -import { AlertAction, State, Context, AlertType, AlertParams } from '../types'; import { Logger, KibanaRequest } from '../../../../../src/core/server'; import { transformActionParams } from './transform_action_params'; import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server'; import { IEventLogger, IEvent, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server'; import { EVENT_LOG_ACTIONS } from '../plugin'; +import { + AlertAction, + AlertInstanceState, + AlertInstanceContext, + AlertType, + AlertTypeParams, +} from '../types'; interface CreateExecutionHandlerOptions { alertId: string; @@ -24,14 +30,14 @@ interface CreateExecutionHandlerOptions { logger: Logger; eventLogger: IEventLogger; request: KibanaRequest; - alertParams: AlertParams; + alertParams: AlertTypeParams; } interface ExecutionHandlerOptions { actionGroup: string; alertInstanceId: string; - context: Context; - state: State; + context: AlertInstanceContext; + state: AlertInstanceState; } export function createExecutionHandler({ diff --git a/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts index 30f062eee3705..913fc51cb0f6e 100644 --- a/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts +++ b/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts @@ -6,7 +6,12 @@ import Mustache from 'mustache'; import { isString, cloneDeepWith } from 'lodash'; -import { AlertActionParams, State, Context, AlertParams } from '../types'; +import { + AlertActionParams, + AlertInstanceState, + AlertInstanceContext, + AlertTypeParams, +} from '../types'; interface TransformActionParamsOptions { alertId: string; @@ -15,9 +20,9 @@ interface TransformActionParamsOptions { tags?: string[]; alertInstanceId: string; actionParams: AlertActionParams; - state: State; - context: Context; - alertParams: AlertParams; + alertParams: AlertTypeParams; + state: AlertInstanceState; + context: AlertInstanceContext; } export function transformActionParams({ diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 71ab35f7f434b..20943ba28885c 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -7,7 +7,6 @@ import { AlertInstance } from './alert_instance'; import { AlertTypeRegistry as OrigAlertTypeRegistry } from './alert_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; -import { Alert, AlertActionParams, ActionGroup } from '../common'; import { AlertsClient } from './alerts_client'; export * from '../common'; import { @@ -17,13 +16,16 @@ import { SavedObjectAttributes, SavedObjectsClientContract, } from '../../../../src/core/server'; +import { + Alert, + AlertActionParams, + ActionGroup, + AlertTypeParams, + AlertTypeState, + AlertInstanceContext, + AlertInstanceState, +} from '../common'; -// This will have to remain `any` until we can extend Alert Executors with generics -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type State = Record; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Context = Record; -export type AlertParams = Record; export type WithoutQueryAndParams = Pick>; export type GetServicesFunction = (request: KibanaRequest) => Services; export type GetBasePathFunction = (spaceId?: string) => string; @@ -44,18 +46,24 @@ export interface Services { getLegacyScopedClusterClient(clusterClient: ILegacyClusterClient): ILegacyScopedClusterClient; } -export interface AlertServices extends Services { - alertInstanceFactory: (id: string) => AlertInstance; +export interface AlertServices< + InstanceState extends AlertInstanceState = AlertInstanceState, + InstanceContext extends AlertInstanceContext = AlertInstanceContext +> extends Services { + alertInstanceFactory: (id: string) => AlertInstance; } -export interface AlertExecutorOptions { +export interface AlertExecutorOptions< + Params extends AlertTypeParams = AlertTypeParams, + State extends AlertTypeState = AlertTypeState, + InstanceState extends AlertInstanceState = AlertInstanceState, + InstanceContext extends AlertInstanceContext = AlertInstanceContext +> { alertId: string; startedAt: Date; previousStartedAt: Date | null; - services: AlertServices; - // This will have to remain `any` until we can extend Alert Executors with generics - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params: Record; + services: AlertServices; + params: Params; state: State; spaceId: string; namespace?: string; @@ -70,15 +78,24 @@ export interface ActionVariable { description: string; } -export interface AlertType { +export interface AlertType< + Params extends AlertTypeParams = AlertTypeParams, + State extends AlertTypeState = AlertTypeState, + InstanceState extends AlertInstanceState = AlertInstanceState, + InstanceContext extends AlertInstanceContext = AlertInstanceContext +> { id: string; name: string; validate?: { - params?: { validate: (object: unknown) => AlertExecutorOptions['params'] }; + params?: { validate: (object: unknown) => Params }; }; actionGroups: ActionGroup[]; defaultActionGroupId: ActionGroup['id']; - executor: ({ services, params, state }: AlertExecutorOptions) => Promise; + executor: ({ + services, + params, + state, + }: AlertExecutorOptions) => Promise; producer: string; actionVariables?: { context?: ActionVariable[]; diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index 06988002a2034..b6c8427375841 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; +import { AlertInstanceState as BaseAlertInstanceState } from '../../../alerts/server'; export interface AlertEnableAction { id: string; config: { [key: string]: any }; } -export interface AlertInstanceState { +export interface AlertInstanceState extends BaseAlertInstanceState { alertStates: AlertState[]; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/types.ts index 1345bf2ac6339..cc9fb149a7e1b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/types.ts @@ -8,7 +8,7 @@ import { AlertsClient, PartialAlert, AlertType, - State, + AlertTypeState, AlertExecutorOptions, } from '../../../../../alerts/server'; import { Alert } from '../../../../../alerts/common'; @@ -102,5 +102,9 @@ export const isNotificationAlertExecutor = ( }; export type NotificationAlertTypeDefinition = Omit & { - executor: ({ services, params, state }: NotificationExecutorOptions) => Promise; + executor: ({ + services, + params, + state, + }: NotificationExecutorOptions) => Promise; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index dd0698b8d1124..aecdbe10695d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -6,7 +6,7 @@ import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; -import { AlertType, State, AlertExecutorOptions } from '../../../../../alerts/server'; +import { AlertType, AlertTypeState, AlertExecutorOptions } from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams } from '../types'; import { SearchResponse } from '../../types'; @@ -109,7 +109,7 @@ export const isAlertExecutor = (obj: SignalRuleAlertTypeDefinition): obj is Aler }; export type SignalRuleAlertTypeDefinition = Omit & { - executor: ({ services, params, state }: RuleExecutorOptions) => Promise; + executor: ({ services, params, state }: RuleExecutorOptions) => Promise; }; export interface Ancestor { diff --git a/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts b/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts index 5963b371f844f..b8a56405ca160 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts @@ -5,7 +5,7 @@ */ import { ILegacyScopedClusterClient } from 'kibana/server'; -import { AlertExecutorOptions, AlertType, State } from '../../../../alerts/server'; +import { AlertExecutorOptions, AlertType, AlertTypeState } from '../../../../alerts/server'; import { savedObjectsAdapter } from '../saved_objects'; import { DynamicSettings } from '../../../common/runtime_types'; @@ -14,7 +14,7 @@ export interface UptimeAlertType extends Omit Promise; + ) => Promise; } export const uptimeAlertWrapper = (uptimeAlert: UptimeAlertType) => ({ diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts index 40b2c33a702aa..23adf5f8cb9a2 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts @@ -5,23 +5,41 @@ */ import { CoreSetup } from 'src/core/server'; -import { schema } from '@kbn/config-schema'; +import { schema, TypeOf } from '@kbn/config-schema'; import { times } from 'lodash'; import { FixtureStartDeps, FixtureSetupDeps } from './plugin'; -import { AlertType, AlertExecutorOptions } from '../../../../../../../plugins/alerts/server'; +import { + AlertType, + AlertInstanceState, + AlertInstanceContext, +} from '../../../../../../../plugins/alerts/server'; -export function defineAlertTypes( - core: CoreSetup, - { alerts }: Pick -) { - const clusterClient = core.elasticsearch.legacy.client; - const alwaysFiringAlertType: AlertType = { +function getAlwaysFiringAlertType() { + const paramsSchema = schema.object({ + index: schema.string(), + reference: schema.string(), + groupsToScheduleActionsInSeries: schema.maybe(schema.arrayOf(schema.nullable(schema.string()))), + }); + type ParamsType = TypeOf; + interface State { + groupInSeriesIndex?: number; + } + interface InstanceState extends AlertInstanceState { + instanceStateValue: boolean; + } + interface InstanceContext extends AlertInstanceContext { + instanceContextValue: boolean; + } + const result: AlertType = { id: 'test.always-firing', name: 'Test: Always Firing', actionGroups: [ { id: 'default', name: 'Default' }, { id: 'other', name: 'Other' }, ], + validate: { + params: paramsSchema, + }, producer: 'alertsFixture', defaultActionGroupId: 'default', actionVariables: { @@ -29,7 +47,7 @@ export function defineAlertTypes( params: [{ name: 'instanceParamsValue', description: 'the instance params value' }], context: [{ name: 'instanceContextValue', description: 'the instance context value' }], }, - async executor(alertExecutorOptions: AlertExecutorOptions) { + async executor(alertExecutorOptions) { const { services, params, @@ -42,7 +60,7 @@ export function defineAlertTypes( createdBy, updatedBy, } = alertExecutorOptions; - let group = 'default'; + let group: string | null = 'default'; const alertInfo = { alertId, spaceId, namespace, name, tags, createdBy, updatedBy }; if (params.groupsToScheduleActionsInSeries) { @@ -75,8 +93,17 @@ export function defineAlertTypes( }; }, }; - // Alert types - const cumulativeFiringAlertType: AlertType = { + return result; +} + +function getCumulativeFiringAlertType() { + interface State { + runCount?: number; + } + interface InstanceState extends AlertInstanceState { + instanceStateValue: boolean; + } + const result: AlertType<{}, State, InstanceState, {}> = { id: 'test.cumulative-firing', name: 'Test: Cumulative Firing', actionGroups: [ @@ -85,7 +112,7 @@ export function defineAlertTypes( ], producer: 'alertsFixture', defaultActionGroupId: 'default', - async executor(alertExecutorOptions: AlertExecutorOptions) { + async executor(alertExecutorOptions) { const { services, state } = alertExecutorOptions; const group = 'default'; @@ -103,7 +130,19 @@ export function defineAlertTypes( }; }, }; - const neverFiringAlertType: AlertType = { + return result; +} + +function getNeverFiringAlertType() { + const paramsSchema = schema.object({ + index: schema.string(), + reference: schema.string(), + }); + type ParamsType = TypeOf; + interface State { + globalStateValue: boolean; + } + const result: AlertType = { id: 'test.never-firing', name: 'Test: Never firing', actionGroups: [ @@ -112,9 +151,12 @@ export function defineAlertTypes( name: 'Default', }, ], + validate: { + params: paramsSchema, + }, producer: 'alertsFixture', defaultActionGroupId: 'default', - async executor({ services, params, state }: AlertExecutorOptions) { + async executor({ services, params, state }) { await services.callCluster('index', { index: params.index, refresh: 'wait_for', @@ -130,9 +172,21 @@ export function defineAlertTypes( }; }, }; - const failingAlertType: AlertType = { + return result; +} + +function getFailingAlertType() { + const paramsSchema = schema.object({ + index: schema.string(), + reference: schema.string(), + }); + type ParamsType = TypeOf; + const result: AlertType = { id: 'test.failing', name: 'Test: Failing', + validate: { + params: paramsSchema, + }, actionGroups: [ { id: 'default', @@ -141,7 +195,7 @@ export function defineAlertTypes( ], producer: 'alertsFixture', defaultActionGroupId: 'default', - async executor({ services, params, state }: AlertExecutorOptions) { + async executor({ services, params, state }) { await services.callCluster('index', { index: params.index, refresh: 'wait_for', @@ -155,7 +209,20 @@ export function defineAlertTypes( throw new Error('Failed to execute alert type'); }, }; - const authorizationAlertType: AlertType = { + return result; +} + +function getAuthorizationAlertType(core: CoreSetup) { + const clusterClient = core.elasticsearch.legacy.client; + const paramsSchema = schema.object({ + callClusterAuthorizationIndex: schema.string(), + savedObjectsClientType: schema.string(), + savedObjectsClientId: schema.string(), + index: schema.string(), + reference: schema.string(), + }); + type ParamsType = TypeOf; + const result: AlertType = { id: 'test.authorization', name: 'Test: Authorization', actionGroups: [ @@ -167,15 +234,9 @@ export function defineAlertTypes( defaultActionGroupId: 'default', producer: 'alertsFixture', validate: { - params: schema.object({ - callClusterAuthorizationIndex: schema.string(), - savedObjectsClientType: schema.string(), - savedObjectsClientId: schema.string(), - index: schema.string(), - reference: schema.string(), - }), + params: paramsSchema, }, - async executor({ services, params, state }: AlertExecutorOptions) { + async executor({ services, params, state }) { // Call cluster let callClusterSuccess = false; let callClusterError; @@ -239,7 +300,15 @@ export function defineAlertTypes( }); }, }; - const validationAlertType: AlertType = { + return result; +} + +function getValidationAlertType() { + const paramsSchema = schema.object({ + param1: schema.string(), + }); + type ParamsType = TypeOf; + const result: AlertType = { id: 'test.validation', name: 'Test: Validation', actionGroups: [ @@ -251,19 +320,71 @@ export function defineAlertTypes( producer: 'alertsFixture', defaultActionGroupId: 'default', validate: { - params: schema.object({ - param1: schema.string(), - }), + params: paramsSchema, + }, + async executor() {}, + }; + return result; +} + +function getPatternFiringAlertType() { + const paramsSchema = schema.object({ + pattern: schema.recordOf(schema.string(), schema.arrayOf(schema.boolean())), + }); + type ParamsType = TypeOf; + interface State { + patternIndex?: number; + } + const result: AlertType = { + id: 'test.patternFiring', + name: 'Test: Firing on a Pattern', + actionGroups: [{ id: 'default', name: 'Default' }], + producer: 'alertsFixture', + defaultActionGroupId: 'default', + async executor(alertExecutorOptions) { + const { services, state, params } = alertExecutorOptions; + const pattern = params.pattern; + if (typeof pattern !== 'object') throw new Error('pattern is not an object'); + let maxPatternLength = 0; + for (const [instanceId, instancePattern] of Object.entries(pattern)) { + if (!Array.isArray(instancePattern)) { + throw new Error(`pattern for instance ${instanceId} is not an array`); + } + maxPatternLength = Math.max(maxPatternLength, instancePattern.length); + } + + // get the pattern index, return if past it + const patternIndex = state.patternIndex ?? 0; + if (patternIndex >= maxPatternLength) { + return { patternIndex }; + } + + // fire if pattern says to + for (const [instanceId, instancePattern] of Object.entries(pattern)) { + if (instancePattern[patternIndex]) { + services.alertInstanceFactory(instanceId).scheduleActions('default'); + } + } + + return { + patternIndex: patternIndex + 1, + }; }, - async executor({ services, params, state }: AlertExecutorOptions) {}, }; + return result; +} + +export function defineAlertTypes( + core: CoreSetup, + { alerts }: Pick +) { const noopAlertType: AlertType = { id: 'test.noop', name: 'Test: Noop', actionGroups: [{ id: 'default', name: 'Default' }], producer: 'alertsFixture', defaultActionGroupId: 'default', - async executor({ services, params, state }: AlertExecutorOptions) {}, + async executor() {}, }; const onlyContextVariablesAlertType: AlertType = { id: 'test.onlyContextVariables', @@ -274,7 +395,7 @@ export function defineAlertTypes( actionVariables: { context: [{ name: 'aContextVariable', description: 'this is a context variable' }], }, - async executor(opts: AlertExecutorOptions) {}, + async executor() {}, }; const onlyStateVariablesAlertType: AlertType = { id: 'test.onlyStateVariables', @@ -285,7 +406,7 @@ export function defineAlertTypes( actionVariables: { state: [{ name: 'aStateVariable', description: 'this is a state variable' }], }, - async executor(opts: AlertExecutorOptions) {}, + async executor() {}, }; const throwAlertType: AlertType = { id: 'test.throw', @@ -298,56 +419,20 @@ export function defineAlertTypes( ], producer: 'alertsFixture', defaultActionGroupId: 'default', - async executor({ services, params, state }: AlertExecutorOptions) { + async executor() { throw new Error('this alert is intended to fail'); }, }; - const patternFiringAlertType: AlertType = { - id: 'test.patternFiring', - name: 'Test: Firing on a Pattern', - actionGroups: [{ id: 'default', name: 'Default' }], - producer: 'alertsFixture', - defaultActionGroupId: 'default', - async executor(alertExecutorOptions: AlertExecutorOptions) { - const { services, state, params } = alertExecutorOptions; - const pattern = params.pattern as Record; - if (typeof pattern !== 'object') throw new Error('pattern is not an object'); - let maxPatternLength = 0; - for (const [instanceId, instancePattern] of Object.entries(pattern)) { - if (!Array.isArray(instancePattern)) { - throw new Error(`pattern for instance ${instanceId} is not an array`); - } - maxPatternLength = Math.max(maxPatternLength, instancePattern.length); - } - - // get the pattern index, return if past it - const patternIndex = state.patternIndex ?? 0; - if (patternIndex >= maxPatternLength) { - return { patternIndex }; - } - - // fire if pattern says to - for (const [instanceId, instancePattern] of Object.entries(pattern)) { - if (instancePattern[patternIndex]) { - services.alertInstanceFactory(instanceId).scheduleActions('default'); - } - } - - return { - patternIndex: patternIndex + 1, - }; - }, - }; - alerts.registerType(alwaysFiringAlertType); - alerts.registerType(cumulativeFiringAlertType); - alerts.registerType(neverFiringAlertType); - alerts.registerType(failingAlertType); - alerts.registerType(validationAlertType); - alerts.registerType(authorizationAlertType); + alerts.registerType(getAlwaysFiringAlertType()); + alerts.registerType(getCumulativeFiringAlertType()); + alerts.registerType(getNeverFiringAlertType()); + alerts.registerType(getFailingAlertType()); + alerts.registerType(getValidationAlertType()); + alerts.registerType(getAuthorizationAlertType(core)); alerts.registerType(noopAlertType); alerts.registerType(onlyContextVariablesAlertType); alerts.registerType(onlyStateVariablesAlertType); - alerts.registerType(patternFiringAlertType); + alerts.registerType(getPatternFiringAlertType()); alerts.registerType(throwAlertType); }