diff --git a/x-pack/examples/alerting_example/common/constants.ts b/x-pack/examples/alerting_example/common/constants.ts index 40cc298db795a..8e4ea4faf014c 100644 --- a/x-pack/examples/alerting_example/common/constants.ts +++ b/x-pack/examples/alerting_example/common/constants.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AlertTypeParams } from '../../../plugins/alerts/common'; + export const ALERTING_EXAMPLE_APP_ID = 'AlertingExample'; // always firing export const DEFAULT_INSTANCES_TO_GENERATE = 5; -export interface AlwaysFiringParams { +export interface AlwaysFiringParams extends AlertTypeParams { instances?: number; thresholds?: { small?: number; diff --git a/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx b/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx index e4687c75fa0b7..eb682a86f5ff6 100644 --- a/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx +++ b/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx @@ -23,7 +23,7 @@ import { withRouter, RouteComponentProps } from 'react-router-dom'; import { CoreStart } from 'kibana/public'; import { isEmpty } from 'lodash'; import { Alert, AlertTaskState, BASE_ALERT_API_PATH } from '../../../../plugins/alerts/common'; -import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; +import { ALERTING_EXAMPLE_APP_ID, AlwaysFiringParams } from '../../common/constants'; type Props = RouteComponentProps & { http: CoreStart['http']; @@ -34,7 +34,7 @@ function hasCraft(state: any): state is { craft: string } { return state && state.craft; } export const ViewPeopleInSpaceAlertPage = withRouter(({ http, id }: Props) => { - const [alert, setAlert] = useState(null); + const [alert, setAlert] = useState | null>(null); const [alertState, setAlertState] = useState(null); useEffect(() => { diff --git a/x-pack/examples/alerting_example/server/alert_types/astros.ts b/x-pack/examples/alerting_example/server/alert_types/astros.ts index 27a8bfc7a53a3..22c2f25c410cd 100644 --- a/x-pack/examples/alerting_example/server/alert_types/astros.ts +++ b/x-pack/examples/alerting_example/server/alert_types/astros.ts @@ -38,7 +38,11 @@ function getCraftFilter(craft: string) { craft === Craft.OuterSpace ? true : craft === person.craft; } -export const alertType: AlertType = { +export const alertType: AlertType< + { outerSpaceCapacity: number; craft: string; op: string }, + { peopleInSpace: number }, + { craft: string } +> = { id: 'example.people-in-space', name: 'People In Space Right Now', actionGroups: [{ id: 'default', name: 'default' }], diff --git a/x-pack/plugins/alerts/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts index d74f66898eff6..ed3fbcf2ddc9b 100644 --- a/x-pack/plugins/alerts/common/alert.ts +++ b/x-pack/plugins/alerts/common/alert.ts @@ -7,10 +7,8 @@ import { SavedObjectAttribute, SavedObjectAttributes } from 'kibana/server'; import { AlertNotifyWhenType } from './alert_notify_when_type'; -// 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 type AlertTypeState = Record; +export type AlertTypeParams = Record; export interface IntervalSchedule extends SavedObjectAttributes { interval: string; @@ -52,7 +50,7 @@ export interface AlertAggregations { alertExecutionStatus: { [status: string]: number }; } -export interface Alert { +export interface Alert { id: string; enabled: boolean; name: string; @@ -61,7 +59,7 @@ export interface Alert { consumer: string; schedule: IntervalSchedule; actions: AlertAction[]; - params: AlertTypeParams; + params: Params; scheduledTaskId?: string; createdBy: string | null; updatedBy: string | null; @@ -76,7 +74,7 @@ export interface Alert { executionStatus: AlertExecutionStatus; } -export type SanitizedAlert = Omit; +export type SanitizedAlert = Omit, 'apiKey'>; export enum HealthStatus { OK = 'ok', diff --git a/x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.ts b/x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.ts index 0b29262ddcc07..47f013a5d0e55 100644 --- a/x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.ts +++ b/x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.ts @@ -4,12 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AlertInstanceContext, AlertInstanceState } from '../types'; import { AlertInstance } from './alert_instance'; -export function createAlertInstanceFactory(alertInstances: Record) { - return (id: string): AlertInstance => { +export function createAlertInstanceFactory< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +>(alertInstances: Record>) { + return (id: string): AlertInstance => { if (!alertInstances[id]) { - alertInstances[id] = new AlertInstance(); + alertInstances[id] = new AlertInstance(); } return alertInstances[id]; diff --git a/x-pack/plugins/alerts/server/alert_type_registry.ts b/x-pack/plugins/alerts/server/alert_type_registry.ts index d436d1987c027..5e4188c1f3bc1 100644 --- a/x-pack/plugins/alerts/server/alert_type_registry.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.ts @@ -32,7 +32,7 @@ export interface ConstructorOptions { export interface RegistryAlertType extends Pick< - NormalizedAlertType, + UntypedNormalizedAlertType, | 'name' | 'actionGroups' | 'recoveryActionGroup' @@ -66,16 +66,23 @@ const alertIdSchema = schema.string({ }); export type NormalizedAlertType< - Params extends AlertTypeParams = AlertTypeParams, - State extends AlertTypeState = AlertTypeState, - InstanceState extends AlertInstanceState = AlertInstanceState, - InstanceContext extends AlertInstanceContext = AlertInstanceContext + Params extends AlertTypeParams, + State extends AlertTypeState, + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext > = Omit, 'recoveryActionGroup'> & Pick>, 'recoveryActionGroup'>; +export type UntypedNormalizedAlertType = NormalizedAlertType< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext +>; + export class AlertTypeRegistry { private readonly taskManager: TaskManagerSetupContract; - private readonly alertTypes: Map = new Map(); + private readonly alertTypes: Map = new Map(); private readonly taskRunnerFactory: TaskRunnerFactory; private readonly licenseState: ILicenseState; private readonly licensing: LicensingPluginSetup; @@ -96,10 +103,10 @@ export class AlertTypeRegistry { } public register< - Params extends AlertTypeParams = AlertTypeParams, - State extends AlertTypeState = AlertTypeState, - InstanceState extends AlertInstanceState = AlertInstanceState, - InstanceContext extends AlertInstanceContext = AlertInstanceContext + Params extends AlertTypeParams, + State extends AlertTypeState, + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext >(alertType: AlertType) { if (this.has(alertType.id)) { throw new Error( @@ -113,14 +120,22 @@ export class AlertTypeRegistry { } alertType.actionVariables = normalizedActionVariables(alertType.actionVariables); - const normalizedAlertType = augmentActionGroupsWithReserved(alertType as AlertType); + const normalizedAlertType = augmentActionGroupsWithReserved< + Params, + State, + InstanceState, + InstanceContext + >(alertType); - this.alertTypes.set(alertIdSchema.validate(alertType.id), normalizedAlertType); + this.alertTypes.set( + alertIdSchema.validate(alertType.id), + normalizedAlertType as UntypedNormalizedAlertType + ); this.taskManager.registerTaskDefinitions({ [`alerting:${alertType.id}`]: { title: alertType.name, createTaskRunner: (context: RunContext) => - this.taskRunnerFactory.create(normalizedAlertType, context), + this.taskRunnerFactory.create(normalizedAlertType as UntypedNormalizedAlertType, context), }, }); // No need to notify usage on basic alert types @@ -170,7 +185,7 @@ export class AlertTypeRegistry { producer, minimumLicenseRequired, }, - ]: [string, NormalizedAlertType]) => ({ + ]: [string, UntypedNormalizedAlertType]) => ({ id, name, actionGroups, diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index f21cd2b02943a..e21fee4ce3d61 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -23,13 +23,13 @@ import { RawAlert, AlertTypeRegistry, AlertAction, - AlertType, IntervalSchedule, SanitizedAlert, AlertTaskState, AlertInstanceSummary, AlertExecutionStatusValues, AlertNotifyWhenType, + AlertTypeParams, } from '../types'; import { validateAlertTypeParams, @@ -44,7 +44,7 @@ import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/se import { TaskManagerStartContract } from '../../../task_manager/server'; import { taskInstanceToAlertTaskInstance } from '../task_runner/alert_task_instance'; import { deleteTaskIfItExists } from '../lib/delete_task_if_it_exists'; -import { RegistryAlertType } from '../alert_type_registry'; +import { RegistryAlertType, UntypedNormalizedAlertType } from '../alert_type_registry'; import { AlertsAuthorization, WriteOperations, ReadOperations } from '../authorization'; import { IEventLogClient } from '../../../../plugins/event_log/server'; import { parseIsoOrRelativeDate } from '../lib/iso_or_relative_date'; @@ -127,16 +127,16 @@ interface AggregateResult { alertExecutionStatus: { [status: string]: number }; } -export interface FindResult { +export interface FindResult { page: number; perPage: number; total: number; - data: SanitizedAlert[]; + data: Array>; } -export interface CreateOptions { +export interface CreateOptions { data: Omit< - Alert, + Alert, | 'id' | 'createdBy' | 'updatedBy' @@ -154,14 +154,14 @@ export interface CreateOptions { }; } -interface UpdateOptions { +interface UpdateOptions { id: string; data: { name: string; tags: string[]; schedule: IntervalSchedule; actions: NormalizedAlertAction[]; - params: Record; + params: Params; throttle: string | null; notifyWhen: AlertNotifyWhenType | null; }; @@ -223,7 +223,10 @@ export class AlertsClient { this.auditLogger = auditLogger; } - public async create({ data, options }: CreateOptions): Promise { + public async create({ + data, + options, + }: CreateOptions): Promise> { const id = SavedObjectsUtils.generateId(); try { @@ -248,7 +251,10 @@ export class AlertsClient { // Throws an error if alert type isn't registered const alertType = this.alertTypeRegistry.get(data.alertTypeId); - const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params); + const validatedAlertTypeParams = validateAlertTypeParams( + data.params, + alertType.validate?.params + ); const username = await this.getUserName(); const createdAPIKey = data.enabled @@ -334,10 +340,14 @@ export class AlertsClient { }); createdAlert.attributes.scheduledTaskId = scheduledTask.id; } - return this.getAlertFromRaw(createdAlert.id, createdAlert.attributes, references); + return this.getAlertFromRaw(createdAlert.id, createdAlert.attributes, references); } - public async get({ id }: { id: string }): Promise { + public async get({ + id, + }: { + id: string; + }): Promise> { const result = await this.unsecuredSavedObjectsClient.get('alert', id); try { await this.authorization.ensureAuthorized( @@ -361,7 +371,7 @@ export class AlertsClient { savedObject: { type: 'alert', id }, }) ); - return this.getAlertFromRaw(result.id, result.attributes, result.references); + return this.getAlertFromRaw(result.id, result.attributes, result.references); } public async getAlertState({ id }: { id: string }): Promise { @@ -426,9 +436,9 @@ export class AlertsClient { }); } - public async find({ + public async find({ options: { fields, ...options } = {}, - }: { options?: FindOptions } = {}): Promise { + }: { options?: FindOptions } = {}): Promise> { let authorizationTuple; try { authorizationTuple = await this.authorization.getFindAuthorizationFilter(); @@ -475,7 +485,7 @@ export class AlertsClient { ); throw error; } - return this.getAlertFromRaw( + return this.getAlertFromRaw( id, fields ? (pick(attributes, fields) as RawAlert) : attributes, references @@ -605,15 +615,21 @@ export class AlertsClient { return removeResult; } - public async update({ id, data }: UpdateOptions): Promise { + public async update({ + id, + data, + }: UpdateOptions): Promise> { return await retryIfConflicts( this.logger, `alertsClient.update('${id}')`, - async () => await this.updateWithOCC({ id, data }) + async () => await this.updateWithOCC({ id, data }) ); } - private async updateWithOCC({ id, data }: UpdateOptions): Promise { + private async updateWithOCC({ + id, + data, + }: UpdateOptions): Promise> { let alertSavedObject: SavedObject; try { @@ -658,7 +674,7 @@ export class AlertsClient { this.alertTypeRegistry.ensureAlertTypeEnabled(alertSavedObject.attributes.alertTypeId); - const updateResult = await this.updateAlert({ id, data }, alertSavedObject); + const updateResult = await this.updateAlert({ id, data }, alertSavedObject); await Promise.all([ alertSavedObject.attributes.apiKey @@ -692,14 +708,17 @@ export class AlertsClient { return updateResult; } - private async updateAlert( - { id, data }: UpdateOptions, + private async updateAlert( + { id, data }: UpdateOptions, { attributes, version }: SavedObject - ): Promise { + ): Promise> { const alertType = this.alertTypeRegistry.get(attributes.alertTypeId); // Validate - const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params); + const validatedAlertTypeParams = validateAlertTypeParams( + data.params, + alertType.validate?.params + ); this.validateActions(alertType, data.actions); const { actions, references } = await this.denormalizeActions(data.actions); @@ -1343,7 +1362,7 @@ export class AlertsClient { }) as Alert['actions']; } - private getAlertFromRaw( + private getAlertFromRaw( id: string, rawAlert: RawAlert, references: SavedObjectReference[] | undefined @@ -1351,14 +1370,14 @@ export class AlertsClient { // In order to support the partial update API of Saved Objects we have to support // partial updates of an Alert, but when we receive an actual RawAlert, it is safe // to cast the result to an Alert - return this.getPartialAlertFromRaw(id, rawAlert, references) as Alert; + return this.getPartialAlertFromRaw(id, rawAlert, references) as Alert; } - private getPartialAlertFromRaw( + private getPartialAlertFromRaw( id: string, { createdAt, updatedAt, meta, notifyWhen, scheduledTaskId, ...rawAlert }: Partial, references: SavedObjectReference[] | undefined - ): PartialAlert { + ): PartialAlert { // Not the prettiest code here, but if we want to use most of the // alert fields from the rawAlert using `...rawAlert` kind of access, we // need to specifically delete the executionStatus as it's a different type @@ -1386,7 +1405,10 @@ export class AlertsClient { }; } - private validateActions(alertType: AlertType, actions: NormalizedAlertAction[]): void { + private validateActions( + alertType: UntypedNormalizedAlertType, + actions: NormalizedAlertAction[] + ): void { const { actionGroups: alertTypeActionGroups } = alertType; const usedAlertActionGroups = actions.map((action) => action.group); const availableAlertTypeActionGroups = new Set(map(alertTypeActionGroups, 'id')); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts index 5f830a6c5bc51..0424a1295c9b9 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts @@ -59,7 +59,11 @@ beforeEach(() => { setGlobalDate(); -function getMockData(overwrites: Record = {}): CreateOptions['data'] { +function getMockData( + overwrites: Record = {} +): CreateOptions<{ + bar: boolean; +}>['data'] { return { enabled: true, name: 'abc', @@ -93,7 +97,11 @@ describe('create()', () => { }); describe('authorization', () => { - function tryToExecuteOperation(options: CreateOptions): Promise { + function tryToExecuteOperation( + options: CreateOptions<{ + bar: boolean; + }> + ): Promise { unsecuredSavedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ { diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts index d6357494546b0..0f91e5d0c24a9 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts @@ -635,11 +635,11 @@ export class EventsFactory { } } -function createAlert(overrides: Partial): SanitizedAlert { +function createAlert(overrides: Partial): SanitizedAlert<{ bar: boolean }> { return { ...BaseAlert, ...overrides }; } -const BaseAlert: SanitizedAlert = { +const BaseAlert: SanitizedAlert<{ bar: boolean }> = { id: 'alert-123', alertTypeId: '123', schedule: { interval: '10s' }, diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts index f540f9a9b884c..a020eecd448a4 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts @@ -9,7 +9,7 @@ import { IEvent } from '../../../event_log/server'; import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin'; export interface AlertInstanceSummaryFromEventLogParams { - alert: SanitizedAlert; + alert: SanitizedAlert<{ bar: boolean }>; events: IEvent[]; dateStart: string; dateEnd: string; diff --git a/x-pack/plugins/alerts/server/lib/validate_alert_type_params.test.ts b/x-pack/plugins/alerts/server/lib/validate_alert_type_params.test.ts index 2814eaef3e02a..634b6885aa59b 100644 --- a/x-pack/plugins/alerts/server/lib/validate_alert_type_params.test.ts +++ b/x-pack/plugins/alerts/server/lib/validate_alert_type_params.test.ts @@ -8,51 +8,19 @@ import { schema } from '@kbn/config-schema'; import { validateAlertTypeParams } from './validate_alert_type_params'; test('should return passed in params when validation not defined', () => { - const result = validateAlertTypeParams( - { - id: 'my-alert-type', - name: 'My description', - actionGroups: [ - { - id: 'default', - name: 'Default', - }, - ], - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - async executor() {}, - producer: 'alerts', - }, - { - foo: true, - } - ); + const result = validateAlertTypeParams({ + foo: true, + }); expect(result).toEqual({ foo: true }); }); test('should validate and apply defaults when params is valid', () => { const result = validateAlertTypeParams( - { - id: 'my-alert-type', - name: 'My description', - actionGroups: [ - { - id: 'default', - name: 'Default', - }, - ], - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - validate: { - params: schema.object({ - param1: schema.string(), - param2: schema.string({ defaultValue: 'default-value' }), - }), - }, - async executor() {}, - producer: 'alerts', - }, - { param1: 'value' } + { param1: 'value' }, + schema.object({ + param1: schema.string(), + param2: schema.string({ defaultValue: 'default-value' }), + }) ); expect(result).toEqual({ param1: 'value', @@ -63,26 +31,10 @@ test('should validate and apply defaults when params is valid', () => { test('should validate and throw error when params is invalid', () => { expect(() => validateAlertTypeParams( - { - id: 'my-alert-type', - name: 'My description', - actionGroups: [ - { - id: 'default', - name: 'Default', - }, - ], - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - validate: { - params: schema.object({ - param1: schema.string(), - }), - }, - async executor() {}, - producer: 'alerts', - }, - {} + {}, + schema.object({ + param1: schema.string(), + }) ) ).toThrowErrorMatchingInlineSnapshot( `"params invalid: [param1]: expected value of type [string] but got [undefined]"` diff --git a/x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts b/x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts index a443143d8cbde..2f510f90a2367 100644 --- a/x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts +++ b/x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts @@ -5,15 +5,14 @@ */ import Boom from '@hapi/boom'; -import { AlertType, AlertExecutorOptions } from '../types'; +import { AlertTypeParams, AlertTypeParamsValidator } from '../types'; -export function validateAlertTypeParams( - alertType: AlertType, - params: Record -): AlertExecutorOptions['params'] { - const validator = alertType.validate && alertType.validate.params; +export function validateAlertTypeParams( + params: Record, + validator?: AlertTypeParamsValidator +): Params { if (!validator) { - return params as AlertExecutorOptions['params']; + return params as Params; } try { diff --git a/x-pack/plugins/alerts/server/mocks.ts b/x-pack/plugins/alerts/server/mocks.ts index cfae4c650bd42..0f042b7a81d6c 100644 --- a/x-pack/plugins/alerts/server/mocks.ts +++ b/x-pack/plugins/alerts/server/mocks.ts @@ -11,6 +11,7 @@ import { elasticsearchServiceMock, savedObjectsClientMock, } from '../../../../src/core/server/mocks'; +import { AlertInstanceContext, AlertInstanceState } from './types'; export { alertsClientMock }; @@ -30,8 +31,14 @@ const createStartMock = () => { return mock; }; -export type AlertInstanceMock = jest.Mocked; -const createAlertInstanceFactoryMock = () => { +export type AlertInstanceMock< + State extends AlertInstanceState = AlertInstanceState, + Context extends AlertInstanceContext = AlertInstanceContext +> = jest.Mocked>; +const createAlertInstanceFactoryMock = < + InstanceState extends AlertInstanceState = AlertInstanceState, + InstanceContext extends AlertInstanceContext = AlertInstanceContext +>() => { const mock = { hasScheduledActions: jest.fn(), isThrottled: jest.fn(), @@ -50,14 +57,17 @@ const createAlertInstanceFactoryMock = () => { mock.unscheduleActions.mockReturnValue(mock); mock.scheduleActions.mockReturnValue(mock); - return (mock as unknown) as AlertInstanceMock; + return (mock as unknown) as AlertInstanceMock; }; -const createAlertServicesMock = () => { - const alertInstanceFactoryMock = createAlertInstanceFactoryMock(); +const createAlertServicesMock = < + InstanceState extends AlertInstanceState = AlertInstanceState, + InstanceContext extends AlertInstanceContext = AlertInstanceContext +>() => { + const alertInstanceFactoryMock = createAlertInstanceFactoryMock(); return { alertInstanceFactory: jest - .fn, [string]>() + .fn>, [string]>() .mockReturnValue(alertInstanceFactoryMock), callCluster: elasticsearchServiceMock.createLegacyScopedClusterClient().callAsCurrentUser, getLegacyScopedClusterClient: jest.fn(), diff --git a/x-pack/plugins/alerts/server/routes/create.test.ts b/x-pack/plugins/alerts/server/routes/create.test.ts index 5597b315158cd..fc531821f25b6 100644 --- a/x-pack/plugins/alerts/server/routes/create.test.ts +++ b/x-pack/plugins/alerts/server/routes/create.test.ts @@ -49,7 +49,7 @@ describe('createAlertRoute', () => { ], }; - const createResult: Alert = { + const createResult: Alert<{ bar: boolean }> = { ...mockedAlert, enabled: true, muteAll: false, diff --git a/x-pack/plugins/alerts/server/routes/create.ts b/x-pack/plugins/alerts/server/routes/create.ts index a34a3118985fa..a79a9d40b236f 100644 --- a/x-pack/plugins/alerts/server/routes/create.ts +++ b/x-pack/plugins/alerts/server/routes/create.ts @@ -16,7 +16,13 @@ import { ILicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { validateDurationSchema } from '../lib'; import { handleDisabledApiKeysError } from './lib/error_handler'; -import { Alert, AlertNotifyWhenType, BASE_ALERT_API_PATH, validateNotifyWhenType } from '../types'; +import { + Alert, + AlertNotifyWhenType, + AlertTypeParams, + BASE_ALERT_API_PATH, + validateNotifyWhenType, +} from '../types'; import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; export const bodySchema = schema.object({ @@ -65,7 +71,9 @@ export const createAlertRoute = (router: IRouter, licenseState: ILicenseState) = const alert = req.body; const notifyWhen = alert?.notifyWhen ? (alert.notifyWhen as AlertNotifyWhenType) : null; try { - const alertRes: Alert = await alertsClient.create({ data: { ...alert, notifyWhen } }); + const alertRes: Alert = await alertsClient.create({ + data: { ...alert, notifyWhen }, + }); return res.ok({ body: alertRes, }); diff --git a/x-pack/plugins/alerts/server/routes/get.test.ts b/x-pack/plugins/alerts/server/routes/get.test.ts index 21e52ece82d2d..747f9b11e2b47 100644 --- a/x-pack/plugins/alerts/server/routes/get.test.ts +++ b/x-pack/plugins/alerts/server/routes/get.test.ts @@ -22,7 +22,9 @@ beforeEach(() => { }); describe('getAlertRoute', () => { - const mockedAlert: Alert = { + const mockedAlert: Alert<{ + bar: true; + }> = { id: '1', alertTypeId: '1', schedule: { interval: '10s' }, diff --git a/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts index 09236ec5e0ad1..1bd8b75e2133d 100644 --- a/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts @@ -9,7 +9,9 @@ import { AlertTaskInstance, taskInstanceToAlertTaskInstance } from './alert_task import uuid from 'uuid'; import { SanitizedAlert } from '../types'; -const alert: SanitizedAlert = { +const alert: SanitizedAlert<{ + bar: boolean; +}> = { id: 'alert-123', alertTypeId: '123', schedule: { interval: '10s' }, diff --git a/x-pack/plugins/alerts/server/task_runner/alert_task_instance.ts b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.ts index a290f3fa33c70..ab074cfdffa1c 100644 --- a/x-pack/plugins/alerts/server/task_runner/alert_task_instance.ts +++ b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.ts @@ -13,6 +13,7 @@ import { alertParamsSchema, alertStateSchema, AlertTaskParams, + AlertTypeParams, } from '../../common'; export interface AlertTaskInstance extends ConcreteTaskInstance { @@ -23,9 +24,9 @@ export interface AlertTaskInstance extends ConcreteTaskInstance { const enumerateErrorFields = (e: t.Errors) => `${e.map(({ context }) => context.map(({ key }) => key).join('.'))}`; -export function taskInstanceToAlertTaskInstance( +export function taskInstanceToAlertTaskInstance( taskInstance: ConcreteTaskInstance, - alert?: SanitizedAlert + alert?: SanitizedAlert ): AlertTaskInstance { return { ...taskInstance, diff --git a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts index b414e726f0101..5603b13a3b1f5 100644 --- a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertType } from '../types'; -import { createExecutionHandler } from './create_execution_handler'; +import { createExecutionHandler, CreateExecutionHandlerOptions } from './create_execution_handler'; import { loggingSystemMock } from '../../../../../src/core/server/mocks'; import { actionsMock, @@ -16,12 +15,19 @@ 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 { + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, +} from '../types'; jest.mock('./inject_action_params', () => ({ injectActionParams: jest.fn(), })); -const alertType: AlertType = { +const alertType: UntypedNormalizedAlertType = { id: 'test', name: 'Test', actionGroups: [ @@ -39,18 +45,26 @@ const alertType: AlertType = { }; const actionsClient = actionsClientMock.create(); -const createExecutionHandlerParams = { - actionsPlugin: actionsMock.createStart(), + +const mockActionsPlugin = actionsMock.createStart(); +const mockEventLogger = eventLoggerMock.create(); +const createExecutionHandlerParams: jest.Mocked< + CreateExecutionHandlerOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + > +> = { + actionsPlugin: mockActionsPlugin, spaceId: 'default', alertId: '1', alertName: 'name-of-alert', tags: ['tag-A', 'tag-B'], apiKey: 'MTIzOmFiYw==', - spaceIdToNamespace: jest.fn().mockReturnValue(undefined), - getBasePath: jest.fn().mockReturnValue(undefined), alertType, logger: loggingSystemMock.create().get(), - eventLogger: eventLoggerMock.create(), + eventLogger: mockEventLogger, actions: [ { id: '1', @@ -79,12 +93,10 @@ beforeEach(() => { .injectActionParams.mockImplementation( ({ actionParams }: InjectActionParamsOpts) => actionParams ); - createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); - createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); - createExecutionHandlerParams.actionsPlugin.getActionsClientWithRequest.mockResolvedValue( - actionsClient - ); - createExecutionHandlerParams.actionsPlugin.renderActionParameterTemplates.mockImplementation( + mockActionsPlugin.isActionTypeEnabled.mockReturnValue(true); + mockActionsPlugin.isActionExecutable.mockReturnValue(true); + mockActionsPlugin.getActionsClientWithRequest.mockResolvedValue(actionsClient); + mockActionsPlugin.renderActionParameterTemplates.mockImplementation( renderActionParameterTemplatesDefault ); }); @@ -97,9 +109,9 @@ test('enqueues execution per selected action', async () => { context: {}, alertInstanceId: '2', }); - expect( - createExecutionHandlerParams.actionsPlugin.getActionsClientWithRequest - ).toHaveBeenCalledWith(createExecutionHandlerParams.request); + expect(mockActionsPlugin.getActionsClientWithRequest).toHaveBeenCalledWith( + createExecutionHandlerParams.request + ); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.enqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -124,9 +136,8 @@ test('enqueues execution per selected action', async () => { ] `); - const eventLogger = createExecutionHandlerParams.eventLogger; - expect(eventLogger.logEvent).toHaveBeenCalledTimes(1); - expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + expect(mockEventLogger.logEvent).toHaveBeenCalledTimes(1); + expect(mockEventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` Array [ Array [ Object { @@ -171,9 +182,9 @@ test('enqueues execution per selected action', async () => { test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () => { // Mock two calls, one for check against actions[0] and the second for actions[1] - createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockReturnValueOnce(false); - createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValueOnce(false); - createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValueOnce(true); + mockActionsPlugin.isActionExecutable.mockReturnValueOnce(false); + mockActionsPlugin.isActionTypeEnabled.mockReturnValueOnce(false); + mockActionsPlugin.isActionTypeEnabled.mockReturnValueOnce(true); const executionHandler = createExecutionHandler({ ...createExecutionHandlerParams, actions: [ @@ -214,9 +225,9 @@ test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () => }); test('trow error error message when action type is disabled', async () => { - createExecutionHandlerParams.actionsPlugin.preconfiguredActions = []; - createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockReturnValue(false); - createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(false); + mockActionsPlugin.preconfiguredActions = []; + mockActionsPlugin.isActionExecutable.mockReturnValue(false); + mockActionsPlugin.isActionTypeEnabled.mockReturnValue(false); const executionHandler = createExecutionHandler({ ...createExecutionHandlerParams, actions: [ @@ -243,7 +254,7 @@ test('trow error error message when action type is disabled', async () => { expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(0); - createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockImplementation(() => true); + mockActionsPlugin.isActionExecutable.mockImplementation(() => true); const executionHandlerForPreconfiguredAction = createExecutionHandler({ ...createExecutionHandlerParams, actions: [...createExecutionHandlerParams.actions], 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 8c7ad79483194..8b4412aeb23e5 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 @@ -15,14 +15,20 @@ import { EVENT_LOG_ACTIONS } from '../plugin'; import { injectActionParams } from './inject_action_params'; import { AlertAction, + AlertTypeParams, + AlertTypeState, AlertInstanceState, AlertInstanceContext, - AlertType, - AlertTypeParams, RawAlert, } from '../types'; +import { NormalizedAlertType } from '../alert_type_registry'; -interface CreateExecutionHandlerOptions { +export interface CreateExecutionHandlerOptions< + Params extends AlertTypeParams, + State extends AlertTypeState, + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +> { alertId: string; alertName: string; tags?: string[]; @@ -30,7 +36,7 @@ interface CreateExecutionHandlerOptions { actions: AlertAction[]; spaceId: string; apiKey: RawAlert['apiKey']; - alertType: AlertType; + alertType: NormalizedAlertType; logger: Logger; eventLogger: IEventLogger; request: KibanaRequest; @@ -45,7 +51,12 @@ interface ExecutionHandlerOptions { state: AlertInstanceState; } -export function createExecutionHandler({ +export function createExecutionHandler< + Params extends AlertTypeParams, + State extends AlertTypeState, + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +>({ logger, alertId, alertName, @@ -58,7 +69,7 @@ export function createExecutionHandler({ eventLogger, request, alertParams, -}: CreateExecutionHandlerOptions) { +}: CreateExecutionHandlerOptions) { const alertTypeActionGroups = new Map( alertType.actionGroups.map((actionGroup) => [actionGroup.id, actionGroup.name]) ); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index a4b565194e431..967c5263b9730 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -6,7 +6,13 @@ import sinon from 'sinon'; import { schema } from '@kbn/config-schema'; -import { AlertExecutorOptions } from '../types'; +import { + AlertExecutorOptions, + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, +} from '../types'; import { ConcreteTaskInstance, isUnrecoverableError, @@ -28,9 +34,9 @@ import { IEventLogger } from '../../../event_log/server'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import { Alert, RecoveredActionGroup } from '../../common'; import { omit } from 'lodash'; -import { NormalizedAlertType } from '../alert_type_registry'; +import { UntypedNormalizedAlertType } from '../alert_type_registry'; import { alertTypeRegistryMock } from '../alert_type_registry.mock'; -const alertType = { +const alertType: jest.Mocked = { id: 'test', name: 'My test alert', actionGroups: [{ id: 'default', name: 'Default' }, RecoveredActionGroup], @@ -91,7 +97,7 @@ describe('Task Runner', () => { alertTypeRegistry, }; - const mockedAlertTypeSavedObject: Alert = { + const mockedAlertTypeSavedObject: Alert = { id: '1', consumer: 'bar', createdAt: new Date('2019-02-12T21:01:22.479Z'), @@ -150,7 +156,7 @@ describe('Task Runner', () => { test('successfully executes the task', async () => { const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, { ...mockedTaskInstance, state: { @@ -254,14 +260,21 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { executorServices .alertInstanceFactory('1') .scheduleActionsWithSubGroup('default', 'subDefault'); } ); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -407,12 +420,19 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { executorServices.alertInstanceFactory('1').scheduleActions('default'); } ); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -516,13 +536,20 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { executorServices.alertInstanceFactory('1').scheduleActions('default'); executorServices.alertInstanceFactory('2').scheduleActions('default'); } ); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -562,12 +589,19 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { executorServices.alertInstanceFactory('1').scheduleActions('default'); } ); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, { ...mockedTaskInstance, state: { @@ -656,12 +690,19 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { executorServices.alertInstanceFactory('1').scheduleActions('default'); } ); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, { ...mockedTaskInstance, state: { @@ -696,14 +737,21 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { executorServices .alertInstanceFactory('1') .scheduleActionsWithSubGroup('default', 'subgroup1'); } ); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, { ...mockedTaskInstance, state: { @@ -744,12 +792,19 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { executorServices.alertInstanceFactory('1').scheduleActions('default'); } ); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -912,12 +967,19 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { executorServices.alertInstanceFactory('1').scheduleActions('default'); } ); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, { ...mockedTaskInstance, state: { @@ -1012,12 +1074,19 @@ describe('Task Runner', () => { }; alertTypeWithCustomRecovery.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { executorServices.alertInstanceFactory('1').scheduleActions('default'); } ); const taskRunner = new TaskRunner( - alertTypeWithCustomRecovery as NormalizedAlertType, + alertTypeWithCustomRecovery, { ...mockedTaskInstance, state: { @@ -1103,13 +1172,20 @@ describe('Task Runner', () => { test('persists alertInstances passed in from state, only if they are scheduled for execution', async () => { alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { executorServices.alertInstanceFactory('1').scheduleActions('default'); } ); const date = new Date().toISOString(); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, { ...mockedTaskInstance, state: { @@ -1239,7 +1315,7 @@ describe('Task Runner', () => { param1: schema.string(), }), }, - } as NormalizedAlertType, + }, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -1267,7 +1343,7 @@ describe('Task Runner', () => { test('uses API key when provided', async () => { const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -1300,7 +1376,7 @@ describe('Task Runner', () => { test(`doesn't use API key when not provided`, async () => { const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -1330,7 +1406,7 @@ describe('Task Runner', () => { test('rescheduled the Alert if the schedule has update during a task run', async () => { const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -1365,13 +1441,20 @@ describe('Task Runner', () => { test('recovers gracefully when the AlertType executor throws an exception', async () => { alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { throw new Error('OMG'); } ); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -1438,7 +1521,7 @@ describe('Task Runner', () => { }); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -1497,7 +1580,7 @@ describe('Task Runner', () => { }); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -1564,7 +1647,7 @@ describe('Task Runner', () => { }); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -1631,7 +1714,7 @@ describe('Task Runner', () => { }); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); @@ -1701,7 +1784,7 @@ describe('Task Runner', () => { const legacyTaskInstance = omit(mockedTaskInstance, 'schedule'); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, legacyTaskInstance, taskRunnerFactoryInitializerParams ); @@ -1733,13 +1816,20 @@ describe('Task Runner', () => { }; alertType.executor.mockImplementation( - ({ services: executorServices }: AlertExecutorOptions) => { + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext + >) => { throw new Error('OMG'); } ); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, { ...mockedTaskInstance, state: originalAlertSate, @@ -1770,7 +1860,7 @@ describe('Task Runner', () => { }); const taskRunner = new TaskRunner( - alertType as NormalizedAlertType, + alertType, mockedTaskInstance, taskRunnerFactoryInitializerParams ); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 44cf7dd91be7d..c4187145e5a16 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -26,7 +26,6 @@ import { RawAlertInstance, AlertTaskState, Alert, - AlertExecutorOptions, SanitizedAlert, AlertExecutionStatus, AlertExecutionStatusErrorReasons, @@ -39,7 +38,13 @@ import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_l import { isAlertSavedObjectNotFoundError } from '../lib/is_alert_not_found_error'; import { AlertsClient } from '../alerts_client'; import { partiallyUpdateAlert } from '../saved_objects'; -import { ActionGroup } from '../../common'; +import { + ActionGroup, + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, +} from '../../common'; import { NormalizedAlertType } from '../alert_type_registry'; const FALLBACK_RETRY_INTERVAL = '5m'; @@ -55,15 +60,20 @@ interface AlertTaskInstance extends ConcreteTaskInstance { state: AlertTaskState; } -export class TaskRunner { +export class TaskRunner< + Params extends AlertTypeParams, + State extends AlertTypeState, + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +> { private context: TaskRunnerContext; private logger: Logger; private taskInstance: AlertTaskInstance; - private alertType: NormalizedAlertType; + private alertType: NormalizedAlertType; private readonly alertTypeRegistry: AlertTypeRegistry; constructor( - alertType: NormalizedAlertType, + alertType: NormalizedAlertType, taskInstance: ConcreteTaskInstance, context: TaskRunnerContext ) { @@ -131,8 +141,8 @@ export class TaskRunner { tags: string[] | undefined, spaceId: string, apiKey: RawAlert['apiKey'], - actions: Alert['actions'], - alertParams: RawAlert['params'] + actions: Alert['actions'], + alertParams: Params ) { return createExecutionHandler({ alertId, @@ -152,7 +162,7 @@ export class TaskRunner { async executeAlertInstance( alertInstanceId: string, - alertInstance: AlertInstance, + alertInstance: AlertInstance, executionHandler: ReturnType ) { const { @@ -168,8 +178,8 @@ export class TaskRunner { async executeAlertInstances( services: Services, - alert: SanitizedAlert, - params: AlertExecutorOptions['params'], + alert: SanitizedAlert, + params: Params, executionHandler: ReturnType, spaceId: string, event: Event @@ -190,9 +200,12 @@ export class TaskRunner { } = this.taskInstance; const namespace = this.context.spaceIdToNamespace(spaceId); - const alertInstances = mapValues, AlertInstance>( + const alertInstances = mapValues< + Record, + AlertInstance + >( alertRawInstances, - (rawAlertInstance) => new AlertInstance(rawAlertInstance) + (rawAlertInstance) => new AlertInstance(rawAlertInstance) ); const originalAlertInstances = cloneDeep(alertInstances); @@ -205,10 +218,12 @@ export class TaskRunner { alertId, services: { ...services, - alertInstanceFactory: createAlertInstanceFactory(alertInstances), + alertInstanceFactory: createAlertInstanceFactory( + alertInstances + ), }, params, - state: alertTypeState, + state: alertTypeState as State, startedAt: this.taskInstance.startedAt!, previousStartedAt: previousStartedAt ? new Date(previousStartedAt) : null, spaceId, @@ -232,12 +247,15 @@ export class TaskRunner { event.event.outcome = 'success'; // Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object - const instancesWithScheduledActions = pickBy(alertInstances, (alertInstance: AlertInstance) => - alertInstance.hasScheduledActions() + const instancesWithScheduledActions = pickBy( + alertInstances, + (alertInstance: AlertInstance) => + alertInstance.hasScheduledActions() ); const recoveredAlertInstances = pickBy( alertInstances, - (alertInstance: AlertInstance) => !alertInstance.hasScheduledActions() + (alertInstance: AlertInstance) => + !alertInstance.hasScheduledActions() ); logActiveAndRecoveredInstances({ @@ -272,7 +290,10 @@ export class TaskRunner { const instancesToExecute = notifyWhen === 'onActionGroupChange' ? Object.entries(instancesWithScheduledActions).filter( - ([alertInstanceName, alertInstance]: [string, AlertInstance]) => { + ([alertInstanceName, alertInstance]: [ + string, + AlertInstance + ]) => { const shouldExecuteAction = alertInstance.scheduledActionGroupOrSubgroupHasChanged(); if (!shouldExecuteAction) { this.logger.debug( @@ -283,7 +304,10 @@ export class TaskRunner { } ) : Object.entries(instancesWithScheduledActions).filter( - ([alertInstanceName, alertInstance]: [string, AlertInstance]) => { + ([alertInstanceName, alertInstance]: [ + string, + AlertInstance + ]) => { const throttled = alertInstance.isThrottled(throttle); const muted = mutedInstanceIdsSet.has(alertInstanceName); const shouldExecuteAction = !throttled && !muted; @@ -299,8 +323,9 @@ export class TaskRunner { ); await Promise.all( - instancesToExecute.map(([id, alertInstance]: [string, AlertInstance]) => - this.executeAlertInstance(id, alertInstance, executionHandler) + instancesToExecute.map( + ([id, alertInstance]: [string, AlertInstance]) => + this.executeAlertInstance(id, alertInstance, executionHandler) ) ); } else { @@ -309,17 +334,17 @@ export class TaskRunner { return { alertTypeState: updatedAlertTypeState || undefined, - alertInstances: mapValues, RawAlertInstance>( - instancesWithScheduledActions, - (alertInstance) => alertInstance.toRaw() - ), + alertInstances: mapValues< + Record>, + RawAlertInstance + >(instancesWithScheduledActions, (alertInstance) => alertInstance.toRaw()), }; } async validateAndExecuteAlert( services: Services, apiKey: RawAlert['apiKey'], - alert: SanitizedAlert, + alert: SanitizedAlert, event: Event ) { const { @@ -327,7 +352,7 @@ export class TaskRunner { } = this.taskInstance; // Validate - const validatedParams = validateAlertTypeParams(this.alertType, alert.params); + const validatedParams = validateAlertTypeParams(alert.params, this.alertType.validate?.params); const executionHandler = this.getExecutionHandler( alertId, alert.name, @@ -359,7 +384,7 @@ export class TaskRunner { } const [services, alertsClient] = this.getServicesWithSpaceLevelPermissions(spaceId, apiKey); - let alert: SanitizedAlert; + let alert: SanitizedAlert; // Ensure API key is still valid and user has access try { @@ -501,19 +526,23 @@ export class TaskRunner { } } -interface GenerateNewAndRecoveredInstanceEventsParams { +interface GenerateNewAndRecoveredInstanceEventsParams< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +> { eventLogger: IEventLogger; - originalAlertInstances: Dictionary; - currentAlertInstances: Dictionary; - recoveredAlertInstances: Dictionary; + originalAlertInstances: Dictionary>; + currentAlertInstances: Dictionary>; + recoveredAlertInstances: Dictionary>; alertId: string; alertLabel: string; namespace: string | undefined; } -function generateNewAndRecoveredInstanceEvents( - params: GenerateNewAndRecoveredInstanceEventsParams -) { +function generateNewAndRecoveredInstanceEvents< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +>(params: GenerateNewAndRecoveredInstanceEventsParams) { const { eventLogger, alertId, @@ -584,16 +613,22 @@ function generateNewAndRecoveredInstanceEvents( } } -interface ScheduleActionsForRecoveredInstancesParams { +interface ScheduleActionsForRecoveredInstancesParams< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +> { logger: Logger; recoveryActionGroup: ActionGroup; - recoveredAlertInstances: Dictionary; + recoveredAlertInstances: Dictionary>; executionHandler: ReturnType; mutedInstanceIdsSet: Set; alertLabel: string; } -function scheduleActionsForRecoveredInstances(params: ScheduleActionsForRecoveredInstancesParams) { +function scheduleActionsForRecoveredInstances< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +>(params: ScheduleActionsForRecoveredInstancesParams) { const { logger, recoveryActionGroup, @@ -623,14 +658,20 @@ function scheduleActionsForRecoveredInstances(params: ScheduleActionsForRecovere } } -interface LogActiveAndRecoveredInstancesParams { +interface LogActiveAndRecoveredInstancesParams< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +> { logger: Logger; - activeAlertInstances: Dictionary; - recoveredAlertInstances: Dictionary; + activeAlertInstances: Dictionary>; + recoveredAlertInstances: Dictionary>; alertLabel: string; } -function logActiveAndRecoveredInstances(params: LogActiveAndRecoveredInstancesParams) { +function logActiveAndRecoveredInstances< + InstanceState extends AlertInstanceState, + InstanceContext extends AlertInstanceContext +>(params: LogActiveAndRecoveredInstancesParams) { const { logger, activeAlertInstances, recoveredAlertInstances, alertLabel } = params; const activeInstanceIds = Object.keys(activeAlertInstances); const recoveredInstanceIds = Object.keys(recoveredAlertInstances); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts index 6c58b64fffa92..3a5a130f582ed 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts @@ -16,10 +16,10 @@ import { import { actionsMock } from '../../../actions/server/mocks'; import { alertsMock, alertsClientMock } from '../mocks'; import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; -import { NormalizedAlertType } from '../alert_type_registry'; +import { UntypedNormalizedAlertType } from '../alert_type_registry'; import { alertTypeRegistryMock } from '../alert_type_registry.mock'; -const alertType: NormalizedAlertType = { +const alertType: UntypedNormalizedAlertType = { id: 'test', name: 'My test alert', actionGroups: [{ id: 'default', name: 'Default' }], diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts index 1fe94972bd4b0..e266608d80880 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts @@ -17,7 +17,7 @@ import { AlertTypeRegistry, GetServicesFunction, SpaceIdToNamespaceFunction } fr import { TaskRunner } from './task_runner'; import { IEventLogger } from '../../../event_log/server'; import { AlertsClient } from '../alerts_client'; -import { NormalizedAlertType } from '../alert_type_registry'; +import { UntypedNormalizedAlertType } from '../alert_type_registry'; export interface TaskRunnerContext { logger: Logger; @@ -44,7 +44,7 @@ export class TaskRunnerFactory { this.taskRunnerContext = taskRunnerContext; } - public create(alertType: NormalizedAlertType, { taskInstance }: RunContext) { + public create(alertType: UntypedNormalizedAlertType, { taskInstance }: RunContext) { if (!this.isInitialized) { throw new Error('TaskRunnerFactory not initialized'); } diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 8704068c3e51a..027f875e2d08d 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -61,10 +61,10 @@ export interface AlertServices< } export interface AlertExecutorOptions< - Params extends AlertTypeParams = AlertTypeParams, - State extends AlertTypeState = AlertTypeState, - InstanceState extends AlertInstanceState = AlertInstanceState, - InstanceContext extends AlertInstanceContext = AlertInstanceContext + Params extends AlertTypeParams = never, + State extends AlertTypeState = never, + InstanceState extends AlertInstanceState = never, + InstanceContext extends AlertInstanceContext = never > { alertId: string; startedAt: Date; @@ -85,26 +85,28 @@ export interface ActionVariable { description: string; } -// signature of the alert type executor function export type ExecutorType< - Params, - State, - InstanceState extends AlertInstanceState = AlertInstanceState, - InstanceContext extends AlertInstanceContext = AlertInstanceContext + Params extends AlertTypeParams = never, + State extends AlertTypeState = never, + InstanceState extends AlertInstanceState = never, + InstanceContext extends AlertInstanceContext = never > = ( options: AlertExecutorOptions ) => Promise; +export interface AlertTypeParamsValidator { + validate: (object: unknown) => Params; +} export interface AlertType< - Params extends AlertTypeParams = AlertTypeParams, - State extends AlertTypeState = AlertTypeState, - InstanceState extends AlertInstanceState = AlertInstanceState, - InstanceContext extends AlertInstanceContext = AlertInstanceContext + Params extends AlertTypeParams = never, + State extends AlertTypeState = never, + InstanceState extends AlertInstanceState = never, + InstanceContext extends AlertInstanceContext = never > { id: string; name: string; validate?: { - params?: { validate: (object: unknown) => Params }; + params?: AlertTypeParamsValidator; }; actionGroups: ActionGroup[]; defaultActionGroupId: ActionGroup['id']; @@ -119,6 +121,13 @@ export interface AlertType< minimumLicenseRequired: LicenseType; } +export type UntypedAlertType = AlertType< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext +>; + export interface RawAlertAction extends SavedObjectAttributes { group: string; actionRef: string; @@ -142,7 +151,8 @@ export interface RawAlertExecutionStatus extends SavedObjectAttributes { }; } -export type PartialAlert = Pick & Partial>; +export type PartialAlert = Pick, 'id'> & + Partial, 'id'>>; export interface RawAlert extends SavedObjectAttributes { enabled: boolean; diff --git a/x-pack/plugins/infra/public/alerting/inventory/index.ts b/x-pack/plugins/infra/public/alerting/inventory/index.ts index 13ce43f77c8b0..da85f363b16ec 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/index.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/index.ts @@ -5,13 +5,21 @@ */ import { i18n } from '@kbn/i18n'; import React from 'react'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../server/lib/alerting/inventory_metric_threshold/types'; +import { + InventoryMetricConditions, + METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../server/lib/alerting/inventory_metric_threshold/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; +import { AlertTypeParams } from '../../../../alerts/common'; import { validateMetricThreshold } from './components/validation'; -export function createInventoryMetricAlertType(): AlertTypeModel { +interface InventoryMetricAlertTypeParams extends AlertTypeParams { + criteria: InventoryMetricConditions[]; +} + +export function createInventoryMetricAlertType(): AlertTypeModel { return { id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, description: i18n.translate('xpack.infra.metrics.inventory.alertFlyout.alertDescription', { diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts index cccd5fbc439d7..9c32c473f4597 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts @@ -8,10 +8,18 @@ import React from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; import { validateMetricThreshold } from './components/validation'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../server/lib/alerting/metric_threshold/types'; +import { AlertTypeParams } from '../../../../alerts/common'; +import { + MetricExpressionParams, + METRIC_THRESHOLD_ALERT_TYPE_ID, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../server/lib/alerting/metric_threshold/types'; + +interface MetricThresholdAlertTypeParams extends AlertTypeParams { + criteria: MetricExpressionParams[]; +} -export function createMetricThresholdAlertType(): AlertTypeModel { +export function createMetricThresholdAlertType(): AlertTypeModel { return { id: METRIC_THRESHOLD_ALERT_TYPE_ID, description: i18n.translate('xpack.infra.metrics.alertFlyout.alertDescription', { diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 1941ec6326ddb..54cf8658a3f0d 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -9,7 +9,11 @@ import moment from 'moment'; import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; import { AlertStates, InventoryMetricConditions } from './types'; -import { RecoveredActionGroup } from '../../../../../alerts/common'; +import { + AlertInstanceContext, + AlertInstanceState, + RecoveredActionGroup, +} from '../../../../../alerts/common'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; import { InfraBackendLibs } from '../../infra_types'; @@ -35,7 +39,15 @@ interface InventoryMetricThresholdParams { export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => async ({ services, params, -}: AlertExecutorOptions) => { +}: AlertExecutorOptions< + /** + * TODO: Remove this use of `any` by utilizing a proper type + */ + Record, + Record, + AlertInstanceState, + AlertInstanceContext +>) => { const { criteria, filterQuery, diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts index 2d1df6e8cb462..a2e8eff34ef98 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; -import { AlertType } from '../../../../../alerts/server'; +import { AlertType, AlertInstanceState, AlertInstanceContext } from '../../../../../alerts/server'; import { createInventoryMetricThresholdExecutor, FIRED_ACTIONS, @@ -40,7 +40,17 @@ const condition = schema.object({ ), }); -export const registerMetricInventoryThresholdAlertType = (libs: InfraBackendLibs): AlertType => ({ +export const registerMetricInventoryThresholdAlertType = ( + libs: InfraBackendLibs +): AlertType< + /** + * TODO: Remove this use of `any` by utilizing a proper type + */ + Record, + Record, + AlertInstanceState, + AlertInstanceContext +> => ({ id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, name: i18n.translate('xpack.infra.metrics.inventory.alertName', { defaultMessage: 'Inventory', diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index d3d34cd2aad58..dccab5168fb60 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -9,7 +9,10 @@ import { AlertExecutorOptions, AlertServices, AlertInstance, + AlertTypeParams, + AlertTypeState, AlertInstanceContext, + AlertInstanceState, } from '../../../../../alerts/server'; import { AlertStates, @@ -34,6 +37,14 @@ import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; import { decodeOrThrow } from '../../../../common/runtime_types'; import { UNGROUPED_FACTORY_KEY } from '../common/utils'; +type LogThresholdAlertServices = AlertServices; +type LogThresholdAlertExecutorOptions = AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext +>; + const COMPOSITE_GROUP_SIZE = 40; const checkValueAgainstComparatorMap: { @@ -46,7 +57,7 @@ const checkValueAgainstComparatorMap: { }; export const createLogThresholdExecutor = (libs: InfraBackendLibs) => - async function ({ services, params }: AlertExecutorOptions) { + async function ({ services, params }: LogThresholdAlertExecutorOptions) { const { alertInstanceFactory, savedObjectsClient, callCluster } = services; const { sources } = libs; @@ -88,8 +99,8 @@ async function executeAlert( alertParams: CountAlertParams, timestampField: string, indexPattern: string, - callCluster: AlertServices['callCluster'], - alertInstanceFactory: AlertServices['alertInstanceFactory'] + callCluster: LogThresholdAlertServices['callCluster'], + alertInstanceFactory: LogThresholdAlertServices['alertInstanceFactory'] ) { const query = getESQuery(alertParams, timestampField, indexPattern); @@ -118,8 +129,8 @@ async function executeRatioAlert( alertParams: RatioAlertParams, timestampField: string, indexPattern: string, - callCluster: AlertServices['callCluster'], - alertInstanceFactory: AlertServices['alertInstanceFactory'] + callCluster: LogThresholdAlertServices['callCluster'], + alertInstanceFactory: LogThresholdAlertServices['alertInstanceFactory'] ) { // Ratio alert params are separated out into two standard sets of alert params const numeratorParams: AlertParams = { @@ -175,7 +186,7 @@ const getESQuery = ( export const processUngroupedResults = ( results: UngroupedSearchQueryResponse, params: CountAlertParams, - alertInstanceFactory: AlertExecutorOptions['services']['alertInstanceFactory'], + alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'], alertInstaceUpdater: AlertInstanceUpdater ) => { const { count, criteria } = params; @@ -204,7 +215,7 @@ export const processUngroupedRatioResults = ( numeratorResults: UngroupedSearchQueryResponse, denominatorResults: UngroupedSearchQueryResponse, params: RatioAlertParams, - alertInstanceFactory: AlertExecutorOptions['services']['alertInstanceFactory'], + alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'], alertInstaceUpdater: AlertInstanceUpdater ) => { const { count, criteria } = params; @@ -259,7 +270,7 @@ const getReducedGroupByResults = ( export const processGroupByResults = ( results: GroupedSearchQueryResponse['aggregations']['groups']['buckets'], params: CountAlertParams, - alertInstanceFactory: AlertExecutorOptions['services']['alertInstanceFactory'], + alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'], alertInstaceUpdater: AlertInstanceUpdater ) => { const { count, criteria } = params; @@ -292,7 +303,7 @@ export const processGroupByRatioResults = ( numeratorResults: GroupedSearchQueryResponse['aggregations']['groups']['buckets'], denominatorResults: GroupedSearchQueryResponse['aggregations']['groups']['buckets'], params: RatioAlertParams, - alertInstanceFactory: AlertExecutorOptions['services']['alertInstanceFactory'], + alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'], alertInstaceUpdater: AlertInstanceUpdater ) => { const { count, criteria } = params; @@ -599,11 +610,17 @@ const getQueryMappingForComparator = (comparator: Comparator) => { return queryMappings[comparator]; }; -const getUngroupedResults = async (query: object, callCluster: AlertServices['callCluster']) => { +const getUngroupedResults = async ( + query: object, + callCluster: LogThresholdAlertServices['callCluster'] +) => { return decodeOrThrow(UngroupedSearchQueryResponseRT)(await callCluster('search', query)); }; -const getGroupedResults = async (query: object, callCluster: AlertServices['callCluster']) => { +const getGroupedResults = async ( + query: object, + callCluster: LogThresholdAlertServices['callCluster'] +) => { let compositeGroupBuckets: GroupedSearchQueryResponse['aggregations']['groups']['buckets'] = []; let lastAfterKey: GroupedSearchQueryResponse['aggregations']['groups']['after_key'] | undefined; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts index 49f82c7ccec0b..d51d9435fc904 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts @@ -12,7 +12,7 @@ import { import { InfraSource } from '../../../../../common/http_api/source_api'; import { InfraDatabaseSearchResponse } from '../../../adapters/framework/adapter_types'; import { createAfterKeyHandler } from '../../../../utils/create_afterkey_handler'; -import { AlertServices, AlertExecutorOptions } from '../../../../../../alerts/server'; +import { AlertServices } from '../../../../../../alerts/server'; import { getAllCompositeData } from '../../../../utils/get_all_composite_data'; import { DOCUMENT_COUNT_I18N } from '../../common/messages'; import { UNGROUPED_FACTORY_KEY } from '../../common/utils'; @@ -35,17 +35,19 @@ interface CompositeAggregationsResponse { }; } -export const evaluateAlert = ( +export interface EvaluatedAlertParams { + criteria: MetricExpressionParams[]; + groupBy: string | undefined | string[]; + filterQuery: string | undefined; +} + +export const evaluateAlert = ( callCluster: AlertServices['callCluster'], - params: AlertExecutorOptions['params'], + params: Params, config: InfraSource['configuration'], timeframe?: { start: number; end: number } ) => { - const { criteria, groupBy, filterQuery } = params as { - criteria: MetricExpressionParams[]; - groupBy: string | undefined | string[]; - filterQuery: string | undefined; - }; + const { criteria, groupBy, filterQuery } = params; return Promise.all( criteria.map(async (criterion) => { const currentValues = await getMetric( diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index a1d6428f3b52b..6c9fac9d1133c 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -7,13 +7,13 @@ import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold import { Comparator, AlertStates } from './types'; import * as mocks from './test_mocks'; import { RecoveredActionGroup } from '../../../../../alerts/common'; -import { AlertExecutorOptions } from '../../../../../alerts/server'; import { alertsMock, AlertServicesMock, AlertInstanceMock, } from '../../../../../alerts/server/mocks'; import { InfraSources } from '../../sources'; +import { MetricThresholdAlertExecutorOptions } from './register_metric_threshold_alert_type'; interface AlertTestInstance { instance: AlertInstanceMock; @@ -23,11 +23,23 @@ interface AlertTestInstance { let persistAlertInstances = false; +const mockOptions = { + alertId: '', + startedAt: new Date(), + previousStartedAt: null, + state: {}, + spaceId: '', + name: '', + tags: [], + createdBy: null, + updatedBy: null, +}; + describe('The metric threshold alert type', () => { describe('querying the entire infrastructure', () => { const instanceID = '*'; const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => - executor({ + executor(({ services, params: { sourceId, @@ -39,7 +51,10 @@ describe('The metric threshold alert type', () => { }, ], }, - }); + /** + * TODO: Remove this use of `as` by utilizing a proper type + */ + } as unknown) as MetricThresholdAlertExecutorOptions); test('alerts as expected with the > comparator', async () => { await execute(Comparator.GT, [0.75]); expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); @@ -109,6 +124,7 @@ describe('The metric threshold alert type', () => { describe('querying with a groupBy parameter', () => { const execute = (comparator: Comparator, threshold: number[]) => executor({ + ...mockOptions, services, params: { groupBy: 'something', @@ -159,6 +175,7 @@ describe('The metric threshold alert type', () => { groupBy: string = '' ) => executor({ + ...mockOptions, services, params: { groupBy, @@ -216,6 +233,7 @@ describe('The metric threshold alert type', () => { const instanceID = '*'; const execute = (comparator: Comparator, threshold: number[]) => executor({ + ...mockOptions, services, params: { criteria: [ @@ -242,6 +260,7 @@ describe('The metric threshold alert type', () => { const instanceID = '*'; const execute = (comparator: Comparator, threshold: number[]) => executor({ + ...mockOptions, services, params: { criteria: [ @@ -268,6 +287,7 @@ describe('The metric threshold alert type', () => { const instanceID = '*'; const execute = (comparator: Comparator, threshold: number[]) => executor({ + ...mockOptions, services, params: { criteria: [ @@ -294,6 +314,7 @@ describe('The metric threshold alert type', () => { const instanceID = '*'; const execute = (alertOnNoData: boolean) => executor({ + ...mockOptions, services, params: { criteria: [ @@ -323,6 +344,7 @@ describe('The metric threshold alert type', () => { const instanceID = '*'; const execute = () => executor({ + ...mockOptions, services, params: { criteria: [ @@ -348,6 +370,7 @@ describe('The metric threshold alert type', () => { const instanceID = '*'; const execute = (threshold: number[]) => executor({ + ...mockOptions, services, params: { criteria: [ @@ -392,6 +415,7 @@ describe('The metric threshold alert type', () => { const instanceID = '*'; const execute = () => executor({ + ...mockOptions, services, params: { sourceId: 'default', @@ -435,10 +459,7 @@ const mockLibs: any = { configuration: createMockStaticConfiguration({}), }; -const executor = createMetricThresholdExecutor(mockLibs) as (opts: { - params: AlertExecutorOptions['params']; - services: { callCluster: AlertExecutorOptions['params']['callCluster'] }; -}) => Promise; +const executor = createMetricThresholdExecutor(mockLibs); const services: AlertServicesMock = alertsMock.createAlertServices(); services.callCluster.mockImplementation(async (_: string, { body, index }: any) => { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 60790648d9a9b..d63b42cd3b146 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -7,7 +7,6 @@ import { first, last } from 'lodash'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; import { RecoveredActionGroup } from '../../../../../alerts/common'; -import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InfraBackendLibs } from '../../infra_types'; import { buildErrorAlertReason, @@ -18,10 +17,16 @@ import { } from '../common/messages'; import { createFormatter } from '../../../../common/formatters'; import { AlertStates } from './types'; -import { evaluateAlert } from './lib/evaluate_alert'; +import { evaluateAlert, EvaluatedAlertParams } from './lib/evaluate_alert'; +import { + MetricThresholdAlertExecutorOptions, + MetricThresholdAlertType, +} from './register_metric_threshold_alert_type'; -export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => - async function (options: AlertExecutorOptions) { +export const createMetricThresholdExecutor = ( + libs: InfraBackendLibs +): MetricThresholdAlertType['executor'] => + async function (options: MetricThresholdAlertExecutorOptions) { const { services, params } = options; const { criteria } = params; if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); @@ -36,7 +41,11 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => sourceId || 'default' ); const config = source.configuration; - const alertResults = await evaluateAlert(services.callCluster, params, config); + const alertResults = await evaluateAlert( + services.callCluster, + params as EvaluatedAlertParams, + config + ); // Because each alert result has the same group definitions, just grab the groups from the first one. const groups = Object.keys(first(alertResults)!); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts index f04a1015bcbcd..000c89f5899ef 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts @@ -5,7 +5,12 @@ */ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; -import { AlertType } from '../../../../../alerts/server'; +import { + AlertType, + AlertInstanceState, + AlertInstanceContext, + AlertExecutorOptions, +} from '../../../../../alerts/server'; import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api/metrics_explorer'; import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor'; import { METRIC_THRESHOLD_ALERT_TYPE_ID, Comparator } from './types'; @@ -21,7 +26,26 @@ import { thresholdActionVariableDescription, } from '../common/messages'; -export function registerMetricThresholdAlertType(libs: InfraBackendLibs): AlertType { +export type MetricThresholdAlertType = AlertType< + /** + * TODO: Remove this use of `any` by utilizing a proper type + */ + Record, + Record, + AlertInstanceState, + AlertInstanceContext +>; +export type MetricThresholdAlertExecutorOptions = AlertExecutorOptions< + /** + * TODO: Remove this use of `any` by utilizing a proper type + */ + Record, + Record, + AlertInstanceState, + AlertInstanceContext +>; + +export function registerMetricThresholdAlertType(libs: InfraBackendLibs): MetricThresholdAlertType { const baseCriterion = { threshold: schema.arrayOf(schema.number()), comparator: oneOfLiterals(Object.values(Comparator)), diff --git a/x-pack/plugins/monitoring/common/types/alerts.ts b/x-pack/plugins/monitoring/common/types/alerts.ts index 93807f9df12b0..df6e169f37f7a 100644 --- a/x-pack/plugins/monitoring/common/types/alerts.ts +++ b/x-pack/plugins/monitoring/common/types/alerts.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Alert, SanitizedAlert } from '../../../alerts/common'; +import { Alert, AlertTypeParams, SanitizedAlert } from '../../../alerts/common'; import { AlertParamType, AlertMessageTokenType, AlertSeverity } from '../enums'; -export type CommonAlert = Alert | SanitizedAlert; +export type CommonAlert = Alert | SanitizedAlert; export interface CommonAlertStatus { states: CommonAlertState[]; - rawAlert: Alert | SanitizedAlert; + rawAlert: Alert | SanitizedAlert; } export interface CommonAlertState { diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx index 4d22d422ecda6..6d7751d91b761 100644 --- a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx @@ -9,8 +9,9 @@ import { i18n } from '@kbn/i18n'; import { Expression, Props } from '../components/duration/expression'; import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; import { ALERT_CCR_READ_EXCEPTIONS, ALERT_DETAILS } from '../../../common/constants'; +import { AlertTypeParams } from '../../../../alerts/common'; -interface ValidateOptions { +interface ValidateOptions extends AlertTypeParams { duration: string; } @@ -30,7 +31,7 @@ const validate = (inputValues: ValidateOptions): ValidationResult => { return validationResult; }; -export function createCCRReadExceptionsAlertType(): AlertTypeModel { +export function createCCRReadExceptionsAlertType(): AlertTypeModel { return { id: ALERT_CCR_READ_EXCEPTIONS, description: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].description, diff --git a/x-pack/plugins/monitoring/public/alerts/components/duration/validation.tsx b/x-pack/plugins/monitoring/public/alerts/components/duration/validation.tsx index 892ee0926ca38..c4e5ff343da17 100644 --- a/x-pack/plugins/monitoring/public/alerts/components/duration/validation.tsx +++ b/x-pack/plugins/monitoring/public/alerts/components/duration/validation.tsx @@ -5,9 +5,11 @@ */ import { i18n } from '@kbn/i18n'; +import { AlertTypeParams } from '../../../../../alerts/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ValidationResult } from '../../../../../triggers_actions_ui/public/types'; +export type MonitoringAlertTypeParams = ValidateOptions & AlertTypeParams; interface ValidateOptions { duration: string; threshold: number; diff --git a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx index 1fe40fc8777f4..d2cec006b1b1d 100644 --- a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx @@ -7,10 +7,10 @@ import React from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; import { ALERT_CPU_USAGE, ALERT_DETAILS } from '../../../common/constants'; -import { validate } from '../components/duration/validation'; +import { validate, MonitoringAlertTypeParams } from '../components/duration/validation'; import { Expression, Props } from '../components/duration/expression'; -export function createCpuUsageAlertType(): AlertTypeModel { +export function createCpuUsageAlertType(): AlertTypeModel { return { id: ALERT_CPU_USAGE, description: ALERT_DETAILS[ALERT_CPU_USAGE].description, diff --git a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx index 5579b8e1275a3..bea399ee89f6a 100644 --- a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx @@ -5,14 +5,14 @@ */ import React from 'react'; -import { validate } from '../components/duration/validation'; +import { validate, MonitoringAlertTypeParams } from '../components/duration/validation'; import { Expression, Props } from '../components/duration/expression'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; import { ALERT_DISK_USAGE, ALERT_DETAILS } from '../../../common/constants'; -export function createDiskUsageAlertType(): AlertTypeModel { +export function createDiskUsageAlertType(): AlertTypeModel { return { id: ALERT_DISK_USAGE, description: ALERT_DETAILS[ALERT_DISK_USAGE].description, diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx index 0400810a8c379..0428e4e7c733e 100644 --- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx @@ -5,14 +5,14 @@ */ import React from 'react'; -import { validate } from '../components/duration/validation'; +import { validate, MonitoringAlertTypeParams } from '../components/duration/validation'; import { Expression, Props } from '../components/duration/expression'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; import { ALERT_MEMORY_USAGE, ALERT_DETAILS } from '../../../common/constants'; -export function createMemoryUsageAlertType(): AlertTypeModel { +export function createMemoryUsageAlertType(): AlertTypeModel { return { id: ALERT_MEMORY_USAGE, description: ALERT_DETAILS[ALERT_MEMORY_USAGE].description, diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index a3bcc310b8084..46adfebfd17bf 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -13,7 +13,7 @@ import { AlertsClient, AlertServices, } from '../../../alerts/server'; -import { Alert, RawAlertInstance, SanitizedAlert } from '../../../alerts/common'; +import { Alert, AlertTypeParams, RawAlertInstance, SanitizedAlert } from '../../../alerts/common'; import { ActionsClient } from '../../../actions/server'; import { AlertState, @@ -135,7 +135,7 @@ export class BaseAlert { alertsClient: AlertsClient, actionsClient: ActionsClient, actions: AlertEnableAction[] - ): Promise { + ): Promise> { const existingAlertData = await alertsClient.find({ options: { search: this.alertOptions.id, @@ -170,7 +170,7 @@ export class BaseAlert { throttle = '1d', interval = '1m', } = this.alertOptions; - return await alertsClient.create({ + return await alertsClient.create({ data: { enabled: true, tags: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts index 5731a51aeabc1..f0895f1367289 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts @@ -6,7 +6,7 @@ import { Alert } from '../../../../../alerts/common'; import { SERVER_APP_ID, NOTIFICATIONS_ID } from '../../../../common/constants'; -import { CreateNotificationParams } from './types'; +import { CreateNotificationParams, RuleNotificationAlertTypeParams } from './types'; import { addTags } from './add_tags'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; @@ -17,8 +17,8 @@ export const createNotifications = async ({ ruleAlertId, interval, name, -}: CreateNotificationParams): Promise => - alertsClient.create({ +}: CreateNotificationParams): Promise> => + alertsClient.create({ data: { name, tags: addTags([], ruleAlertId), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/find_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/find_notifications.ts index 5d3a328dd6fbb..1bf07a1574be6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/find_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/find_notifications.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FindResult } from '../../../../../alerts/server'; +import { AlertTypeParams, FindResult } from '../../../../../alerts/server'; import { NOTIFICATIONS_ID } from '../../../../common/constants'; import { FindNotificationParams } from './types'; @@ -24,7 +24,7 @@ export const findNotifications = async ({ filter, sortField, sortOrder, -}: FindNotificationParams): Promise => +}: FindNotificationParams): Promise> => alertsClient.find({ options: { fields, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/read_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/read_notifications.ts index fe9101335b4f5..74df77bf9bf74 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/read_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/read_notifications.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SanitizedAlert } from '../../../../../alerts/common'; +import { AlertTypeParams, SanitizedAlert } from '../../../../../alerts/common'; import { ReadNotificationParams, isAlertType } from './types'; import { findNotifications } from './find_notifications'; import { INTERNAL_RULE_ALERT_ID_KEY } from '../../../../common/constants'; @@ -13,7 +13,7 @@ export const readNotifications = async ({ alertsClient, id, ruleAlertId, -}: ReadNotificationParams): Promise => { +}: ReadNotificationParams): Promise | null> => { if (id != null) { try { const notification = await alertsClient.get({ id }); 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 cc9fb149a7e1b..e4e9df552101b 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,18 +8,20 @@ import { AlertsClient, PartialAlert, AlertType, + AlertTypeParams, AlertTypeState, + AlertInstanceState, + AlertInstanceContext, AlertExecutorOptions, } from '../../../../../alerts/server'; import { Alert } from '../../../../../alerts/common'; import { NOTIFICATIONS_ID } from '../../../../common/constants'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -export interface RuleNotificationAlertType extends Alert { - params: { - ruleAlertId: string; - }; +export interface RuleNotificationAlertTypeParams extends AlertTypeParams { + ruleAlertId: string; } +export type RuleNotificationAlertType = Alert; export interface FindNotificationParams { alertsClient: AlertsClient; @@ -76,32 +78,36 @@ export interface ReadNotificationParams { } export const isAlertTypes = ( - partialAlert: PartialAlert[] + partialAlert: Array> ): partialAlert is RuleNotificationAlertType[] => { return partialAlert.every((rule) => isAlertType(rule)); }; export const isAlertType = ( - partialAlert: PartialAlert + partialAlert: PartialAlert ): partialAlert is RuleNotificationAlertType => { return partialAlert.alertTypeId === NOTIFICATIONS_ID; }; -export type NotificationExecutorOptions = Omit & { - params: { - ruleAlertId: string; - }; -}; +export type NotificationExecutorOptions = AlertExecutorOptions< + RuleNotificationAlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext +>; // This returns true because by default a NotificationAlertTypeDefinition is an AlertType // since we are only increasing the strictness of params. export const isNotificationAlertExecutor = ( obj: NotificationAlertTypeDefinition -): obj is AlertType => { +): obj is AlertType => { return true; }; -export type NotificationAlertTypeDefinition = Omit & { +export type NotificationAlertTypeDefinition = Omit< + AlertType, + 'executor' +> & { executor: ({ services, params, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts index d6c8973215117..8528d53b51f5b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/update_notifications.ts @@ -6,7 +6,7 @@ import { PartialAlert } from '../../../../../alerts/server'; import { readNotifications } from './read_notifications'; -import { UpdateNotificationParams } from './types'; +import { RuleNotificationAlertTypeParams, UpdateNotificationParams } from './types'; import { addTags } from './add_tags'; import { createNotifications } from './create_notifications'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; @@ -18,11 +18,11 @@ export const updateNotifications = async ({ ruleAlertId, name, interval, -}: UpdateNotificationParams): Promise => { +}: UpdateNotificationParams): Promise | null> => { const notification = await readNotifications({ alertsClient, id: undefined, ruleAlertId }); if (interval && notification) { - return alertsClient.update({ + return alertsClient.update({ id: notification.id, data: { tags: addTags([], ruleAlertId), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index b185b8780abe2..3473948b000c7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -22,6 +22,8 @@ import { buildRouteValidation } from '../../../../utils/build_validation/route_v import { transformBulkError, createBulkErrorObject, buildSiemResponse } from '../utils'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'; +import { RuleTypeParams } from '../../types'; +import { Alert } from '../../../../../../alerts/common'; export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => { router.post( @@ -95,9 +97,12 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => }); } - const createdRule = await alertsClient.create({ + /** + * TODO: Remove this use of `as` by utilizing the proper type + */ + const createdRule = (await alertsClient.create({ data: internalRule, - }); + })) as Alert; const ruleActions = await updateRulesNotifications({ ruleAlertId: createdRule.id, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts index b52248f670188..c59d5d2a36fd5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -19,6 +19,8 @@ import { createRulesSchema } from '../../../../../common/detection_engine/schema import { newTransformValidate } from './validate'; import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents'; import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'; +import { RuleTypeParams } from '../../types'; +import { Alert } from '../../../../../../alerts/common'; export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void => { router.post( @@ -85,9 +87,12 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void // This will create the endpoint list if it does not exist yet await context.lists?.getExceptionListClient().createEndpointList(); - const createdRule = await alertsClient.create({ + /** + * TODO: Remove this use of `as` by utilizing the proper type + */ + const createdRule = (await alertsClient.create({ data: internalRule, - }); + })) as Alert; const ruleActions = await updateRulesNotifications({ ruleAlertId: createdRule.id, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts index 7a6cd707eb185..bd7f11b2d8756 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts @@ -31,6 +31,7 @@ import { OutputError, } from '../utils'; import { RuleActions } from '../../rule_actions/types'; +import { RuleTypeParams } from '../../types'; type PromiseFromStreams = ImportRulesSchemaDecoded | Error; @@ -172,7 +173,7 @@ export const transformAlertsToRules = (alerts: RuleAlertType[]): Array, ruleActions: Array, ruleStatuses?: Array> ): { @@ -203,7 +204,7 @@ export const transformFindAlerts = ( }; export const transform = ( - alert: PartialAlert, + alert: PartialAlert, ruleActions?: RuleActions | null, ruleStatus?: SavedObject ): Partial | null => { @@ -220,7 +221,7 @@ export const transform = ( export const transformOrBulkError = ( ruleId: string, - alert: PartialAlert, + alert: PartialAlert, ruleActions: RuleActions, ruleStatus?: unknown ): Partial | BulkError => { @@ -241,7 +242,7 @@ export const transformOrBulkError = ( export const transformOrImportError = ( ruleId: string, - alert: PartialAlert, + alert: PartialAlert, existingImportSuccessError: ImportSuccessError ): ImportSuccessError => { if (isAlertType(alert)) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts index 8653bdc0427e4..51b08cb3fce89 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts @@ -15,6 +15,7 @@ import { RulesSchema } from '../../../../../common/detection_engine/schemas/resp import { getResult, getFindResultStatus } from '../__mocks__/request_responses'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; +import { RuleTypeParams } from '../../types'; export const ruleOutput = (): RulesSchema => ({ actions: [], @@ -88,7 +89,7 @@ describe('validate', () => { describe('transformValidateFindAlerts', () => { test('it should do a validation correctly of a find alert', () => { - const findResult: FindResult = { + const findResult: FindResult = { data: [getResult()], page: 1, perPage: 0, @@ -111,7 +112,7 @@ describe('validate', () => { }); test('it should do an in-validation correctly of a partial alert', () => { - const findResult: FindResult = { + const findResult: FindResult = { data: [getResult()], page: 1, perPage: 0, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts index 382186df16cd1..3e3b85a407fc2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts @@ -31,9 +31,10 @@ import { import { createBulkErrorObject, BulkError } from '../utils'; import { transformFindAlerts, transform, transformAlertToRule } from './utils'; import { RuleActions } from '../../rule_actions/types'; +import { RuleTypeParams } from '../../types'; export const transformValidateFindAlerts = ( - findResults: FindResult, + findResults: FindResult, ruleActions: Array, ruleStatuses?: Array> ): [ @@ -63,7 +64,7 @@ export const transformValidateFindAlerts = ( }; export const transformValidate = ( - alert: PartialAlert, + alert: PartialAlert, ruleActions?: RuleActions | null, ruleStatus?: SavedObject ): [RulesSchema | null, string | null] => { @@ -76,7 +77,7 @@ export const transformValidate = ( }; export const newTransformValidate = ( - alert: PartialAlert, + alert: PartialAlert, ruleActions?: RuleActions | null, ruleStatus?: SavedObject ): [FullResponseSchema | null, string | null] => { @@ -90,7 +91,7 @@ export const newTransformValidate = ( export const transformValidateBulkError = ( ruleId: string, - alert: PartialAlert, + alert: PartialAlert, ruleActions?: RuleActions | null, ruleStatus?: SavedObjectsFindResponse ): RulesSchema | BulkError => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index 0519a98df1fae..6339e92eb012b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -9,6 +9,7 @@ import { Alert } from '../../../../../alerts/common'; import { SERVER_APP_ID, SIGNALS_ID } from '../../../../common/constants'; import { CreateRulesOptions } from './types'; import { addTags } from './add_tags'; +import { PartialFilter, RuleTypeParams } from '../types'; export const createRules = async ({ alertsClient, @@ -59,8 +60,8 @@ export const createRules = async ({ version, exceptionsList, actions, -}: CreateRulesOptions): Promise => { - return alertsClient.create({ +}: CreateRulesOptions): Promise> => { + return alertsClient.create({ data: { name, tags: addTags(tags, ruleId, immutable), @@ -95,7 +96,10 @@ export const createRules = async ({ severityMapping, threat, threshold, - threatFilters, + /** + * TODO: Fix typing inconsistancy between `RuleTypeParams` and `CreateRulesOptions` + */ + threatFilters: threatFilters as PartialFilter[] | undefined, threatIndex, threatQuery, concurrentSearches, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts index 18b851c440e20..5ab97262515da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts @@ -6,6 +6,7 @@ import { FindResult } from '../../../../../alerts/server'; import { SIGNALS_ID } from '../../../../common/constants'; +import { RuleTypeParams } from '../types'; import { FindRuleOptions } from './types'; export const getFilter = (filter: string | null | undefined) => { @@ -24,7 +25,7 @@ export const findRules = async ({ filter, sortField, sortOrder, -}: FindRuleOptions): Promise => { +}: FindRuleOptions): Promise> => { return alertsClient.find({ options: { fields, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts index 4c01318f02cde..1f43706c17fec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -5,7 +5,7 @@ */ import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import { Alert } from '../../../../../alerts/common'; +import { Alert, AlertTypeParams } from '../../../../../alerts/common'; import { AlertsClient } from '../../../../../alerts/server'; import { createRules } from './create_rules'; import { PartialFilter } from '../types'; @@ -14,8 +14,8 @@ export const installPrepackagedRules = ( alertsClient: AlertsClient, rules: AddPrepackagedRulesSchemaDecoded[], outputIndex: string -): Array> => - rules.reduce>>((acc, rule) => { +): Array>> => + rules.reduce>>>((acc, rule) => { const { anomaly_threshold: anomalyThreshold, author, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts index b2303d48b0517..484dac8e31fb4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts @@ -9,8 +9,9 @@ import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks'; import { INTERNAL_RULE_ID_KEY, INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants'; import { SanitizedAlert } from '../../../../../alerts/common'; +import { RuleTypeParams } from '../types'; -const rule: SanitizedAlert = { +const rule: SanitizedAlert = { id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', name: 'Detect Root/Admin Users', tags: [`${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:false`], @@ -67,6 +68,8 @@ const rule: SanitizedAlert = { note: '# Investigative notes', version: 1, exceptionsList: [ + /** + TODO: fix this mock. Which the typing has revealed is wrong { field: 'source.ip', values_operator: 'included', @@ -96,8 +99,31 @@ const rule: SanitizedAlert = { ], }, ], - }, + },*/ ], + /** + * The fields below were missing as the type was partial and hence not technically correct + */ + author: [], + buildingBlockType: undefined, + eventCategoryOverride: undefined, + license: undefined, + savedId: undefined, + interval: undefined, + riskScoreMapping: undefined, + ruleNameOverride: undefined, + name: undefined, + severityMapping: undefined, + tags: undefined, + threshold: undefined, + threatFilters: undefined, + threatIndex: undefined, + threatQuery: undefined, + threatMapping: undefined, + threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, + timestampOverride: undefined, }, createdAt: new Date('2019-12-13T16:40:33.400Z'), updatedAt: new Date('2019-12-13T16:40:33.400Z'), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index c86526cee9302..c1720c4fa3587 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -13,6 +13,7 @@ import { addTags } from './add_tags'; import { calculateVersion, calculateName, calculateInterval, removeUndefined } from './utils'; import { ruleStatusSavedObjectsClientFactory } from '../signals/rule_status_saved_objects_client'; import { internalRuleUpdate } from '../schemas/rule_schemas'; +import { RuleTypeParams } from '../types'; class PatchError extends Error { public readonly statusCode: number; @@ -71,7 +72,7 @@ export const patchRules = async ({ anomalyThreshold, machineLearningJobId, actions, -}: PatchRulesOptions): Promise => { +}: PatchRulesOptions): Promise | null> => { if (rule == null) { return null; } @@ -185,10 +186,13 @@ export const patchRules = async ({ throw new PatchError(`Applying patch would create invalid rule: ${errors}`, 400); } - const update = await alertsClient.update({ + /** + * TODO: Remove this use of `as` by utilizing the proper type + */ + const update = (await alertsClient.update({ id: rule.id, data: validated, - }); + })) as PartialAlert; if (rule.enabled && enabled === false) { await alertsClient.disable({ id: rule.id }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts index e4bb65a907e2d..bc277bd2089a6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts @@ -6,6 +6,7 @@ import { SanitizedAlert } from '../../../../../alerts/common'; import { INTERNAL_RULE_ID_KEY } from '../../../../common/constants'; +import { RuleTypeParams } from '../types'; import { findRules } from './find_rules'; import { isAlertType, ReadRuleOptions } from './types'; @@ -21,7 +22,7 @@ export const readRules = async ({ alertsClient, id, ruleId, -}: ReadRuleOptions): Promise => { +}: ReadRuleOptions): Promise | null> => { if (id != null) { try { const rule = await alertsClient.get({ id }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index aaeb487ad9a78..34dab20c279b4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -103,9 +103,7 @@ import { SIGNALS_ID } from '../../../../common/constants'; import { RuleTypeParams, PartialFilter } from '../types'; import { ListArrayOrUndefined, ListArray } from '../../../../common/detection_engine/schemas/types'; -export interface RuleAlertType extends Alert { - params: RuleTypeParams; -} +export type RuleAlertType = Alert; // eslint-disable-next-line @typescript-eslint/no-explicit-any export interface IRuleStatusSOAttributes extends Record { @@ -173,11 +171,15 @@ export interface Clients { alertsClient: AlertsClient; } -export const isAlertTypes = (partialAlert: PartialAlert[]): partialAlert is RuleAlertType[] => { +export const isAlertTypes = ( + partialAlert: Array> +): partialAlert is RuleAlertType[] => { return partialAlert.every((rule) => isAlertType(rule)); }; -export const isAlertType = (partialAlert: PartialAlert): partialAlert is RuleAlertType => { +export const isAlertType = ( + partialAlert: PartialAlert +): partialAlert is RuleAlertType => { return partialAlert.alertTypeId === SIGNALS_ID; }; @@ -305,7 +307,7 @@ export interface PatchRulesOptions { version: VersionOrUndefined; exceptionsList: ListArrayOrUndefined; actions: RuleAlertAction[] | undefined; - rule: SanitizedAlert | null; + rule: SanitizedAlert | null; } export interface ReadRuleOptions { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index c63bd01cd1813..b3e0aaae715c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -15,13 +15,14 @@ import { addTags } from './add_tags'; import { ruleStatusSavedObjectsClientFactory } from '../signals/rule_status_saved_objects_client'; import { typeSpecificSnakeToCamel } from '../schemas/rule_converters'; import { InternalRuleUpdate } from '../schemas/rule_schemas'; +import { RuleTypeParams } from '../types'; export const updateRules = async ({ alertsClient, savedObjectsClient, defaultOutputIndex, ruleUpdate, -}: UpdateRulesOptions): Promise => { +}: UpdateRulesOptions): Promise | null> => { const existingRule = await readRules({ alertsClient, ruleId: ruleUpdate.rule_id, @@ -77,10 +78,13 @@ export const updateRules = async ({ notifyWhen: null, }; - const update = await alertsClient.update({ + /** + * TODO: Remove this use of `as` by utilizing the proper type + */ + const update = (await alertsClient.update({ id: existingRule.id, data: newInternalRule, - }); + })) as PartialAlert; if (existingRule.enabled && enabled === false) { await alertsClient.disable({ id: existingRule.id }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts index 50e740e81830f..aede91c5af143 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts @@ -8,7 +8,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; -const signalSchema = schema.object({ +export const signalSchema = schema.object({ anomalyThreshold: schema.maybe(schema.number()), author: schema.arrayOf(schema.string(), { defaultValue: [] }), buildingBlockType: schema.nullable(schema.string()), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 9a40573095a1a..9c2ea0945297e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -49,7 +49,10 @@ jest.mock('./find_ml_signals'); jest.mock('./bulk_create_ml_signals'); jest.mock('../../../../common/detection_engine/parse_schedule_dates'); -const getPayload = (ruleAlert: RuleAlertType, services: AlertServicesMock) => ({ +const getPayload = ( + ruleAlert: RuleAlertType, + services: AlertServicesMock +): RuleExecutorOptions => ({ alertId: ruleAlert.id, services, params: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 3928228357d4c..476b9aa56f572 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -66,6 +66,7 @@ import { getIndexVersion } from '../routes/index/get_index_version'; import { MIN_EQL_RULE_INDEX_VERSION } from '../routes/index/get_signals_template'; import { filterEventsAgainstList } from './filters/filter_events_against_list'; import { isOutdated } from '../migrations/helpers'; +import { RuleTypeParams } from '../types'; export const signalRulesAlertType = ({ logger, @@ -86,7 +87,16 @@ export const signalRulesAlertType = ({ actionGroups: siemRuleActionGroups, defaultActionGroupId: 'default', validate: { - params: signalParamsSchema(), + /** + * TODO: Fix typing inconsistancy between `RuleTypeParams` and `CreateRulesOptions` + * Once that's done, you should be able to do: + * ``` + * params: signalParamsSchema(), + * ``` + */ + params: (signalParamsSchema() as unknown) as { + validate: (object: unknown) => RuleTypeParams; + }, }, producer: SERVER_APP_ID, minimumLicenseRequired: 'basic', 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 4167d056df885..62339f50d939c 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 @@ -11,6 +11,8 @@ import { RulesSchema } from '../../../../common/detection_engine/schemas/respons import { AlertType, AlertTypeState, + AlertInstanceState, + AlertInstanceContext, AlertExecutorOptions, AlertServices, } from '../../../../../alerts/server'; @@ -128,19 +130,27 @@ export type BaseSignalHit = BaseHit; export type EqlSignalSearchResponse = EqlSearchResponse; -export type RuleExecutorOptions = Omit & { - params: RuleTypeParams; -}; +export type RuleExecutorOptions = AlertExecutorOptions< + RuleTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext +>; // This returns true because by default a RuleAlertTypeDefinition is an AlertType // since we are only increasing the strictness of params. -export const isAlertExecutor = (obj: SignalRuleAlertTypeDefinition): obj is AlertType => { +export const isAlertExecutor = ( + obj: SignalRuleAlertTypeDefinition +): obj is AlertType => { return true; }; -export type SignalRuleAlertTypeDefinition = Omit & { - executor: ({ services, params, state }: RuleExecutorOptions) => Promise; -}; +export type SignalRuleAlertTypeDefinition = AlertType< + RuleTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext +>; export interface Ancestor { rule?: string; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts index 57535178c5280..90f2ce638855b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts @@ -51,10 +51,11 @@ import { import { LegacyCallAPIOptions } from '../../../../../../src/core/server'; import { Filter } from '../../../../../../src/plugins/data/server'; import { ListArrayOrUndefined } from '../../../common/detection_engine/schemas/types'; +import { AlertTypeParams } from '../../../../alerts/common'; export type PartialFilter = Partial; -export interface RuleTypeParams { +export interface RuleTypeParams extends AlertTypeParams { anomalyThreshold: AnomalyThresholdOrUndefined; author: AuthorOrUndefined; buildingBlockType: BuildingBlockTypeOrUndefined; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts index 89252f7c90104..d1f64c9298f15 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AlertTypeParams } from '../../../../alerts/common'; import { Query } from '../../../../../../src/plugins/data/common'; -export interface GeoContainmentAlertParams { +export interface GeoContainmentAlertParams extends AlertTypeParams { index: string; indexId: string; geoField: string; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts index 5ac9c7fd29317..3f487135f0474 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AlertTypeParams } from '../../../../alerts/common'; import { Query } from '../../../../../../src/plugins/data/common'; export enum TrackingEvent { @@ -12,7 +13,7 @@ export enum TrackingEvent { crossed = 'crossed', } -export interface GeoThresholdAlertParams { +export interface GeoThresholdAlertParams extends AlertTypeParams { index: string; indexId: string; geoField: string; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx index 3c84f2a5d4f9c..12e021958f497 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx @@ -71,6 +71,10 @@ interface KibanaDeps { http: HttpSetup; } +function isString(value: unknown): value is string { + return typeof value === 'string'; +} + export const IndexThresholdAlertTypeExpression: React.FunctionComponent< AlertTypeParamsExpressionProps > = ({ alertParams, alertInterval, setAlertParams, setAlertProperty, errors, charts, data }) => { @@ -190,10 +194,10 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent< }; })} onChange={async (selected: EuiComboBoxOptionOption[]) => { - setAlertParams( - 'index', - selected.map((aSelected) => aSelected.value) - ); + const indicies: string[] = selected + .map((aSelected) => aSelected.value) + .filter(isString); + setAlertParams('index', indicies); const indices = selected.map((s) => s.value as string); // reset time field and expression fields if indices are deleted diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts index 356b0fbbc0845..4868b92feaeb9 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AlertTypeParams } from '../../../../alerts/common'; + export interface Comparator { text: string; value: string; @@ -24,7 +26,7 @@ export interface GroupByType { validNormalizedTypes: string[]; } -export interface IndexThresholdAlertParams { +export interface IndexThresholdAlertParams extends AlertTypeParams { index: string[]; timeField?: string; aggType: string; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts index 51d7361bfe762..85dcf1125becd 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts @@ -9,7 +9,13 @@ import { schema } from '@kbn/config-schema'; import { Logger } from 'src/core/server'; import { STACK_ALERTS_FEATURE_ID } from '../../../common'; import { getGeoContainmentExecutor } from './geo_containment'; -import { AlertType } from '../../../../alerts/server'; +import { + AlertType, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, + AlertTypeParams, +} from '../../../../alerts/server'; import { Query } from '../../../../../../src/plugins/data/common/query'; export const GEO_CONTAINMENT_ID = '.geo-containment'; @@ -96,7 +102,7 @@ export const ParamsSchema = schema.object({ boundaryIndexQuery: schema.maybe(schema.any({})), }); -export interface GeoContainmentParams { +export interface GeoContainmentParams extends AlertTypeParams { index: string; indexId: string; geoField: string; @@ -111,8 +117,34 @@ export interface GeoContainmentParams { indexQuery?: Query; boundaryIndexQuery?: Query; } +export interface GeoContainmentState extends AlertTypeState { + shapesFilters: Record; + shapesIdsNamesMap: Record; +} +export interface GeoContainmentInstanceState extends AlertInstanceState { + location: number[]; + shapeLocationId: string; + dateInShape: string | null; + docId: string; +} +export interface GeoContainmentInstanceContext extends AlertInstanceContext { + entityId: string; + entityDateTime: string | null; + entityDocumentId: string; + detectionDateTime: string; + entityLocation: string; + containingBoundaryId: string; + containingBoundaryName: unknown; +} + +export type GeoContainmentAlertType = AlertType< + GeoContainmentParams, + GeoContainmentState, + GeoContainmentInstanceState, + GeoContainmentInstanceContext +>; -export function getAlertType(logger: Logger): AlertType { +export function getAlertType(logger: Logger): GeoContainmentAlertType { const alertTypeName = i18n.translate('xpack.stackAlerts.geoContainment.alertTypeTitle', { defaultMessage: 'Tracking containment', }); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts index ed951f340f8ed..612eff3014985 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts @@ -8,15 +8,16 @@ import _ from 'lodash'; import { SearchResponse } from 'elasticsearch'; import { Logger } from 'src/core/server'; import { executeEsQueryFactory, getShapesFilters, OTHER_CATEGORY } from './es_query_builder'; -import { AlertServices, AlertTypeState } from '../../../../alerts/server'; -import { ActionGroupId, GEO_CONTAINMENT_ID, GeoContainmentParams } from './alert_type'; +import { AlertServices } from '../../../../alerts/server'; +import { + ActionGroupId, + GEO_CONTAINMENT_ID, + GeoContainmentInstanceState, + GeoContainmentAlertType, + GeoContainmentInstanceContext, +} from './alert_type'; -export interface LatestEntityLocation { - location: number[]; - shapeLocationId: string; - dateInShape: string | null; - docId: string; -} +export type LatestEntityLocation = GeoContainmentInstanceState; // Flatten agg results and get latest locations for each entity export function transformResults( @@ -97,9 +98,10 @@ function getOffsetTime(delayOffsetWithUnits: string, oldTime: Date): Date { export function getActiveEntriesAndGenerateAlerts( prevLocationMap: Record, currLocationMap: Map, - alertInstanceFactory: ( - x: string - ) => { scheduleActions: (x: string, y: Record) => void }, + alertInstanceFactory: AlertServices< + GeoContainmentInstanceState, + GeoContainmentInstanceContext + >['alertInstanceFactory'], shapesIdsNamesMap: Record, currIntervalEndTime: Date ) { @@ -127,23 +129,8 @@ export function getActiveEntriesAndGenerateAlerts( }); return allActiveEntriesMap; } - -export const getGeoContainmentExecutor = (log: Logger) => - async function ({ - previousStartedAt, - startedAt, - services, - params, - alertId, - state, - }: { - previousStartedAt: Date | null; - startedAt: Date; - services: AlertServices; - params: GeoContainmentParams; - alertId: string; - state: AlertTypeState; - }): Promise { +export const getGeoContainmentExecutor = (log: Logger): GeoContainmentAlertType['executor'] => + async function ({ previousStartedAt, startedAt, services, params, alertId, state }) { const { shapesFilters, shapesIdsNamesMap } = state.shapesFilters ? state : await getShapesFilters( diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/index.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/index.ts index 02116d0701bfa..86e9e4fa3d672 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/index.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/index.ts @@ -6,7 +6,13 @@ import { Logger } from 'src/core/server'; import { AlertingSetup } from '../../types'; -import { GeoContainmentParams, getAlertType } from './alert_type'; +import { + GeoContainmentParams, + GeoContainmentState, + GeoContainmentInstanceState, + GeoContainmentInstanceContext, + getAlertType, +} from './alert_type'; interface RegisterParams { logger: Logger; @@ -15,5 +21,10 @@ interface RegisterParams { export function register(params: RegisterParams) { const { logger, alerts } = params; - alerts.registerType(getAlertType(logger)); + alerts.registerType< + GeoContainmentParams, + GeoContainmentState, + GeoContainmentInstanceState, + GeoContainmentInstanceContext + >(getAlertType(logger)); } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts index 885081e859dd7..26b51060c2e73 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts @@ -10,6 +10,8 @@ import sampleJsonResponseWithNesting from './es_sample_response_with_nesting.jso import { getActiveEntriesAndGenerateAlerts, transformResults } from '../geo_containment'; import { SearchResponse } from 'elasticsearch'; import { OTHER_CATEGORY } from '../es_query_builder'; +import { alertsMock } from '../../../../../alerts/server/mocks'; +import { GeoContainmentInstanceContext, GeoContainmentInstanceState } from '../alert_type'; describe('geo_containment', () => { describe('transformResults', () => { @@ -191,8 +193,12 @@ describe('geo_containment', () => { const emptyShapesIdsNamesMap = {}; const alertInstanceFactory = (instanceId: string) => { - return { - scheduleActions: (actionGroupId: string, context: Record) => { + const alertInstance = alertsMock.createAlertInstanceFactory< + GeoContainmentInstanceState, + GeoContainmentInstanceContext + >(); + alertInstance.scheduleActions.mockImplementation( + (actionGroupId: string, context?: GeoContainmentInstanceContext) => { const contextKeys = Object.keys(expectedContext[0].context); const contextSubset = _.pickBy(context, (v, k) => contextKeys.includes(k)); testAlertActionArr.push({ @@ -200,9 +206,12 @@ describe('geo_containment', () => { instanceId, context: contextSubset, }); - }, - }; + return alertInstance; + } + ); + return alertInstance; }; + const currentDateTime = new Date(); it('should use currently active entities if no older entity entries', () => { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts index bf5e2fe2289db..2eccf2ff96fb0 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts @@ -9,7 +9,13 @@ import { schema } from '@kbn/config-schema'; import { Logger } from 'src/core/server'; import { STACK_ALERTS_FEATURE_ID } from '../../../common'; import { getGeoThresholdExecutor } from './geo_threshold'; -import { AlertType } from '../../../../alerts/server'; +import { + AlertType, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, + AlertTypeParams, +} from '../../../../alerts/server'; import { Query } from '../../../../../../src/plugins/data/common/query'; export const GEO_THRESHOLD_ID = '.geo-threshold'; @@ -155,7 +161,7 @@ export const ParamsSchema = schema.object({ boundaryIndexQuery: schema.maybe(schema.any({})), }); -export interface GeoThresholdParams { +export interface GeoThresholdParams extends AlertTypeParams { index: string; indexId: string; geoField: string; @@ -171,8 +177,41 @@ export interface GeoThresholdParams { indexQuery?: Query; boundaryIndexQuery?: Query; } +export interface GeoThresholdState extends AlertTypeState { + shapesFilters: Record; + shapesIdsNamesMap: Record; + prevLocationArr: GeoThresholdInstanceState[]; +} +export interface GeoThresholdInstanceState extends AlertInstanceState { + location: number[]; + shapeLocationId: string; + entityName: string; + dateInShape: string | null; + docId: string; +} +export interface GeoThresholdInstanceContext extends AlertInstanceContext { + entityId: string; + timeOfDetection: number; + crossingLine: string; + toEntityLocation: string; + toEntityDateTime: string | null; + toEntityDocumentId: string; + toBoundaryId: string; + toBoundaryName: unknown; + fromEntityLocation: string; + fromEntityDateTime: string | null; + fromEntityDocumentId: string; + fromBoundaryId: string; + fromBoundaryName: unknown; +} -export function getAlertType(logger: Logger): AlertType { +export type GeoThresholdAlertType = AlertType< + GeoThresholdParams, + GeoThresholdState, + GeoThresholdInstanceState, + GeoThresholdInstanceContext +>; +export function getAlertType(logger: Logger): GeoThresholdAlertType { const alertTypeName = i18n.translate('xpack.stackAlerts.geoThreshold.alertTypeTitle', { defaultMessage: 'Tracking threshold', }); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts index 5cb4156e84623..a2375537ae6e5 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts @@ -8,16 +8,14 @@ import _ from 'lodash'; import { SearchResponse } from 'elasticsearch'; import { Logger } from 'src/core/server'; import { executeEsQueryFactory, getShapesFilters, OTHER_CATEGORY } from './es_query_builder'; -import { AlertServices, AlertTypeState } from '../../../../alerts/server'; -import { ActionGroupId, GEO_THRESHOLD_ID, GeoThresholdParams } from './alert_type'; +import { + ActionGroupId, + GEO_THRESHOLD_ID, + GeoThresholdAlertType, + GeoThresholdInstanceState, +} from './alert_type'; -interface LatestEntityLocation { - location: number[]; - shapeLocationId: string; - entityName: string; - dateInShape: string | null; - docId: string; -} +export type LatestEntityLocation = GeoThresholdInstanceState; // Flatten agg results and get latest locations for each entity export function transformResults( @@ -172,22 +170,8 @@ function getOffsetTime(delayOffsetWithUnits: string, oldTime: Date): Date { return adjustedDate; } -export const getGeoThresholdExecutor = (log: Logger) => - async function ({ - previousStartedAt, - startedAt, - services, - params, - alertId, - state, - }: { - previousStartedAt: Date | null; - startedAt: Date; - services: AlertServices; - params: GeoThresholdParams; - alertId: string; - state: AlertTypeState; - }): Promise { +export const getGeoThresholdExecutor = (log: Logger): GeoThresholdAlertType['executor'] => + async function ({ previousStartedAt, startedAt, services, params, alertId, state }) { const { shapesFilters, shapesIdsNamesMap } = state.shapesFilters ? state : await getShapesFilters( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index 538c6be89ab4b..ea654bb21e88b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Alert, AlertType } from '../../types'; +import { Alert, AlertType, AlertUpdates } from '../../types'; import { httpServiceMock } from '../../../../../../src/core/public/mocks'; import { createAlert, @@ -538,7 +538,7 @@ describe('deleteAlerts', () => { describe('createAlert', () => { test('should call create alert API', async () => { - const alertToCreate = { + const alertToCreate: AlertUpdates = { name: 'test', consumer: 'alerts', tags: ['foo'], @@ -553,10 +553,13 @@ describe('createAlert', () => { notifyWhen: 'onActionGroupChange' as AlertNotifyWhenType, createdAt: new Date('1970-01-01T00:00:00.000Z'), updatedAt: new Date('1970-01-01T00:00:00.000Z'), - apiKey: null, apiKeyOwner: null, + createdBy: null, + updatedBy: null, + muteAll: false, + mutedInstanceIds: [], }; - const resolvedValue: Alert = { + const resolvedValue = { ...alertToCreate, id: '123', createdBy: null, @@ -576,7 +579,7 @@ describe('createAlert', () => { Array [ "/api/alerts/alert", Object { - "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"notifyWhen\\":\\"onActionGroupChange\\",\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", + "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"notifyWhen\\":\\"onActionGroupChange\\",\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKeyOwner\\":null,\\"createdBy\\":null,\\"updatedBy\\":null,\\"muteAll\\":false,\\"mutedInstanceIds\\":[]}", }, ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index f2c8957400fa5..05bfba27420c7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -35,12 +35,12 @@ import { AlertEditProps } from './application/sections/alert_form/alert_edit'; export interface TriggersAndActionsUIPublicPluginSetup { actionTypeRegistry: TypeRegistry; - alertTypeRegistry: TypeRegistry; + alertTypeRegistry: TypeRegistry>; } export interface TriggersAndActionsUIPublicPluginStart { actionTypeRegistry: TypeRegistry; - alertTypeRegistry: TypeRegistry; + alertTypeRegistry: TypeRegistry>; getAddConnectorFlyout: ( props: Omit ) => ReactElement; diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 3fffe9fe230b4..43780b324aa56 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -8,12 +8,12 @@ import type { DocLinksStart } from 'kibana/public'; import { ComponentType } from 'react'; import { ChartsPluginSetup } from 'src/plugins/charts/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { ActionGroup, AlertActionParam } from '../../alerts/common'; +import { ActionGroup, AlertActionParam, AlertTypeParams } from '../../alerts/common'; import { ActionType } from '../../actions/common'; import { TypeRegistry } from './application/type_registry'; import { AlertType as CommonAlertType } from '../../alerts/common'; import { - SanitizedAlert as Alert, + SanitizedAlert, AlertAction, AlertAggregations, AlertTaskState, @@ -23,6 +23,11 @@ import { AlertingFrameworkHealth, AlertNotifyWhenType, } from '../../alerts/common'; + +// In Triggers and Actions we treat all `Alert`s as `SanitizedAlert` +// so the `Params` is a black-box of Record +type Alert = SanitizedAlert; + export { Alert, AlertAction, @@ -169,14 +174,17 @@ export interface AlertTableItem extends Alert { } export interface AlertTypeParamsExpressionProps< - AlertParamsType = unknown, + Params extends AlertTypeParams = AlertTypeParams, MetaData = Record > { - alertParams: AlertParamsType; + alertParams: Params; alertInterval: string; alertThrottle: string; - setAlertParams: (property: string, value: any) => void; - setAlertProperty: (key: Key, value: Alert[Key] | null) => void; + setAlertParams: (property: Key, value: Params[Key] | undefined) => void; + setAlertProperty: ( + key: Prop, + value: SanitizedAlert[Prop] | null + ) => void; errors: IErrorObject; defaultActionGroupId: string; actionGroups: ActionGroup[]; @@ -185,15 +193,15 @@ export interface AlertTypeParamsExpressionProps< data: DataPublicPluginStart; } -export interface AlertTypeModel { +export interface AlertTypeModel { id: string; description: string; iconClass: string; documentationUrl: string | ((docLinks: DocLinksStart) => string) | null; - validate: (alertParams: AlertParamsType) => ValidationResult; + validate: (alertParams: Params) => ValidationResult; alertParamsExpression: | React.FunctionComponent - | React.LazyExoticComponent>>; + | React.LazyExoticComponent>>; requiresAppContext: boolean; defaultActionMessage?: string; } diff --git a/x-pack/plugins/uptime/public/state/actions/types.ts b/x-pack/plugins/uptime/public/state/actions/types.ts index 8d87ae5d52cb2..322f1eb0136b4 100644 --- a/x-pack/plugins/uptime/public/state/actions/types.ts +++ b/x-pack/plugins/uptime/public/state/actions/types.ts @@ -6,7 +6,8 @@ import { Action } from 'redux-actions'; import { IHttpFetchError } from 'src/core/public'; -import { Alert } from '../../../../triggers_actions_ui/public'; +import { Alert } from '../../../../alerts/common'; +import { UptimeAlertTypeParams } from '../alerts/alerts'; export interface AsyncAction { get: (payload: Payload) => Action; @@ -59,5 +60,5 @@ export interface AlertsResult { page: number; perPage: number; total: number; - data: Alert[]; + data: Array>; } diff --git a/x-pack/plugins/uptime/public/state/alerts/alerts.ts b/x-pack/plugins/uptime/public/state/alerts/alerts.ts index aeb81bb413aa7..43186e0ef5422 100644 --- a/x-pack/plugins/uptime/public/state/alerts/alerts.ts +++ b/x-pack/plugins/uptime/public/state/alerts/alerts.ts @@ -20,10 +20,8 @@ import { fetchMonitorAlertRecords, NewAlertParams, } from '../api/alerts'; -import { - ActionConnector as RawActionConnector, - Alert, -} from '../../../../triggers_actions_ui/public'; +import { ActionConnector as RawActionConnector } from '../../../../triggers_actions_ui/public'; +import { Alert } from '../../../../alerts/common'; import { kibanaService } from '../kibana_service'; import { monitorIdSelector } from '../selectors'; import { AlertsResult, MonitorIdParam } from '../actions/types'; @@ -31,13 +29,22 @@ import { simpleAlertEnabled } from '../../lib/alert_types/alert_messages'; export type ActionConnector = Omit; -export const createAlertAction = createAsyncAction('CREATE ALERT'); +/** + * TODO: Use actual AlertType Params type that's specific to Uptime instead of `any` + */ +export type UptimeAlertTypeParams = Record; + +export const createAlertAction = createAsyncAction< + NewAlertParams, + Alert | null +>('CREATE ALERT'); export const getConnectorsAction = createAsyncAction<{}, ActionConnector[]>('GET CONNECTORS'); export const getMonitorAlertsAction = createAsyncAction<{}, AlertsResult | null>('GET ALERTS'); -export const getAnomalyAlertAction = createAsyncAction( - 'GET EXISTING ALERTS' -); +export const getAnomalyAlertAction = createAsyncAction< + MonitorIdParam, + Alert +>('GET EXISTING ALERTS'); export const deleteAlertAction = createAsyncAction<{ alertId: string }, string | null>( 'DELETE ALERTS' ); @@ -47,9 +54,9 @@ export const deleteAnomalyAlertAction = createAsyncAction<{ alertId: string }, a interface AlertState { connectors: AsyncInitState; - newAlert: AsyncInitState; + newAlert: AsyncInitState>; alerts: AsyncInitState; - anomalyAlert: AsyncInitState; + anomalyAlert: AsyncInitState>; alertDeletion: AsyncInitState; anomalyAlertDeletion: AsyncInitState; } diff --git a/x-pack/plugins/uptime/public/state/api/alerts.ts b/x-pack/plugins/uptime/public/state/api/alerts.ts index da86da12a70ca..9d4dd3a1253c3 100644 --- a/x-pack/plugins/uptime/public/state/api/alerts.ts +++ b/x-pack/plugins/uptime/public/state/api/alerts.ts @@ -9,9 +9,10 @@ import { apiService } from './utils'; import { ActionConnector } from '../alerts/alerts'; import { AlertsResult, MonitorIdParam } from '../actions/types'; -import { Alert, AlertAction } from '../../../../triggers_actions_ui/public'; +import { AlertAction } from '../../../../triggers_actions_ui/public'; import { API_URLS } from '../../../common/constants'; import { MonitorStatusTranslations } from '../../../common/translations'; +import { Alert, AlertTypeParams } from '../../../../alerts/common'; const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS; @@ -21,7 +22,7 @@ export const fetchConnectors = async () => { return await apiService.get(API_URLS.ALERT_ACTIONS); }; -export interface NewAlertParams { +export interface NewAlertParams extends AlertTypeParams { monitorId: string; monitorName?: string; defaultActions: ActionConnector[]; @@ -80,7 +81,9 @@ export const fetchMonitorAlertRecords = async (): Promise => { return await apiService.get(API_URLS.ALERTS_FIND, data); }; -export const fetchAlertRecords = async ({ monitorId }: MonitorIdParam): Promise => { +export const fetchAlertRecords = async ({ + monitorId, +}: MonitorIdParam): Promise> => { const data = { page: 1, per_page: 500, @@ -90,7 +93,7 @@ export const fetchAlertRecords = async ({ monitorId }: MonitorIdParam): Promise< sort_order: 'asc', }; const alerts = await apiService.get(API_URLS.ALERTS_FIND, data); - return alerts.data.find((alert: Alert) => alert.params.monitorId === monitorId); + return alerts.data.find((alert: Alert) => alert.params.monitorId === monitorId); }; export const disableAlertById = async ({ alertId }: { alertId: string }) => { diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 4f9fefa4188e5..d5fd92476dd16 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -11,7 +11,13 @@ import { getStatusMessage, getUniqueIdsByLoc, } from '../status_check'; -import { AlertType } from '../../../../../alerts/server'; +import { + AlertType, + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, +} from '../../../../../alerts/server'; import { IRouter } from 'kibana/server'; import { UMServerLibs } from '../../lib'; import { UptimeCorePlugins, UptimeCoreSetup } from '../../adapters'; @@ -868,7 +874,7 @@ describe('status check alert', () => { }); describe('alert factory', () => { - let alert: AlertType; + let alert: AlertType; beforeEach(() => { const { server, libs, plugins } = bootstrapDependencies(); diff --git a/x-pack/plugins/uptime/server/lib/alerts/types.ts b/x-pack/plugins/uptime/server/lib/alerts/types.ts index 0a80b36046860..d143e33fb8e96 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/types.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/types.ts @@ -6,10 +6,17 @@ import { UptimeCorePlugins, UptimeCoreSetup } from '../adapters'; import { UMServerLibs } from '../lib'; -import { AlertType } from '../../../../alerts/server'; +import { AlertType, AlertInstanceState, AlertInstanceContext } from '../../../../alerts/server'; +export type UptimeAlertTypeParam = Record; +export type UptimeAlertTypeState = Record; export type UptimeAlertTypeFactory = ( server: UptimeCoreSetup, libs: UMServerLibs, plugins: UptimeCorePlugins -) => AlertType; +) => AlertType< + UptimeAlertTypeParam, + UptimeAlertTypeState, + AlertInstanceState, + AlertInstanceContext +>; 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 965287ffbde8e..a4a2f2c64db1b 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,28 +5,46 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AlertExecutorOptions, AlertType, AlertTypeState } from '../../../../alerts/server'; +import { + AlertExecutorOptions, + AlertInstanceState, + AlertInstanceContext, +} from '../../../../alerts/server'; import { savedObjectsAdapter } from '../saved_objects'; import { DynamicSettings } from '../../../common/runtime_types'; import { createUptimeESClient, UptimeESClient } from '../lib'; +import { UptimeAlertTypeFactory, UptimeAlertTypeParam, UptimeAlertTypeState } from './types'; -export interface UptimeAlertType extends Omit { +export interface UptimeAlertType + extends Omit, 'executor' | 'producer'> { executor: ({ options, uptimeEsClient, dynamicSettings, }: { - options: AlertExecutorOptions; + options: AlertExecutorOptions< + UptimeAlertTypeParam, + UptimeAlertTypeState, + AlertInstanceState, + AlertInstanceContext + >; uptimeEsClient: UptimeESClient; dynamicSettings: DynamicSettings; savedObjectsClient: SavedObjectsClientContract; - }) => Promise; + }) => Promise; } export const uptimeAlertWrapper = (uptimeAlert: UptimeAlertType) => ({ ...uptimeAlert, producer: 'uptime', - executor: async (options: AlertExecutorOptions) => { + executor: async ( + options: AlertExecutorOptions< + UptimeAlertTypeParam, + UptimeAlertTypeState, + AlertInstanceState, + AlertInstanceContext + > + ) => { const { services: { scopedClusterClient: esClient, savedObjectsClient }, } = options; 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 b4ee273e57d61..30c19f735b75d 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 @@ -13,6 +13,8 @@ import { AlertType, AlertInstanceState, AlertInstanceContext, + AlertTypeState, + AlertTypeParams, } from '../../../../../../../plugins/alerts/server'; export const EscapableStrings = { @@ -50,7 +52,7 @@ function getAlwaysFiringAlertType() { groupsToScheduleActionsInSeries: schema.maybe(schema.arrayOf(schema.nullable(schema.string()))), }); type ParamsType = TypeOf; - interface State { + interface State extends AlertTypeState { groupInSeriesIndex?: number; } interface InstanceState extends AlertInstanceState { @@ -59,7 +61,7 @@ function getAlwaysFiringAlertType() { interface InstanceContext extends AlertInstanceContext { instanceContextValue: boolean; } - const result: AlertType = { + const result: AlertType = { id: 'test.always-firing', name: 'Test: Always Firing', actionGroups: [ @@ -141,7 +143,7 @@ async function alwaysFiringExecutor(alertExecutorOptions: any) { } function getCumulativeFiringAlertType() { - interface State { + interface State extends AlertTypeState { runCount?: number; } interface InstanceState extends AlertInstanceState { @@ -175,7 +177,7 @@ function getCumulativeFiringAlertType() { }; }, }; - return result as AlertType; + return result; } function getNeverFiringAlertType() { @@ -184,7 +186,7 @@ function getNeverFiringAlertType() { reference: schema.string(), }); type ParamsType = TypeOf; - interface State { + interface State extends AlertTypeState { globalStateValue: boolean; } const result: AlertType = { @@ -385,7 +387,7 @@ function getPatternFiringAlertType() { reference: schema.maybe(schema.string()), }); type ParamsType = TypeOf; - interface State { + interface State extends AlertTypeState { patternIndex?: number; } const result: AlertType = { diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts index f6cbc52e7a421..cf09286fe1ba6 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts @@ -27,7 +27,14 @@ export const noopAlertType: AlertType = { producer: 'alerts', }; -export const alwaysFiringAlertType: AlertType = { +export const alwaysFiringAlertType: AlertType< + { instances: Array<{ id: string; state: any }> }, + { + globalStateValue: boolean; + groupInSeriesIndex: number; + }, + { instanceStateValue: boolean; globalStateValue: boolean; groupInSeriesIndex: number } +> = { id: 'test.always-firing', name: 'Always Firing', actionGroups: [ @@ -37,7 +44,7 @@ export const alwaysFiringAlertType: AlertType = { defaultActionGroupId: 'default', producer: 'alerts', minimumLicenseRequired: 'basic', - async executor(alertExecutorOptions: any) { + async executor(alertExecutorOptions) { const { services, state, params } = alertExecutorOptions; (params.instances || []).forEach((instance: { id: string; state: any }) => {