diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts index f056c292b018f3..974fb8bf35ae0a 100644 --- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts +++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts @@ -39,6 +39,7 @@ function getTShirtSizeByIdAndThreshold( export const alertType: AlertType< AlwaysFiringParams, + never, { count?: number }, { triggerdOnCycle: number }, never, 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 8f9a2935183002..93bdeb2eada9cc 100644 --- a/x-pack/examples/alerting_example/server/alert_types/astros.ts +++ b/x-pack/examples/alerting_example/server/alert_types/astros.ts @@ -41,6 +41,7 @@ function getCraftFilter(craft: string) { export const alertType: AlertType< { outerSpaceCapacity: number; craft: string; op: string }, + never, { peopleInSpace: number }, { craft: string }, never, diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerting/README.md index 62d2f2b57b8e87..215ff9164c1a72 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerting/README.md @@ -118,6 +118,8 @@ The following table describes the properties of the `options` object. |executor|This is where the code for the rule type lives. This is a function to be called when executing a rule on an interval basis. For full details, see the executor section below.|Function| |producer|The id of the application producing this rule type.|string| |minimumLicenseRequired|The value of a minimum license. Most of the rules are licensed as "basic".|string| +|useSavedObjectReferences.extractReferences|(Optional) When developing a rule type, you can choose to implement hooks for extracting saved object references from rule parameters. This hook will be invoked when a rule is created or updated. Implementing this hook is optional, but if an extract hook is implemented, an inject hook must also be implemented.|Function +|useSavedObjectReferences.injectReferences|(Optional) When developing a rule type, you can choose to implement hooks for injecting saved object references into rule parameters. This hook will be invoked when a rule is retrieved (get or find). Implementing this hook is optional, but if an inject hook is implemented, an extract hook must also be implemented.|Function |isExportable|Whether the rule type is exportable from the Saved Objects Management UI.|boolean| ### Executor @@ -173,6 +175,19 @@ For example, if the `context` has one variable `foo` which is an object that has } ``` +### useSavedObjectReferences Hooks + +This is an optional pair of functions that can be implemented by a rule type. Both `extractReferences` and `injectReferences` functions must be implemented if either is impemented. + +**useSavedObjectReferences.extractReferences** + +This function should take the rule type params as input and extract out any saved object IDs stored within the params. For each saved object ID, a new saved object reference should be created and a saved object reference should replace the saved object ID in the rule params. This function should return the modified rule type params (with saved object reference name, not IDs) and an array of saved object references. + + +**useSavedObjectReferences.injectReferences** + + +This function should take the rule type params (with saved object references) and the saved object references array as input and inject the saved object ID in place of any saved object references in the rule type params. Note that any error thrown within this function will be propagated. ## Licensing Currently most rule types are free features. But some rule types are subscription features, such as the tracking containment rule. @@ -210,6 +225,13 @@ import { interface MyRuleTypeParams extends AlertTypeParams { server: string; threshold: number; + testSavedObjectId: string; +} + +interface MyRuleTypeExtractedParams extends AlertTypeParams { + server: string; + threshold: number; + testSavedObjectRef: string; } interface MyRuleTypeState extends AlertTypeState { @@ -229,6 +251,7 @@ type MyRuleTypeActionGroups = 'default' | 'warning'; const myRuleType: AlertType< MyRuleTypeParams, + MyRuleTypeExtractedParams, MyRuleTypeState, MyRuleTypeAlertState, MyRuleTypeAlertContext, @@ -274,6 +297,7 @@ const myRuleType: AlertType< rule, }: AlertExecutorOptions< MyRuleTypeParams, + MyRuleTypeExtractedParams, MyRuleTypeState, MyRuleTypeAlertState, MyRuleTypeAlertContext, @@ -320,6 +344,29 @@ const myRuleType: AlertType< }; }, producer: 'alerting', + useSavedObjectReferences: { + extractReferences: (params: Params): RuleParamsAndRefs => { + const { testSavedObjectId, ...otherParams } = params; + + const testSavedObjectRef = 'testRef_0'; + const references = [ + { + name: `testRef_0`, + id: testSavedObjectId, + type: 'index-pattern', + }, + ]; + return { params: { ...otherParams, testSavedObjectRef }, references }; + }, + injectReferences: (params: SavedObjectAttributes, references: SavedObjectReference[]) => { + const { testSavedObjectRef, ...otherParams } = params; + const reference = references.find((ref) => ref.name === testSavedObjectRef); + if (!reference) { + throw new Error(`Test reference "${testSavedObjectRef}"`); + } + return { ...otherParams, testSavedObjectId: reference.id } as Params; + }, + } }; server.newPlatform.setup.plugins.alerting.registerType(myRuleType); diff --git a/x-pack/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/plugins/alerting/server/alert_type_registry.test.ts index 63e381bc66c0ae..835c98b9e03c50 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.test.ts @@ -57,7 +57,7 @@ describe('has()', () => { describe('register()', () => { test('throws if AlertType Id contains invalid characters', () => { - const alertType: AlertType = { + const alertType: AlertType = { id: 'test', name: 'Test', actionGroups: [ @@ -90,7 +90,7 @@ describe('register()', () => { }); test('throws if AlertType Id isnt a string', () => { - const alertType: AlertType = { + const alertType: AlertType = { id: (123 as unknown) as string, name: 'Test', actionGroups: [ @@ -113,7 +113,7 @@ describe('register()', () => { }); test('throws if AlertType action groups contains reserved group id', () => { - const alertType: AlertType = { + const alertType: AlertType = { id: 'test', name: 'Test', actionGroups: [ @@ -146,7 +146,7 @@ describe('register()', () => { }); test('allows an AlertType to specify a custom recovery group', () => { - const alertType: AlertType = { + const alertType: AlertType = { id: 'test', name: 'Test', actionGroups: [ @@ -187,6 +187,7 @@ describe('register()', () => { never, never, never, + never, 'default' | 'backToAwesome', 'backToAwesome' > = { @@ -222,7 +223,7 @@ describe('register()', () => { }); test('registers the executor with the task manager', () => { - const alertType: AlertType = { + const alertType: AlertType = { id: 'test', name: 'Test', actionGroups: [ @@ -253,7 +254,7 @@ describe('register()', () => { }); test('shallow clones the given alert type', () => { - const alertType: AlertType = { + const alertType: AlertType = { id: 'test', name: 'Test', actionGroups: [ @@ -506,8 +507,8 @@ function alertTypeWithVariables( id: ActionGroupIds, context: string, state: string -): AlertType { - const baseAlert: AlertType = { +): AlertType { + const baseAlert: AlertType = { id, name: `${id}-name`, actionGroups: [], diff --git a/x-pack/plugins/alerting/server/alert_type_registry.ts b/x-pack/plugins/alerting/server/alert_type_registry.ts index 64fca58c25e66e..f77dd3f7e46eca 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.ts @@ -74,6 +74,7 @@ const alertIdSchema = schema.string({ export type NormalizedAlertType< Params extends AlertTypeParams, + ExtractedParams extends AlertTypeParams, State extends AlertTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, @@ -82,13 +83,22 @@ export type NormalizedAlertType< > = { actionGroups: Array>; } & Omit< - AlertType, + AlertType< + Params, + ExtractedParams, + State, + InstanceState, + InstanceContext, + ActionGroupIds, + RecoveryActionGroupId + >, 'recoveryActionGroup' | 'actionGroups' > & Pick< Required< AlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -100,6 +110,7 @@ export type NormalizedAlertType< >; export type UntypedNormalizedAlertType = NormalizedAlertType< + AlertTypeParams, AlertTypeParams, AlertTypeState, AlertInstanceState, @@ -132,6 +143,7 @@ export class AlertTypeRegistry { public register< Params extends AlertTypeParams, + ExtractedParams extends AlertTypeParams, State extends AlertTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, @@ -140,6 +152,7 @@ export class AlertTypeRegistry { >( alertType: AlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -161,6 +174,7 @@ export class AlertTypeRegistry { const normalizedAlertType = augmentActionGroupsWithReserved< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -179,6 +193,7 @@ export class AlertTypeRegistry { createTaskRunner: (context: RunContext) => this.taskRunnerFactory.create< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -198,6 +213,7 @@ export class AlertTypeRegistry { public get< Params extends AlertTypeParams = AlertTypeParams, + ExtractedParams extends AlertTypeParams = AlertTypeParams, State extends AlertTypeState = AlertTypeState, InstanceState extends AlertInstanceState = AlertInstanceState, InstanceContext extends AlertInstanceContext = AlertInstanceContext, @@ -207,6 +223,7 @@ export class AlertTypeRegistry { id: string ): NormalizedAlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -230,6 +247,7 @@ export class AlertTypeRegistry { */ return (this.alertTypes.get(id)! as unknown) as NormalizedAlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -284,6 +302,7 @@ function normalizedActionVariables(actionVariables: AlertType['actionVariables'] function augmentActionGroupsWithReserved< Params extends AlertTypeParams, + ExtractedParams extends AlertTypeParams, State extends AlertTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, @@ -292,6 +311,7 @@ function augmentActionGroupsWithReserved< >( alertType: AlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -300,6 +320,7 @@ function augmentActionGroupsWithReserved< > ): NormalizedAlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts index 53d888967c431f..3b121413e489bd 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts @@ -16,6 +16,7 @@ import { SavedObject, PluginInitializerContext, SavedObjectsUtils, + SavedObjectAttributes, } from '../../../../../src/core/server'; import { esKuery } from '../../../../../src/plugins/data/server'; import { ActionsClient, ActionsAuthorization } from '../../../actions/server'; @@ -183,6 +184,9 @@ export interface GetAlertInstanceSummaryParams { dateStart?: string; } +// NOTE: Changing this prefix will require a migration to update the prefix in all existing `rule` saved objects +const extractedSavedObjectParamReferenceNamePrefix = 'param:'; + const alertingAuthorizationFilterOpts: AlertingAuthorizationFilterOpts = { type: AlertingAuthorizationFilterType.KQL, fieldNames: { ruleTypeId: 'alert.attributes.alertTypeId', consumer: 'alert.attributes.consumer' }, @@ -284,9 +288,14 @@ export class AlertsClient { await this.validateActions(alertType, data.actions); - const createTime = Date.now(); - const { references, actions } = await this.denormalizeActions(data.actions); + // Extract saved object references for this rule + const { references, params: updatedParams, actions } = await this.extractReferences( + alertType, + data.actions, + validatedAlertTypeParams + ); + const createTime = Date.now(); const notifyWhen = getAlertNotifyWhenType(data.notifyWhen, data.throttle); const rawAlert: RawAlert = { @@ -297,7 +306,7 @@ export class AlertsClient { updatedBy: username, createdAt: new Date(createTime).toISOString(), updatedAt: new Date(createTime).toISOString(), - params: validatedAlertTypeParams as RawAlert['params'], + params: updatedParams as RawAlert['params'], muteAll: false, mutedInstanceIds: [], notifyWhen, @@ -357,7 +366,12 @@ export class AlertsClient { }); createdAlert.attributes.scheduledTaskId = scheduledTask.id; } - return this.getAlertFromRaw(createdAlert.id, createdAlert.attributes, references); + return this.getAlertFromRaw( + createdAlert.id, + createdAlert.attributes.alertTypeId, + createdAlert.attributes, + references + ); } public async get({ @@ -389,7 +403,12 @@ export class AlertsClient { savedObject: { type: 'alert', id }, }) ); - return this.getAlertFromRaw(result.id, result.attributes, result.references); + return this.getAlertFromRaw( + result.id, + result.attributes.alertTypeId, + result.attributes, + result.references + ); } public async getAlertState({ id }: { id: string }): Promise { @@ -518,6 +537,7 @@ export class AlertsClient { } return this.getAlertFromRaw( id, + attributes.alertTypeId, fields ? (pick(attributes, fields) as RawAlert) : attributes, references ); @@ -760,7 +780,13 @@ export class AlertsClient { ); await this.validateActions(alertType, data.actions); - const { actions, references } = await this.denormalizeActions(data.actions); + // Extract saved object references for this rule + const { references, params: updatedParams, actions } = await this.extractReferences( + alertType, + data.actions, + validatedAlertTypeParams + ); + const username = await this.getUserName(); let createdAPIKey = null; @@ -780,7 +806,7 @@ export class AlertsClient { ...attributes, ...data, ...apiKeyAttributes, - params: validatedAlertTypeParams as RawAlert['params'], + params: updatedParams as RawAlert['params'], actions, notifyWhen, updatedBy: username, @@ -807,7 +833,12 @@ export class AlertsClient { throw e; } - return this.getPartialAlertFromRaw(id, updatedObject.attributes, updatedObject.references); + return this.getPartialAlertFromRaw( + id, + alertType, + updatedObject.attributes, + updatedObject.references + ); } private apiKeyAsAlertAttributes( @@ -1436,18 +1467,29 @@ export class AlertsClient { private getAlertFromRaw( id: string, + ruleTypeId: string, rawAlert: RawAlert, references: SavedObjectReference[] | undefined ): Alert { + const ruleType = this.alertTypeRegistry.get(ruleTypeId); // 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, ruleType, rawAlert, references) as Alert; } private getPartialAlertFromRaw( id: string, - { createdAt, updatedAt, meta, notifyWhen, scheduledTaskId, ...rawAlert }: Partial, + ruleType: UntypedNormalizedAlertType, + { + createdAt, + updatedAt, + meta, + notifyWhen, + scheduledTaskId, + params, + ...rawAlert + }: Partial, references: SavedObjectReference[] | undefined ): PartialAlert { // Not the prettiest code here, but if we want to use most of the @@ -1460,6 +1502,7 @@ export class AlertsClient { }; delete rawAlertWithoutExecutionStatus.executionStatus; const executionStatus = alertExecutionStatusFromRaw(this.logger, id, rawAlert.executionStatus); + return { id, notifyWhen, @@ -1470,6 +1513,7 @@ export class AlertsClient { actions: rawAlert.actions ? this.injectReferencesIntoActions(id, rawAlert.actions, references || []) : [], + params: this.injectReferencesIntoParams(id, ruleType, params, references || []) as Params, ...(updatedAt ? { updatedAt: new Date(updatedAt) } : {}), ...(createdAt ? { createdAt: new Date(createdAt) } : {}), ...(scheduledTaskId ? { scheduledTaskId } : {}), @@ -1525,6 +1569,73 @@ export class AlertsClient { } } + private async extractReferences< + Params extends AlertTypeParams, + ExtractedParams extends AlertTypeParams + >( + ruleType: UntypedNormalizedAlertType, + ruleActions: NormalizedAlertAction[], + ruleParams: Params + ): Promise<{ + actions: RawAlert['actions']; + params: ExtractedParams; + references: SavedObjectReference[]; + }> { + const { references: actionReferences, actions } = await this.denormalizeActions(ruleActions); + + // Extracts any references using configured reference extractor if available + const extractedRefsAndParams = ruleType?.useSavedObjectReferences?.extractReferences + ? ruleType.useSavedObjectReferences.extractReferences(ruleParams) + : null; + const extractedReferences = extractedRefsAndParams?.references ?? []; + const params = (extractedRefsAndParams?.params as ExtractedParams) ?? ruleParams; + + // Prefix extracted references in order to avoid clashes with framework level references + const paramReferences = extractedReferences.map((reference: SavedObjectReference) => ({ + ...reference, + name: `${extractedSavedObjectParamReferenceNamePrefix}${reference.name}`, + })); + + const references = [...actionReferences, ...paramReferences]; + + return { + actions, + params, + references, + }; + } + + private injectReferencesIntoParams< + Params extends AlertTypeParams, + ExtractedParams extends AlertTypeParams + >( + ruleId: string, + ruleType: UntypedNormalizedAlertType, + ruleParams: SavedObjectAttributes | undefined, + references: SavedObjectReference[] + ): Params { + try { + const paramReferences = references + .filter((reference: SavedObjectReference) => + reference.name.startsWith(extractedSavedObjectParamReferenceNamePrefix) + ) + .map((reference: SavedObjectReference) => ({ + ...reference, + name: reference.name.replace(extractedSavedObjectParamReferenceNamePrefix, ''), + })); + return ruleParams && ruleType?.useSavedObjectReferences?.injectReferences + ? (ruleType.useSavedObjectReferences.injectReferences( + ruleParams as ExtractedParams, + paramReferences + ) as Params) + : (ruleParams as Params); + } catch (err) { + throw Boom.badRequest( + `Error injecting reference into rule params for rule id ${ruleId} - ${err.message}` + ); + } + } + private async denormalizeActions( alertActions: NormalizedAlertAction[] ): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> { diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts index e231d1e3c27a29..3275e25b85df5b 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts @@ -801,6 +801,360 @@ describe('create()', () => { expect(taskManager.schedule).toHaveBeenCalledTimes(0); }); + test('should call useSavedObjectReferences.extractReferences and useSavedObjectReferences.injectReferences if defined for rule type', async () => { + const ruleParams = { + bar: true, + parameterThatIsSavedObjectId: '9', + }; + const extractReferencesFn = jest.fn().mockReturnValue({ + params: { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + references: [ + { + name: 'soRef_0', + type: 'someSavedObjectType', + id: '9', + }, + ], + }); + const injectReferencesFn = jest.fn().mockReturnValue({ + bar: true, + parameterThatIsSavedObjectId: '9', + }); + alertTypeRegistry.get.mockImplementation(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: extractReferencesFn, + injectReferences: injectReferencesFn, + }, + })); + const data = getMockData({ + params: ruleParams, + }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + notifyWhen: null, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'param:soRef_0', + type: 'someSavedObjectType', + id: '9', + }, + ], + }); + taskManager.schedule.mockResolvedValueOnce({ + id: 'task-123', + taskType: 'alerting:123', + scheduledAt: new Date(), + attempts: 1, + status: TaskStatus.Idle, + runAt: new Date(), + startedAt: null, + retryAt: null, + state: {}, + params: {}, + ownerId: null, + }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + actions: [], + scheduledTaskId: 'task-123', + }, + references: [], + }); + const result = await alertsClient.create({ data }); + + expect(extractReferencesFn).toHaveBeenCalledWith(ruleParams); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( + 'alert', + { + actions: [ + { actionRef: 'action_0', actionTypeId: 'test', group: 'default', params: { foo: true } }, + ], + alertTypeId: '123', + apiKey: null, + apiKeyOwner: null, + consumer: 'bar', + createdAt: '2019-02-12T21:01:22.479Z', + createdBy: 'elastic', + enabled: true, + executionStatus: { + error: null, + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + }, + meta: { versionApiKeyLastmodified: 'v7.10.0' }, + muteAll: false, + mutedInstanceIds: [], + name: 'abc', + notifyWhen: 'onActiveAlert', + params: { bar: true, parameterThatIsSavedObjectRef: 'soRef_0' }, + schedule: { interval: '10s' }, + tags: ['foo'], + throttle: null, + updatedAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + }, + { + id: 'mock-saved-object-id', + references: [ + { id: '1', name: 'action_0', type: 'action' }, + { id: '9', name: 'param:soRef_0', type: 'someSavedObjectType' }, + ], + } + ); + + expect(injectReferencesFn).toHaveBeenCalledWith( + { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + [{ id: '9', name: 'soRef_0', type: 'someSavedObjectType' }] + ); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "createdAt": 2019-02-12T21:01:22.479Z, + "id": "1", + "notifyWhen": null, + "params": Object { + "bar": true, + "parameterThatIsSavedObjectId": "9", + }, + "schedule": Object { + "interval": "10s", + }, + "scheduledTaskId": "task-123", + "updatedAt": 2019-02-12T21:01:22.479Z, + } + `); + }); + + test('should allow rule types to use action_ prefix for saved object reference names', async () => { + const ruleParams = { + bar: true, + parameterThatIsSavedObjectId: '8', + }; + const extractReferencesFn = jest.fn().mockReturnValue({ + params: { + bar: true, + parameterThatIsSavedObjectRef: 'action_0', + }, + references: [ + { + name: 'action_0', + type: 'someSavedObjectType', + id: '8', + }, + ], + }); + const injectReferencesFn = jest.fn().mockReturnValue({ + bar: true, + parameterThatIsSavedObjectId: '8', + }); + alertTypeRegistry.get.mockImplementation(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: extractReferencesFn, + injectReferences: injectReferencesFn, + }, + })); + const data = getMockData({ + params: ruleParams, + }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + parameterThatIsSavedObjectRef: 'action_0', + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + notifyWhen: null, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'param:action_0', + type: 'someSavedObjectType', + id: '8', + }, + ], + }); + taskManager.schedule.mockResolvedValueOnce({ + id: 'task-123', + taskType: 'alerting:123', + scheduledAt: new Date(), + attempts: 1, + status: TaskStatus.Idle, + runAt: new Date(), + startedAt: null, + retryAt: null, + state: {}, + params: {}, + ownerId: null, + }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + actions: [], + scheduledTaskId: 'task-123', + }, + references: [], + }); + const result = await alertsClient.create({ data }); + + expect(extractReferencesFn).toHaveBeenCalledWith(ruleParams); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( + 'alert', + { + actions: [ + { actionRef: 'action_0', actionTypeId: 'test', group: 'default', params: { foo: true } }, + ], + alertTypeId: '123', + apiKey: null, + apiKeyOwner: null, + consumer: 'bar', + createdAt: '2019-02-12T21:01:22.479Z', + createdBy: 'elastic', + enabled: true, + executionStatus: { + error: null, + lastExecutionDate: '2019-02-12T21:01:22.479Z', + status: 'pending', + }, + meta: { versionApiKeyLastmodified: 'v7.10.0' }, + muteAll: false, + mutedInstanceIds: [], + name: 'abc', + notifyWhen: 'onActiveAlert', + params: { bar: true, parameterThatIsSavedObjectRef: 'action_0' }, + schedule: { interval: '10s' }, + tags: ['foo'], + throttle: null, + updatedAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + }, + { + id: 'mock-saved-object-id', + references: [ + { id: '1', name: 'action_0', type: 'action' }, + { id: '8', name: 'param:action_0', type: 'someSavedObjectType' }, + ], + } + ); + + expect(injectReferencesFn).toHaveBeenCalledWith( + { + bar: true, + parameterThatIsSavedObjectRef: 'action_0', + }, + [{ id: '8', name: 'action_0', type: 'someSavedObjectType' }] + ); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "createdAt": 2019-02-12T21:01:22.479Z, + "id": "1", + "notifyWhen": null, + "params": Object { + "bar": true, + "parameterThatIsSavedObjectId": "8", + }, + "schedule": Object { + "interval": "10s", + }, + "scheduledTaskId": "task-123", + "updatedAt": 2019-02-12T21:01:22.479Z, + } + `); + }); + test('should trim alert name when creating API key', async () => { const data = getMockData({ name: ' my alert name ' }); unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts index 5ec39681a758bb..0633dda8e7a59f 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts @@ -191,6 +191,335 @@ describe('find()', () => { expect(jest.requireMock('../lib/map_sort_field').mapSortField).toHaveBeenCalledWith('name'); }); + test('should call useSavedObjectReferences.injectReferences if defined for rule type', async () => { + jest.resetAllMocks(); + authorization.getFindAuthorizationFilter.mockResolvedValue({ + ensureRuleTypeIsAuthorized() {}, + logSuccessfulAuthorization() {}, + }); + const injectReferencesFn = jest.fn().mockReturnValue({ + bar: true, + parameterThatIsSavedObjectId: '9', + }); + alertTypeRegistry.list.mockReturnValue( + new Set([ + ...listedTypes, + { + actionGroups: [], + recoveryActionGroup: RecoveredActionGroup, + actionVariables: undefined, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + id: '123', + name: 'myType', + producer: 'myApp', + enabledInLicense: true, + }, + ]) + ); + alertTypeRegistry.get.mockImplementationOnce(() => ({ + id: 'myType', + name: 'myType', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'myApp', + })); + alertTypeRegistry.get.mockImplementationOnce(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: jest.fn(), + injectReferences: injectReferencesFn, + }, + })); + unsecuredSavedObjectsClient.find.mockResolvedValue({ + total: 2, + per_page: 10, + page: 1, + saved_objects: [ + { + id: '1', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + }, + score: 1, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + { + id: '2', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '20s' }, + params: { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + }, + score: 1, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'param:soRef_0', + type: 'someSavedObjectType', + id: '9', + }, + ], + }, + ], + }); + const alertsClient = new AlertsClient(alertsClientParams); + const result = await alertsClient.find({ options: {} }); + + expect(injectReferencesFn).toHaveBeenCalledTimes(1); + expect(injectReferencesFn).toHaveBeenCalledWith( + { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + [{ id: '9', name: 'soRef_0', type: 'someSavedObjectType' }] + ); + + expect(result).toMatchInlineSnapshot(` + Object { + "data": Array [ + Object { + "actions": Array [ + Object { + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "myType", + "createdAt": 2019-02-12T21:01:22.479Z, + "id": "1", + "notifyWhen": "onActiveAlert", + "params": Object { + "bar": true, + }, + "schedule": Object { + "interval": "10s", + }, + "updatedAt": 2019-02-12T21:01:22.479Z, + }, + Object { + "actions": Array [ + Object { + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "createdAt": 2019-02-12T21:01:22.479Z, + "id": "2", + "notifyWhen": "onActiveAlert", + "params": Object { + "bar": true, + "parameterThatIsSavedObjectId": "9", + }, + "schedule": Object { + "interval": "20s", + }, + "updatedAt": 2019-02-12T21:01:22.479Z, + }, + ], + "page": 1, + "perPage": 10, + "total": 2, + } + `); + }); + + test('throws an error if useSavedObjectReferences.injectReferences throws an error', async () => { + jest.resetAllMocks(); + authorization.getFindAuthorizationFilter.mockResolvedValue({ + ensureRuleTypeIsAuthorized() {}, + logSuccessfulAuthorization() {}, + }); + const injectReferencesFn = jest.fn().mockImplementation(() => { + throw new Error('something went wrong!'); + }); + alertTypeRegistry.list.mockReturnValue( + new Set([ + ...listedTypes, + { + actionGroups: [], + recoveryActionGroup: RecoveredActionGroup, + actionVariables: undefined, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + id: '123', + name: 'myType', + producer: 'myApp', + enabledInLicense: true, + }, + ]) + ); + alertTypeRegistry.get.mockImplementationOnce(() => ({ + id: 'myType', + name: 'myType', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'myApp', + })); + alertTypeRegistry.get.mockImplementationOnce(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: jest.fn(), + injectReferences: injectReferencesFn, + }, + })); + unsecuredSavedObjectsClient.find.mockResolvedValue({ + total: 2, + per_page: 10, + page: 1, + saved_objects: [ + { + id: '1', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + }, + score: 1, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + { + id: '2', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '20s' }, + params: { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + }, + score: 1, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'soRef_0', + type: 'someSavedObjectType', + id: '9', + }, + ], + }, + ], + }); + const alertsClient = new AlertsClient(alertsClientParams); + await expect(alertsClient.find({ options: {} })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Error injecting reference into rule params for rule id 2 - something went wrong!"` + ); + }); + describe('authorization', () => { test('ensures user is query filter types down to those the user is authorized to find', async () => { const filter = esKuery.fromKueryExpression( @@ -257,6 +586,7 @@ describe('find()', () => { "actions": Array [], "id": "1", "notifyWhen": undefined, + "params": undefined, "schedule": undefined, "tags": Array [ "myTag", diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/get.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/get.test.ts index 1be9d3e3ba2c92..82a8acefb386df 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/get.test.ts @@ -17,6 +17,7 @@ import { ActionsAuthorization } from '../../../../actions/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; import { getBeforeSetup, setGlobalDate } from './lib'; +import { RecoveredActionGroup } from '../../../common'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -118,6 +119,99 @@ describe('get()', () => { `); }); + test('should call useSavedObjectReferences.injectReferences if defined for rule type', async () => { + const injectReferencesFn = jest.fn().mockReturnValue({ + bar: true, + parameterThatIsSavedObjectId: '9', + }); + alertTypeRegistry.get.mockImplementation(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: jest.fn(), + injectReferences: injectReferencesFn, + }, + })); + const alertsClient = new AlertsClient(alertsClientParams); + unsecuredSavedObjectsClient.get.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + notifyWhen: 'onActiveAlert', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'param:soRef_0', + type: 'someSavedObjectType', + id: '9', + }, + ], + }); + const result = await alertsClient.get({ id: '1' }); + + expect(injectReferencesFn).toHaveBeenCalledWith( + { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + [{ id: '9', name: 'soRef_0', type: 'someSavedObjectType' }] + ); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "createdAt": 2019-02-12T21:01:22.479Z, + "id": "1", + "notifyWhen": "onActiveAlert", + "params": Object { + "bar": true, + "parameterThatIsSavedObjectId": "9", + }, + "schedule": Object { + "interval": "10s", + }, + "updatedAt": 2019-02-12T21:01:22.479Z, + } + `); + }); + test(`throws an error when references aren't found`, async () => { const alertsClient = new AlertsClient(alertsClientParams); unsecuredSavedObjectsClient.get.mockResolvedValueOnce({ @@ -146,6 +240,67 @@ describe('get()', () => { ); }); + test('throws an error if useSavedObjectReferences.injectReferences throws an error', async () => { + const injectReferencesFn = jest.fn().mockImplementation(() => { + throw new Error('something went wrong!'); + }); + alertTypeRegistry.get.mockImplementation(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: jest.fn(), + injectReferences: injectReferencesFn, + }, + })); + const alertsClient = new AlertsClient(alertsClientParams); + unsecuredSavedObjectsClient.get.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + notifyWhen: 'onActiveAlert', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'soRef_0', + type: 'someSavedObjectType', + id: '9', + }, + ], + }); + await expect(alertsClient.get({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Error injecting reference into rule params for rule id 1 - something went wrong!"` + ); + }); + describe('authorization', () => { beforeEach(() => { unsecuredSavedObjectsClient.get.mockResolvedValueOnce({ diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts index 2de56d20702f45..b65f3e06df9fcd 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts @@ -24,6 +24,12 @@ import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; import { getBeforeSetup, setGlobalDate } from './lib'; +jest.mock('../../../../../../src/core/server/saved_objects/service/lib/utils', () => ({ + SavedObjectsUtils: { + generateId: () => 'mock-saved-object-id', + }, +})); + const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); @@ -401,6 +407,172 @@ describe('update()', () => { expect(actionsClient.isActionTypeEnabled).toHaveBeenCalledWith('test2', { notifyUsage: true }); }); + test('should call useSavedObjectReferences.extractReferences and useSavedObjectReferences.injectReferences if defined for rule type', async () => { + const ruleParams = { + bar: true, + parameterThatIsSavedObjectId: '9', + }; + const extractReferencesFn = jest.fn().mockReturnValue({ + params: { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + references: [ + { + name: 'soRef_0', + type: 'someSavedObjectType', + id: '9', + }, + ], + }); + const injectReferencesFn = jest.fn().mockReturnValue({ + bar: true, + parameterThatIsSavedObjectId: '9', + }); + alertTypeRegistry.get.mockImplementation(() => ({ + id: 'myType', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: extractReferencesFn, + injectReferences: injectReferencesFn, + }, + })); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: true, + schedule: { interval: '10s' }, + params: { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + notifyWhen: 'onActiveAlert', + scheduledTaskId: 'task-123', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'param:soRef_0', + type: 'someSavedObjectType', + id: '9', + }, + ], + }); + const result = await alertsClient.update({ + id: '1', + data: { + schedule: { interval: '10s' }, + name: 'abc', + tags: ['foo'], + params: ruleParams, + throttle: null, + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + }, + }); + + expect(extractReferencesFn).toHaveBeenCalledWith(ruleParams); + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( + 'alert', + { + actions: [ + { actionRef: 'action_0', actionTypeId: 'test', group: 'default', params: { foo: true } }, + ], + alertTypeId: 'myType', + apiKey: null, + apiKeyOwner: null, + consumer: 'myApp', + enabled: true, + meta: { versionApiKeyLastmodified: 'v7.10.0' }, + name: 'abc', + notifyWhen: 'onActiveAlert', + params: { bar: true, parameterThatIsSavedObjectRef: 'soRef_0' }, + schedule: { interval: '10s' }, + scheduledTaskId: 'task-123', + tags: ['foo'], + throttle: null, + updatedAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + }, + { + id: '1', + overwrite: true, + references: [ + { id: '1', name: 'action_0', type: 'action' }, + { id: '9', name: 'param:soRef_0', type: 'someSavedObjectType' }, + ], + version: '123', + } + ); + + expect(injectReferencesFn).toHaveBeenCalledWith( + { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + [{ id: '9', name: 'soRef_0', type: 'someSavedObjectType' }] + ); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "createdAt": 2019-02-12T21:01:22.479Z, + "enabled": true, + "id": "1", + "notifyWhen": "onActiveAlert", + "params": Object { + "bar": true, + "parameterThatIsSavedObjectId": "9", + }, + "schedule": Object { + "interval": "10s", + }, + "scheduledTaskId": "task-123", + "updatedAt": 2019-02-12T21:01:22.479Z, + } + `); + }); + it('calls the createApiKey function', async () => { alertsClientParams.createAPIKey.mockResolvedValueOnce({ apiKeysEnabled: true, diff --git a/x-pack/plugins/alerting/server/index.ts b/x-pack/plugins/alerting/server/index.ts index 0faaca5d549f8c..d4cc47d221906e 100644 --- a/x-pack/plugins/alerting/server/index.ts +++ b/x-pack/plugins/alerting/server/index.ts @@ -28,6 +28,7 @@ export type { AlertInstanceState, AlertInstanceContext, AlertingApiRequestHandlerContext, + RuleParamsAndRefs, } from './types'; export { DEFAULT_MAX_EPHEMERAL_ACTIONS_PER_ALERT } from './config'; export { PluginSetupContract, PluginStartContract } from './plugin'; diff --git a/x-pack/plugins/alerting/server/lib/license_state.test.ts b/x-pack/plugins/alerting/server/lib/license_state.test.ts index e04ce85b353744..6cfe3682458427 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.test.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.test.ts @@ -57,7 +57,7 @@ describe('getLicenseCheckForAlertType', () => { let license: Subject; let licenseState: ILicenseState; const mockNotifyUsage = jest.fn(); - const alertType: AlertType = { + const alertType: AlertType = { id: 'test', name: 'Test', actionGroups: [ @@ -192,7 +192,7 @@ describe('ensureLicenseForAlertType()', () => { let license: Subject; let licenseState: ILicenseState; const mockNotifyUsage = jest.fn(); - const alertType: AlertType = { + const alertType: AlertType = { id: 'test', name: 'Test', actionGroups: [ diff --git a/x-pack/plugins/alerting/server/lib/license_state.ts b/x-pack/plugins/alerting/server/lib/license_state.ts index dc5d9d278b6b5f..837fecde11659c 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.ts @@ -140,6 +140,7 @@ export class LicenseState { public ensureLicenseForAlertType< Params extends AlertTypeParams, + ExtractedParams extends AlertTypeParams, State extends AlertTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, @@ -148,6 +149,7 @@ export class LicenseState { >( alertType: AlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index cfb30b6bd22027..b1fc44ab4122ff 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -62,7 +62,7 @@ describe('Alerting Plugin', () => { describe('registerType()', () => { let setup: PluginSetupContract; - const sampleAlertType: AlertType = { + const sampleAlertType: AlertType = { id: 'test', name: 'test', minimumLicenseRequired: 'basic', diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 8a90a918b4d063..096182dc9c3c80 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -87,6 +87,7 @@ export const LEGACY_EVENT_LOG_ACTIONS = { export interface PluginSetupContract { registerType< Params extends AlertTypeParams = AlertTypeParams, + ExtractedParams extends AlertTypeParams = AlertTypeParams, State extends AlertTypeState = AlertTypeState, InstanceState extends AlertInstanceState = AlertInstanceState, InstanceContext extends AlertInstanceContext = AlertInstanceContext, @@ -95,6 +96,7 @@ export interface PluginSetupContract { >( alertType: AlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -277,6 +279,7 @@ export class AlertingPlugin { return { registerType< Params extends AlertTypeParams = AlertTypeParams, + ExtractedParams extends AlertTypeParams = AlertTypeParams, State extends AlertTypeState = AlertTypeState, InstanceState extends AlertInstanceState = AlertInstanceState, InstanceContext extends AlertInstanceContext = AlertInstanceContext, @@ -285,6 +288,7 @@ export class AlertingPlugin { >( alertType: AlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts index e6618d293a3996..a40f7dc2abc720 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts @@ -29,6 +29,7 @@ jest.mock('./inject_action_params', () => ({ })); const alertType: NormalizedAlertType< + AlertTypeParams, AlertTypeParams, AlertTypeState, AlertInstanceState, @@ -59,6 +60,7 @@ const mockActionsPlugin = actionsMock.createStart(); const mockEventLogger = eventLoggerMock.create(); const createExecutionHandlerParams: jest.Mocked< CreateExecutionHandlerOptions< + AlertTypeParams, AlertTypeParams, AlertTypeState, AlertInstanceState, diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts index b1f5b44f529c40..808e9f5de8f421 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts @@ -26,6 +26,7 @@ import { isEphemeralTaskRejectedDueToCapacityError } from '../../../task_manager export interface CreateExecutionHandlerOptions< Params extends AlertTypeParams, + ExtractedParams extends AlertTypeParams, State extends AlertTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, @@ -42,6 +43,7 @@ export interface CreateExecutionHandlerOptions< kibanaBaseUrl: string | undefined; alertType: NormalizedAlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -70,6 +72,7 @@ export type ExecutionHandler = ( export function createExecutionHandler< Params extends AlertTypeParams, + ExtractedParams extends AlertTypeParams, State extends AlertTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, @@ -93,6 +96,7 @@ export function createExecutionHandler< maxEphemeralActionsPerAlert, }: CreateExecutionHandlerOptions< Params, + ExtractedParams, State, InstanceState, InstanceContext, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index ff5393ccd9fb84..605588cbf321ff 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -70,6 +70,7 @@ interface AlertTaskInstance extends ConcreteTaskInstance { export class TaskRunner< Params extends AlertTypeParams, + ExtractedParams extends AlertTypeParams, State extends AlertTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, @@ -81,6 +82,7 @@ export class TaskRunner< private taskInstance: AlertTaskInstance; private alertType: NormalizedAlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -92,6 +94,7 @@ export class TaskRunner< constructor( alertType: NormalizedAlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -171,6 +174,7 @@ export class TaskRunner< ) { return createExecutionHandler< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -703,6 +707,7 @@ interface GenerateNewAndRecoveredInstanceEventsParams< alertLabel: string; namespace: string | undefined; ruleType: NormalizedAlertType< + AlertTypeParams, AlertTypeParams, AlertTypeState, { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts index 5a6b28dc059fa3..698cb935874b25 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -59,6 +59,7 @@ export class TaskRunnerFactory { public create< Params extends AlertTypeParams, + ExtractedParams extends AlertTypeParams, State extends AlertTypeState, InstanceState extends AlertInstanceState, InstanceContext extends AlertInstanceContext, @@ -67,6 +68,7 @@ export class TaskRunnerFactory { >( alertType: NormalizedAlertType< Params, + ExtractedParams, State, InstanceState, InstanceContext, @@ -81,6 +83,7 @@ export class TaskRunnerFactory { return new TaskRunner< Params, + ExtractedParams, State, InstanceState, InstanceContext, diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index f21e17adc841d8..b12341a5a602da 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { IRouter, RequestHandlerContext, SavedObjectReference } from 'src/core/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { PublicAlertInstance } from './alert_instance'; import { AlertTypeRegistry as OrigAlertTypeRegistry } from './alert_type_registry'; @@ -99,6 +99,11 @@ export interface AlertExecutorOptions< updatedBy: string | null; } +export interface RuleParamsAndRefs { + references: SavedObjectReference[]; + params: Params; +} + export type ExecutorType< Params extends AlertTypeParams = never, State extends AlertTypeState = never, @@ -114,6 +119,7 @@ export interface AlertTypeParamsValidator { } export interface AlertType< Params extends AlertTypeParams = never, + ExtractedParams extends AlertTypeParams = never, State extends AlertTypeState = never, InstanceState extends AlertInstanceState = never, InstanceContext extends AlertInstanceContext = never, @@ -146,6 +152,10 @@ export interface AlertType< params?: ActionVariable[]; }; minimumLicenseRequired: LicenseType; + useSavedObjectReferences?: { + extractReferences: (params: Params) => RuleParamsAndRefs; + injectReferences: (params: ExtractedParams, references: SavedObjectReference[]) => Params; + }; isExportable: boolean; } 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 a2d8e522c7c8d5..5410353ac46a0f 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 @@ -62,6 +62,7 @@ export const registerMetricInventoryThresholdAlertType = ( * TODO: Remove this use of `any` by utilizing a proper type */ Record, + never, // Only use if defining useSavedObjectReferences hook Record, AlertInstanceState, AlertInstanceContext, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts index 63354111a1a98c..2e3660c901b4a6 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts @@ -34,6 +34,7 @@ export const registerMetricAnomalyAlertType = ( * TODO: Remove this use of `any` by utilizing a proper type */ Record, + never, // Only use if defining useSavedObjectReferences hook Record, AlertInstanceState, AlertInstanceContext, 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 e519d67b446a5d..9418762d3e1bfd 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 @@ -38,6 +38,7 @@ export type MetricThresholdAlertType = AlertType< * TODO: Remove this use of `any` by utilizing a proper type */ Record, + never, // Only use if defining useSavedObjectReferences hook Record, AlertInstanceState, AlertInstanceContext, diff --git a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts index 93c627c0f6311f..07bca8f3aae745 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -46,6 +46,7 @@ export function registerAnomalyDetectionAlertType({ }: RegisterAlertParams) { alerting.registerType< MlAnomalyDetectionAlertParams, + never, // Only use if defining useSavedObjectReferences hook AlertTypeState, AlertInstanceState, AnomalyDetectionAlertContext, diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index cfd7630a65dbc4..0020ef779838f9 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -81,7 +81,7 @@ export class BaseAlert { this.scopedLogger = Globals.app.getLogger(alertOptions.id); } - public getAlertType(): AlertType { + public getAlertType(): AlertType { const { id, name, actionVariables } = this.alertOptions; return { id, diff --git a/x-pack/plugins/rule_registry/server/types.ts b/x-pack/plugins/rule_registry/server/types.ts index 051789b1896bbe..4b63acd5b01abc 100644 --- a/x-pack/plugins/rule_registry/server/types.ts +++ b/x-pack/plugins/rule_registry/server/types.ts @@ -18,7 +18,15 @@ import { AlertsClient } from './alert_data_client/alerts_client'; type SimpleAlertType< TParams extends AlertTypeParams = {}, TAlertInstanceContext extends AlertInstanceContext = {} -> = AlertType; +> = AlertType< + TParams, + TParams, + AlertTypeState, + AlertInstanceState, + TAlertInstanceContext, + string, + string +>; export type AlertTypeExecutor< TParams extends AlertTypeParams = {}, @@ -35,7 +43,15 @@ export type AlertTypeWithExecutor< TAlertInstanceContext extends AlertInstanceContext = {}, TServices extends Record = {} > = Omit< - AlertType, + AlertType< + TParams, + TParams, + AlertTypeState, + AlertInstanceState, + TAlertInstanceContext, + string, + string + >, 'executor' > & { executor: AlertTypeExecutor; 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 0a32e0c21a075a..50ad98865544ea 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 @@ -101,12 +101,25 @@ export type NotificationExecutorOptions = AlertExecutorOptions< // since we are only increasing the strictness of params. export const isNotificationAlertExecutor = ( obj: NotificationAlertTypeDefinition -): obj is AlertType => { +): obj is AlertType< + AlertTypeParams, + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext +> => { return true; }; export type NotificationAlertTypeDefinition = Omit< - AlertType, + AlertType< + AlertTypeParams, + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, + 'default' + >, 'executor' > & { executor: ({ 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 3fc36d5930a0aa..edf6d244cfa174 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 @@ -184,6 +184,7 @@ export const isAlertExecutor = ( obj: SignalRuleAlertTypeDefinition ): obj is AlertType< RuleParams, + never, // Only use if defining useSavedObjectReferences hook AlertTypeState, AlertInstanceState, AlertInstanceContext, @@ -194,6 +195,7 @@ export const isAlertExecutor = ( export type SignalRuleAlertTypeDefinition = AlertType< RuleParams, + never, // Only use if defining useSavedObjectReferences hook AlertTypeState, AlertInstanceState, AlertInstanceContext, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts index 9e5b542e882d18..5e2e4699c25ca0 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts @@ -27,7 +27,14 @@ export const ConditionMetAlertInstanceId = 'query matched'; export function getAlertType( logger: Logger -): AlertType { +): AlertType< + EsQueryAlertParams, + never, // Only use if defining useSavedObjectReferences hook + EsQueryAlertState, + {}, + ActionContext, + typeof ActionGroupId +> { const alertTypeName = i18n.translate('xpack.stackAlerts.esQuery.alertTypeTitle', { defaultMessage: 'Elasticsearch query', }); 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 43ae726fa2478a..111fda3bdaca83 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 @@ -140,6 +140,7 @@ export interface GeoContainmentInstanceContext extends AlertInstanceContext { export type GeoContainmentAlertType = AlertType< GeoContainmentParams, + never, // Only use if defining useSavedObjectReferences hook GeoContainmentState, GeoContainmentInstanceState, GeoContainmentInstanceContext, 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 ee0b36bcd17668..023ea168a77d2a 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 @@ -26,6 +26,7 @@ export function register(params: RegisterParams) { const { logger, alerting } = params; alerting.registerType< GeoContainmentParams, + never, // Only use if defining useSavedObjectReferences hook GeoContainmentState, GeoContainmentInstanceState, GeoContainmentInstanceContext, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts index 035d999699d4b8..46c3c3467cefea 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts @@ -23,7 +23,7 @@ export const ActionGroupId = 'threshold met'; export function getAlertType( logger: Logger, data: Promise -): AlertType { +): AlertType { const alertTypeName = i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeTitle', { defaultMessage: 'Index threshold', }); diff --git a/x-pack/plugins/stack_alerts/server/types.ts b/x-pack/plugins/stack_alerts/server/types.ts index 9725d901383002..b78aa4e6432d5e 100644 --- a/x-pack/plugins/stack_alerts/server/types.ts +++ b/x-pack/plugins/stack_alerts/server/types.ts @@ -11,6 +11,7 @@ import { PluginSetupContract as AlertingSetup } from '../../alerting/server'; export { PluginSetupContract as AlertingSetup, AlertType, + RuleParamsAndRefs, AlertExecutorOptions, } from '../../alerting/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; 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 3c9d783f5a3578..f75f9ce9962692 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 @@ -64,6 +64,7 @@ function getAlwaysFiringAlertType() { } const result: AlertType< ParamsType & AlertTypeParams, + never, // Only use if defining useSavedObjectReferences hook State, InstanceState, InstanceContext, @@ -158,7 +159,7 @@ function getCumulativeFiringAlertType() { interface InstanceState extends AlertInstanceState { instanceStateValue: boolean; } - const result: AlertType<{}, State, InstanceState, {}, 'default' | 'other'> = { + const result: AlertType<{}, {}, State, InstanceState, {}, 'default' | 'other'> = { id: 'test.cumulative-firing', name: 'Test: Cumulative Firing', actionGroups: [ @@ -199,7 +200,7 @@ function getNeverFiringAlertType() { interface State extends AlertTypeState { globalStateValue: boolean; } - const result: AlertType = { + const result: AlertType = { id: 'test.never-firing', name: 'Test: Never firing', actionGroups: [ @@ -240,7 +241,7 @@ function getFailingAlertType() { reference: schema.string(), }); type ParamsType = TypeOf; - const result: AlertType = { + const result: AlertType = { id: 'test.failing', name: 'Test: Failing', validate: { @@ -282,7 +283,7 @@ function getAuthorizationAlertType(core: CoreSetup) { reference: schema.string(), }); type ParamsType = TypeOf; - const result: AlertType = { + const result: AlertType = { id: 'test.authorization', name: 'Test: Authorization', actionGroups: [ @@ -370,7 +371,7 @@ function getValidationAlertType() { param1: schema.string(), }); type ParamsType = TypeOf; - const result: AlertType = { + const result: AlertType = { id: 'test.validation', name: 'Test: Validation', actionGroups: [ @@ -403,7 +404,7 @@ function getPatternFiringAlertType() { interface State extends AlertTypeState { patternIndex?: number; } - const result: AlertType = { + const result: AlertType = { id: 'test.patternFiring', name: 'Test: Firing on a Pattern', actionGroups: [{ id: 'default', name: 'Default' }], @@ -468,7 +469,7 @@ export function defineAlertTypes( core: CoreSetup, { alerting }: Pick ) { - const noopAlertType: AlertType<{}, {}, {}, {}, 'default'> = { + const noopAlertType: AlertType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.noop', name: 'Test: Noop', actionGroups: [{ id: 'default', name: 'Default' }], @@ -478,7 +479,7 @@ export function defineAlertTypes( isExportable: true, async executor() {}, }; - const goldNoopAlertType: AlertType<{}, {}, {}, {}, 'default'> = { + const goldNoopAlertType: AlertType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.gold.noop', name: 'Test: Noop', actionGroups: [{ id: 'default', name: 'Default' }], @@ -488,7 +489,7 @@ export function defineAlertTypes( isExportable: true, async executor() {}, }; - const onlyContextVariablesAlertType: AlertType<{}, {}, {}, {}, 'default'> = { + const onlyContextVariablesAlertType: AlertType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.onlyContextVariables', name: 'Test: Only Context Variables', actionGroups: [{ id: 'default', name: 'Default' }], @@ -501,7 +502,7 @@ export function defineAlertTypes( }, async executor() {}, }; - const onlyStateVariablesAlertType: AlertType<{}, {}, {}, {}, 'default'> = { + const onlyStateVariablesAlertType: AlertType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.onlyStateVariables', name: 'Test: Only State Variables', actionGroups: [{ id: 'default', name: 'Default' }], @@ -514,7 +515,7 @@ export function defineAlertTypes( isExportable: true, async executor() {}, }; - const throwAlertType: AlertType<{}, {}, {}, {}, 'default'> = { + const throwAlertType: AlertType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.throw', name: 'Test: Throw', actionGroups: [ @@ -531,7 +532,7 @@ export function defineAlertTypes( throw new Error('this alert is intended to fail'); }, }; - const longRunningAlertType: AlertType<{}, {}, {}, {}, 'default'> = { + const longRunningAlertType: AlertType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.longRunning', name: 'Test: Long Running', actionGroups: [ @@ -548,6 +549,27 @@ export function defineAlertTypes( await new Promise((resolve) => setTimeout(resolve, 5000)); }, }; + const exampleAlwaysFiringAlertType: AlertType< + {}, + {}, + {}, + {}, + {}, + 'small' | 'medium' | 'large' + > = { + id: 'example.always-firing', + name: 'Always firing', + actionGroups: [ + { id: 'small', name: 'Small t-shirt' }, + { id: 'medium', name: 'Medium t-shirt' }, + { id: 'large', name: 'Large t-shirt' }, + ], + defaultActionGroupId: 'small', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alertsFixture', + }; alerting.registerType(getAlwaysFiringAlertType()); alerting.registerType(getCumulativeFiringAlertType()); @@ -562,4 +584,5 @@ export function defineAlertTypes( alerting.registerType(throwAlertType); alerting.registerType(longRunningAlertType); alerting.registerType(goldNoopAlertType); + alerting.registerType(exampleAlwaysFiringAlertType); } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts_restricted/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts_restricted/server/alert_types.ts index 2255d1fa95e2de..4fd3f35b4fc4f5 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts_restricted/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts_restricted/server/alert_types.ts @@ -13,7 +13,7 @@ export function defineAlertTypes( core: CoreSetup, { alerting }: Pick ) { - const noopRestrictedAlertType: AlertType<{}, {}, {}, {}, 'default', 'restrictedRecovered'> = { + const noopRestrictedAlertType: AlertType<{}, {}, {}, {}, {}, 'default', 'restrictedRecovered'> = { id: 'test.restricted-noop', name: 'Test: Restricted Noop', actionGroups: [{ id: 'default', name: 'Default' }], @@ -24,7 +24,7 @@ export function defineAlertTypes( recoveryActionGroup: { id: 'restrictedRecovered', name: 'Restricted Recovery' }, async executor() {}, }; - const noopUnrestrictedAlertType: AlertType<{}, {}, {}, {}, 'default'> = { + const noopUnrestrictedAlertType: AlertType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.unrestricted-noop', name: 'Test: Unrestricted Noop', actionGroups: [{ id: 'default', name: 'Default' }], 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 8d0d2c4f0be335..bb0b1d9c5a4a59 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 @@ -18,7 +18,7 @@ export interface AlertingExampleDeps { features: FeaturesPluginSetup; } -export const noopAlertType: AlertType<{}, {}, {}, {}, 'default'> = { +export const noopAlertType: AlertType<{}, {}, {}, {}, {}, 'default'> = { id: 'test.noop', name: 'Test: Noop', actionGroups: [{ id: 'default', name: 'Default' }], @@ -31,6 +31,7 @@ export const noopAlertType: AlertType<{}, {}, {}, {}, 'default'> = { export const alwaysFiringAlertType: AlertType< { instances: Array<{ id: string; state: any }> }, + never, // Only use if defining useSavedObjectReferences hook { globalStateValue: boolean; groupInSeriesIndex: number; @@ -66,7 +67,7 @@ export const alwaysFiringAlertType: AlertType< }, }; -export const failingAlertType: AlertType = { +export const failingAlertType: AlertType = { id: 'test.failing', name: 'Test: Failing', actionGroups: [