Add UUID to RuleAction (#148038)

Resolves: [#149587](https://github.com/elastic/kibana/issues/149587)

This PR intends to add a `uuid` field to the rule actions.

A migration script add a uuid to all the existing actions.

Create method of the rule_client (and create endpoint) accepts a
rule.actions data without uuid and adds a new uuid to the all actions.

Update and bulkEdit methods of the rule_client (and update and bulk_edit
endpoints) accepts rule.actions data with and without uuid. As a user
can add a new action to an existing rule, UI should send the uuid of an
existing action back then update and bulkEdit methods would add the
uuid's to all the new actions.

All the get methods return uuid in the actions.

Since we don't query by the uuid of an action, I marked actions as
`dynamic: false` in the mappings so we don't need to add the uuid to the
mappings.

I tried not to modify the legacy APIs, therefore i used some type
castings there, but please let me know if they need to be modified as
well.

## To verify: 

Create a rule with some actions and save. 
Then add some more actions and expect all of them to have an
auto-generated uuid field and still work as they were.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Ersin Erdal 2023-02-22 15:37:16 +01:00 committed by GitHub
parent 8440a299a6
commit f562b3c289
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 785 additions and 151 deletions

View file

@ -5,6 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { NonEmptyString } from '@kbn/securitysolution-io-ts-types';
import * as t from 'io-ts';
import { saved_object_attributes } from '../saved_object_attributes';
@ -18,6 +19,9 @@ export const RuleActionId = t.string;
export type RuleActionTypeId = t.TypeOf<typeof RuleActionTypeId>;
export const RuleActionTypeId = t.string;
export type RuleActionUuid = t.TypeOf<typeof RuleActionUuid>;
export const RuleActionUuid = NonEmptyString;
/**
* Params is an "object", since it is a type of RuleActionParams which is action templates.
* @see x-pack/plugins/alerting/common/rule.ts
@ -27,12 +31,15 @@ export const RuleActionParams = saved_object_attributes;
export type RuleAction = t.TypeOf<typeof RuleAction>;
export const RuleAction = t.exact(
t.type({
group: RuleActionGroup,
id: RuleActionId,
action_type_id: RuleActionTypeId,
params: RuleActionParams,
})
t.intersection([
t.type({
group: RuleActionGroup,
id: RuleActionId,
action_type_id: RuleActionTypeId,
params: RuleActionParams,
}),
t.partial({ uuid: RuleActionUuid }),
])
);
export type RuleActionArray = t.TypeOf<typeof RuleActionArray>;
@ -40,12 +47,15 @@ export const RuleActionArray = t.array(RuleAction);
export type RuleActionCamel = t.TypeOf<typeof RuleActionCamel>;
export const RuleActionCamel = t.exact(
t.type({
group: RuleActionGroup,
id: RuleActionId,
actionTypeId: RuleActionTypeId,
params: RuleActionParams,
})
t.intersection([
t.type({
group: RuleActionGroup,
id: RuleActionId,
actionTypeId: RuleActionTypeId,
params: RuleActionParams,
}),
t.partial({ uuid: RuleActionUuid }),
])
);
export type RuleActionArrayCamel = t.TypeOf<typeof RuleActionArrayCamel>;

View file

@ -57,7 +57,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
Object {
"action": "6cfc277ed3211639e37546ac625f4a68f2494215",
"action_task_params": "db2afea7d78e00e725486b791554d0d4e81956ef",
"alert": "f81ad957a7936522482e4539c7a96a963ebdbc3e",
"alert": "2568bf6d8ba0876441c61c9e58e08016c1dc1617",
"api_key_pending_invalidation": "16e7bcf8e78764102d7f525542d5b616809a21ee",
"apm-indices": "d19dd7fb51f2d2cbc1f8769481721e0953f9a6d2",
"apm-server-schema": "1d42f17eff9ec6c16d3a9324d9539e2d123d0a9a",

View file

@ -77,6 +77,7 @@ export type RuleActionParams = SavedObjectAttributes;
export type RuleActionParam = SavedObjectAttribute;
export interface RuleAction {
uuid?: string;
group: string;
id: string;
actionTypeId: string;

View file

@ -109,6 +109,7 @@ describe('loadRule', () => {
"params": Object {
"message": "alert 37: {{context.message}}",
},
"uuid": "123-456",
},
],
"alertTypeId": ".index-threshold",
@ -278,6 +279,7 @@ function getApiRule() {
},
group: 'threshold met',
id: '3619a0d0-582b-11ec-8995-2b1578a3bc5d',
uuid: '123-456',
},
],
params: { x: 42 },
@ -319,6 +321,7 @@ function getRule(): Rule<{ x: number }> {
},
group: 'threshold met',
id: '3619a0d0-582b-11ec-8995-2b1578a3bc5d',
uuid: '123-456',
},
],
params: { x: 42 },

View file

@ -31,6 +31,7 @@ describe('common_transformations', () => {
group: 'some group',
id: 'some-connector-id',
params: { foo: 'car', bar: [1, 2, 3] },
uuid: '123-456',
},
],
params: { bar: 'foo', numbers: { 1: [2, 3] } } as never,
@ -108,6 +109,7 @@ describe('common_transformations', () => {
],
"foo": "car",
},
"uuid": "123-456",
},
],
"alertTypeId": "some-rule-type",
@ -213,6 +215,7 @@ describe('common_transformations', () => {
group: 'some group',
id: 'some-connector-id',
params: {},
uuid: '123-456',
},
],
params: {} as never,
@ -277,6 +280,7 @@ describe('common_transformations', () => {
"group": "some group",
"id": "some-connector-id",
"params": Object {},
"uuid": "123-456",
},
],
"alertTypeId": "some-rule-type",

View file

@ -41,6 +41,7 @@ describe('bulkEditInternalRulesRoute', () => {
params: {
foo: true,
},
uuid: '123-456',
},
],
consumer: 'bar',
@ -111,6 +112,7 @@ describe('bulkEditInternalRulesRoute', () => {
params: {
foo: true,
},
uuid: '123-456',
},
],
}),

View file

@ -18,6 +18,7 @@ const ruleActionSchema = schema.object({
group: schema.string(),
id: schema.string(),
params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
uuid: schema.maybe(schema.string()),
});
const operationsSchema = schema.arrayOf(

View file

@ -48,6 +48,7 @@ describe('cloneRuleRoute', () => {
params: {
foo: true,
},
uuid: '123-456',
},
],
enabled: true,
@ -97,6 +98,7 @@ describe('cloneRuleRoute', () => {
{
...ruleToClone.actions[0],
connector_type_id: 'test',
uuid: '123-456',
},
],
};

View file

@ -51,6 +51,7 @@ describe('createRuleRoute', () => {
params: {
foo: true,
},
uuid: '123-456',
},
],
enabled: true,
@ -100,6 +101,7 @@ describe('createRuleRoute', () => {
{
...ruleToCreate.actions[0],
connector_type_id: 'test',
uuid: '123-456',
},
],
};

View file

@ -44,6 +44,7 @@ describe('getRuleRoute', () => {
params: {
foo: true,
},
uuid: '123-456',
},
],
consumer: 'bar',
@ -85,6 +86,7 @@ describe('getRuleRoute', () => {
id: mockedAlert.actions[0].id,
params: mockedAlert.actions[0].params,
connector_type_id: mockedAlert.actions[0].actionTypeId,
uuid: mockedAlert.actions[0].uuid,
},
],
};

View file

@ -60,7 +60,7 @@ const rewriteBodyRes: RewriteResponseCase<SanitizedRule<RuleTypeParams>> = ({
last_execution_date: executionStatus.lastExecutionDate,
last_duration: executionStatus.lastDuration,
},
actions: actions.map(({ group, id, actionTypeId, params, frequency }) => ({
actions: actions.map(({ group, id, actionTypeId, params, frequency, uuid }) => ({
group,
id,
params,
@ -72,6 +72,7 @@ const rewriteBodyRes: RewriteResponseCase<SanitizedRule<RuleTypeParams>> = ({
throttle: frequency.throttle,
}
: undefined,
...(uuid && { uuid }),
})),
...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}),
...(nextRun ? { next_run: nextRun } : {}),

View file

@ -24,6 +24,7 @@ export const actionsSchema = schema.arrayOf(
throttle: schema.nullable(schema.string({ validate: validateDurationSchema })),
})
),
uuid: schema.maybe(schema.string()),
}),
{ defaultValue: [] }
);

View file

@ -57,7 +57,7 @@ export const rewriteRule = ({
last_execution_date: executionStatus.lastExecutionDate,
last_duration: executionStatus.lastDuration,
},
actions: actions.map(({ group, id, actionTypeId, params, frequency }) => ({
actions: actions.map(({ group, id, actionTypeId, params, frequency, uuid }) => ({
group,
id,
params,
@ -71,6 +71,7 @@ export const rewriteRule = ({
},
}
: {}),
...(uuid && { uuid }),
})),
...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}),
...(nextRun ? { next_run: nextRun } : {}),

View file

@ -44,6 +44,7 @@ describe('resolveRuleRoute', () => {
params: {
foo: true,
},
uuid: '123-456',
},
],
consumer: 'bar',
@ -97,6 +98,7 @@ describe('resolveRuleRoute', () => {
id: mockedRule.actions[0].id,
params: mockedRule.actions[0].params,
connector_type_id: mockedRule.actions[0].actionTypeId,
uuid: mockedRule.actions[0].uuid,
},
],
outcome: 'aliasMatch',

View file

@ -42,6 +42,7 @@ describe('updateRuleRoute', () => {
updatedAt: new Date(),
actions: [
{
uuid: '1234-5678',
group: 'default',
id: '2',
actionTypeId: 'test',
@ -58,6 +59,7 @@ describe('updateRuleRoute', () => {
notify_when: mockedAlert.notifyWhen,
actions: [
{
uuid: '1234-5678',
group: mockedAlert.actions[0].group,
id: mockedAlert.actions[0].id,
params: mockedAlert.actions[0].params,
@ -114,6 +116,7 @@ describe('updateRuleRoute', () => {
"params": Object {
"baz": true,
},
"uuid": "1234-5678",
},
],
"name": "abc",

View file

@ -0,0 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { v4 } from 'uuid';
import { NormalizedAlertAction, NormalizedAlertActionWithUuid } from '..';
export function addUuid(actions: NormalizedAlertAction[] = []): NormalizedAlertActionWithUuid[] {
return actions.map((action) => ({
...action,
uuid: action.uuid || v4(),
}));
}

View file

@ -8,12 +8,11 @@
import { SavedObjectReference } from '@kbn/core/server';
import { RawRule } from '../../types';
import { preconfiguredConnectorActionRefPrefix } from '../common/constants';
import { RulesClientContext } from '../types';
import { NormalizedAlertAction } from '../types';
import { NormalizedAlertActionWithUuid, RulesClientContext } from '../types';
export async function denormalizeActions(
context: RulesClientContext,
alertActions: NormalizedAlertAction[]
alertActions: NormalizedAlertActionWithUuid[]
): Promise<{ actions: RawRule['actions']; references: SavedObjectReference[] }> {
const references: SavedObjectReference[] = [];
const actions: RawRule['actions'] = [];

View file

@ -8,7 +8,7 @@
import { SavedObjectReference } from '@kbn/core/server';
import { RawRule, RuleTypeParams } from '../../types';
import { UntypedNormalizedRuleType } from '../../rule_type_registry';
import { NormalizedAlertAction } from '../types';
import { NormalizedAlertActionWithUuid } from '../types';
import { extractedSavedObjectParamReferenceNamePrefix } from '../common/constants';
import { RulesClientContext } from '../types';
import { denormalizeActions } from './denormalize_actions';
@ -19,7 +19,7 @@ export async function extractReferences<
>(
context: RulesClientContext,
ruleType: UntypedNormalizedRuleType,
ruleActions: NormalizedAlertAction[],
ruleActions: NormalizedAlertActionWithUuid[],
ruleParams: Params
): Promise<{
actions: RawRule['actions'];

View file

@ -15,3 +15,4 @@ export { checkAuthorizationAndGetTotal } from './check_authorization_and_get_tot
export { scheduleTask } from './schedule_task';
export { createNewAPIKeySet } from './create_new_api_key_set';
export { recoverRuleAlerts } from './recover_rule_alerts';
export { addUuid } from './add_uuid';

View file

@ -31,6 +31,15 @@ export async function validateActions(
const errors = [];
const uniqueActions = new Set(actions.map((action) => action.uuid));
if (uniqueActions.size < actions.length) {
errors.push(
i18n.translate('xpack.alerting.rulesClient.validateActions.hasDuplicatedUuid', {
defaultMessage: 'Actions have duplicated UUIDs',
})
);
}
// check for actions using connectors with missing secrets
const actionsClient = await context.getActionsClient();
const actionIds = [...new Set(actions.map((action) => action.id))];

View file

@ -54,13 +54,14 @@ import {
API_KEY_GENERATE_CONCURRENCY,
} from '../common/constants';
import { getMappedParams } from '../common/mapped_params_utils';
import { getAlertFromRaw, extractReferences, validateActions, updateMeta } from '../lib';
import { getAlertFromRaw, extractReferences, validateActions, updateMeta, addUuid } from '../lib';
import {
NormalizedAlertAction,
BulkOperationError,
RuleBulkOperationAggregation,
RulesClientContext,
CreateAPIKeyResult,
NormalizedAlertActionWithUuid,
} from '../types';
export type BulkEditFields = keyof Pick<
@ -452,7 +453,7 @@ async function updateRuleAttributesAndParamsInMemory<Params extends RuleTypePara
} = await extractReferences(
context,
ruleType,
ruleActions.actions,
ruleActions.actions as NormalizedAlertActionWithUuid[],
validatedMutatedAlertTypeParams
);
@ -527,7 +528,11 @@ async function getUpdatedAttributesFromOperations(
) {
let attributes = cloneDeep(rule.attributes);
let ruleActions = {
actions: injectReferencesIntoActions(rule.id, rule.attributes.actions, rule.references || []),
actions: injectReferencesIntoActions(
rule.id,
rule.attributes.actions || [],
rule.references || []
),
};
let hasUpdateApiKeyOperation = false;
@ -540,23 +545,28 @@ async function getUpdatedAttributesFromOperations(
// the `isAttributesUpdateSkipped` flag to false.
switch (operation.field) {
case 'actions': {
const updatedOperation = {
...operation,
value: addUuid(operation.value),
};
try {
await validateActions(context, ruleType, {
...attributes,
actions: operation.value,
actions: updatedOperation.value,
});
} catch (e) {
// If validateActions fails on the first attempt, it may be because of legacy rule-level frequency params
attributes = await attemptToMigrateLegacyFrequency(
context,
operation,
updatedOperation,
attributes,
ruleType
);
}
const { modifiedAttributes, isAttributeModified } = applyBulkEditOperation(
operation,
updatedOperation,
ruleActions
);
if (isAttributeModified) {
@ -566,7 +576,7 @@ async function getUpdatedAttributesFromOperations(
// TODO https://github.com/elastic/kibana/issues/148414
// If any action-level frequencies get pushed into a SIEM rule, strip their frequencies
const firstFrequency = operation.value[0]?.frequency;
const firstFrequency = updatedOperation.value[0]?.frequency;
if (rule.attributes.consumer === AlertConsumers.SIEM && firstFrequency) {
ruleActions.actions = ruleActions.actions.map((action) => omit(action, 'frequency'));
if (!attributes.notifyWhen) {

View file

@ -11,16 +11,15 @@ import { AlertConsumers } from '@kbn/rule-data-utils';
import { SavedObjectsUtils } from '@kbn/core/server';
import { withSpan } from '@kbn/apm-utils';
import { parseDuration } from '../../../common/parse_duration';
import { RawRule, SanitizedRule, RuleTypeParams, RuleAction, Rule } from '../../types';
import { RawRule, SanitizedRule, RuleTypeParams, Rule } from '../../types';
import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization';
import { validateRuleTypeParams, getRuleNotifyWhenType, getDefaultMonitoring } from '../../lib';
import { getRuleExecutionStatusPending } from '../../lib/rule_execution_status';
import { createRuleSavedObject, extractReferences, validateActions } from '../lib';
import { createRuleSavedObject, extractReferences, validateActions, addUuid } from '../lib';
import { generateAPIKeyName, getMappedParams, apiKeyAsAlertAttributes } from '../common';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { RulesClientContext } from '../types';
import { NormalizedAlertAction, RulesClientContext } from '../types';
type NormalizedAlertAction = Omit<RuleAction, 'actionTypeId'>;
interface SavedObjectOptions {
id?: string;
migrationVersion?: Record<string, string>;
@ -51,8 +50,10 @@ export interface CreateOptions<Params extends RuleTypeParams> {
export async function create<Params extends RuleTypeParams = never>(
context: RulesClientContext,
{ data, options, allowMissingConnectorSecrets }: CreateOptions<Params>
{ data: initialData, options, allowMissingConnectorSecrets }: CreateOptions<Params>
): Promise<SanitizedRule<Params>> {
const data = { ...initialData, actions: addUuid(initialData.actions) };
const id = options?.id || SavedObjectsUtils.generateId();
try {
@ -105,7 +106,6 @@ export async function create<Params extends RuleTypeParams = never>(
}
}
await validateActions(context, ruleType, data, allowMissingConnectorSecrets);
await withSpan({ name: 'validateActions', type: 'rules' }, () =>
validateActions(context, ruleType, data, allowMissingConnectorSecrets)
);

View file

@ -24,7 +24,13 @@ import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_key
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { getMappedParams } from '../common/mapped_params_utils';
import { NormalizedAlertAction, RulesClientContext } from '../types';
import { validateActions, extractReferences, updateMeta, getPartialRuleFromRaw } from '../lib';
import {
validateActions,
extractReferences,
updateMeta,
getPartialRuleFromRaw,
addUuid,
} from '../lib';
import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common';
export interface UpdateOptions<Params extends RuleTypeParams> {
@ -143,9 +149,11 @@ async function updateWithOCC<Params extends RuleTypeParams>(
async function updateAlert<Params extends RuleTypeParams>(
context: RulesClientContext,
{ id, data, allowMissingConnectorSecrets }: UpdateOptions<Params>,
{ id, data: initialData, allowMissingConnectorSecrets }: UpdateOptions<Params>,
{ attributes, version }: SavedObject<RawRule>
): Promise<PartialRule<Params>> {
const data = { ...initialData, actions: addUuid(initialData.actions) };
const ruleType = context.ruleTypeRegistry.get(attributes.alertTypeId);
// TODO https://github.com/elastic/kibana/issues/148414

View file

@ -20,6 +20,7 @@ import { ActionsAuthorization, ActionsClient } from '@kbn/actions-plugin/server'
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
import { getBeforeSetup, setGlobalDate } from './lib';
import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation';
import { NormalizedAlertAction } from '../types';
jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({
bulkMarkApiKeysForInvalidation: jest.fn(),
@ -29,6 +30,11 @@ jest.mock('../../lib/snooze/is_snooze_active', () => ({
isSnoozeActive: jest.fn(),
}));
jest.mock('uuid', () => {
let uuid = 100;
return { v4: () => `${uuid++}` };
});
const { isSnoozeActive } = jest.requireMock('../../lib/snooze/is_snooze_active');
const taskManager = taskManagerMock.createStart();
@ -115,7 +121,29 @@ describe('bulkEdit()', () => {
beforeEach(async () => {
rulesClient = new RulesClient(rulesClientParams);
actionsClient = (await rulesClientParams.getActionsClient()) as jest.Mocked<ActionsClient>;
actionsClient.getBulk.mockReset();
actionsClient.getBulk.mockResolvedValue([
{
id: '1',
actionTypeId: 'test',
config: {
from: 'me@me.com',
hasAuth: false,
host: 'hello',
port: 22,
secure: null,
service: null,
},
isMissingSecrets: false,
name: 'email connector',
isPreconfigured: false,
isDeprecated: false,
},
]);
rulesClientParams.getActionsClient.mockResolvedValue(actionsClient);
authorization.getFindAuthorizationFilter.mockResolvedValue({
ensureRuleTypeIsAuthorized() {},
});
@ -426,6 +454,146 @@ describe('bulkEdit()', () => {
});
});
describe('actions operations', () => {
beforeEach(() => {
mockCreatePointInTimeFinderAsInternalUser({
saved_objects: [existingDecryptedRule],
});
});
test('should add uuid to new actions', async () => {
const existingAction = {
frequency: {
notifyWhen: 'onActiveAlert',
summary: false,
throttle: null,
},
group: 'default',
id: '1',
params: {},
uuid: '111',
};
const newAction = {
frequency: {
notifyWhen: 'onActiveAlert',
summary: false,
throttle: null,
},
group: 'default',
id: '2',
params: {},
};
const newAction2 = {
frequency: {
notifyWhen: 'onActiveAlert',
summary: false,
throttle: null,
},
group: 'default',
id: '3',
params: {},
};
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [
{
...existingRule,
attributes: {
...existingRule.attributes,
actions: [
{
...existingAction,
actionRef: 'action_0',
},
{
...newAction,
actionRef: 'action_1',
uuid: '222',
},
],
},
references: [
{
name: 'action_0',
type: 'action',
id: '1',
},
{
name: 'action_1',
type: 'action',
id: '2',
},
],
},
],
});
const result = await rulesClient.bulkEdit({
filter: '',
operations: [
{
field: 'actions',
operation: 'add',
value: [existingAction, newAction, newAction2] as NormalizedAlertAction[],
},
],
});
expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith(
[
{
...existingRule,
attributes: {
...existingRule.attributes,
actions: [
{
actionRef: 'action_0',
actionTypeId: 'test',
frequency: { notifyWhen: 'onActiveAlert', summary: false, throttle: null },
group: 'default',
params: {},
uuid: '111',
},
{
actionRef: '',
actionTypeId: '',
frequency: { notifyWhen: 'onActiveAlert', summary: false, throttle: null },
group: 'default',
params: {},
uuid: '100',
},
{
actionRef: '',
actionTypeId: '',
frequency: { notifyWhen: 'onActiveAlert', summary: false, throttle: null },
group: 'default',
params: {},
uuid: '101',
},
],
apiKey: null,
apiKeyOwner: null,
meta: { versionApiKeyLastmodified: 'v8.2.0' },
name: 'my rule name',
enabled: false,
updatedAt: '2019-02-12T21:01:22.479Z',
updatedBy: 'elastic',
tags: ['foo'],
},
references: [{ id: '1', name: 'action_0', type: 'action' }],
},
],
{ overwrite: true }
);
expect(result.rules[0]).toEqual({
...existingRule.attributes,
actions: [existingAction, { ...newAction, uuid: '222' }],
id: existingRule.id,
snoozeSchedule: [],
});
});
});
describe('index pattern operations', () => {
beforeEach(() => {
mockCreatePointInTimeFinderAsInternalUser({

View file

@ -38,6 +38,11 @@ jest.mock('@kbn/core-saved-objects-utils-server', () => {
};
});
jest.mock('uuid', () => {
let uuid = 100;
return { v4: () => `${uuid++}` };
});
const taskManager = taskManagerMock.createStart();
const ruleTypeRegistry = ruleTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
@ -406,6 +411,7 @@ describe('create()', () => {
"params": Object {
"foo": true,
},
"uuid": "102",
},
],
"alertTypeId": "123",
@ -624,6 +630,7 @@ describe('create()', () => {
"params": Object {
"foo": true,
},
"uuid": "104",
},
],
"alertTypeId": "123",
@ -1053,6 +1060,7 @@ describe('create()', () => {
params: {
foo: true,
},
uuid: '108',
},
{
group: 'default',
@ -1061,6 +1069,7 @@ describe('create()', () => {
params: {
foo: true,
},
uuid: '109',
},
{
group: 'default',
@ -1069,6 +1078,7 @@ describe('create()', () => {
params: {
foo: true,
},
uuid: '110',
},
],
alertTypeId: '123',
@ -1272,7 +1282,13 @@ describe('create()', () => {
'alert',
{
actions: [
{ actionRef: 'action_0', actionTypeId: 'test', group: 'default', params: { foo: true } },
{
actionRef: 'action_0',
actionTypeId: 'test',
group: 'default',
params: { foo: true },
uuid: '112',
},
],
alertTypeId: '123',
apiKey: null,
@ -1445,7 +1461,13 @@ describe('create()', () => {
'alert',
{
actions: [
{ actionRef: 'action_0', actionTypeId: 'test', group: 'default', params: { foo: true } },
{
actionRef: 'action_0',
actionTypeId: 'test',
group: 'default',
params: { foo: true },
uuid: '113',
},
],
alertTypeId: '123',
apiKey: null,
@ -1612,6 +1634,7 @@ describe('create()', () => {
group: 'default',
actionTypeId: 'test',
params: { foo: true },
uuid: '115',
},
],
alertTypeId: '123',
@ -1747,6 +1770,7 @@ describe('create()', () => {
group: 'default',
actionTypeId: 'test',
params: { foo: true },
uuid: '116',
},
],
legacyId: null,
@ -1882,6 +1906,7 @@ describe('create()', () => {
group: 'default',
actionTypeId: 'test',
params: { foo: true },
uuid: '117',
},
],
legacyId: null,
@ -2044,6 +2069,7 @@ describe('create()', () => {
},
actionRef: 'action_0',
actionTypeId: 'test',
uuid: '118',
},
],
apiKeyOwner: null,
@ -2409,6 +2435,7 @@ describe('create()', () => {
group: 'default',
actionTypeId: 'test',
params: { foo: true },
uuid: '126',
},
],
alertTypeId: '123',
@ -2513,6 +2540,7 @@ describe('create()', () => {
group: 'default',
actionTypeId: 'test',
params: { foo: true },
uuid: '127',
},
],
legacyId: null,

View file

@ -37,6 +37,11 @@ jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation
bulkMarkApiKeysForInvalidation: jest.fn(),
}));
jest.mock('uuid', () => {
let uuid = 100;
return { v4: () => `${uuid++}` };
});
const bulkMarkApiKeysForInvalidationMock = bulkMarkApiKeysForInvalidation as jest.Mock;
const taskManager = taskManagerMock.createStart();
const ruleTypeRegistry = ruleTypeRegistryMock.create();
@ -350,6 +355,7 @@ describe('update()', () => {
"params": Object {
"foo": true,
},
"uuid": "100",
},
Object {
"actionRef": "action_1",
@ -358,6 +364,7 @@ describe('update()', () => {
"params": Object {
"foo": true,
},
"uuid": "101",
},
Object {
"actionRef": "action_2",
@ -366,6 +373,7 @@ describe('update()', () => {
"params": Object {
"foo": true,
},
"uuid": "102",
},
],
"alertTypeId": "myType",
@ -585,6 +593,7 @@ describe('update()', () => {
params: {
foo: true,
},
uuid: '103',
},
{
group: 'default',
@ -593,6 +602,7 @@ describe('update()', () => {
params: {
foo: true,
},
uuid: '104',
},
{
group: 'custom',
@ -601,6 +611,7 @@ describe('update()', () => {
params: {
foo: true,
},
uuid: '105',
},
],
alertTypeId: 'myType',
@ -779,7 +790,13 @@ describe('update()', () => {
'alert',
{
actions: [
{ actionRef: 'action_0', actionTypeId: 'test', group: 'default', params: { foo: true } },
{
actionRef: 'action_0',
actionTypeId: 'test',
group: 'default',
params: { foo: true },
uuid: '106',
},
],
alertTypeId: 'myType',
apiKey: null,
@ -953,6 +970,7 @@ describe('update()', () => {
"params": Object {
"foo": true,
},
"uuid": "107",
},
],
"alertTypeId": "myType",
@ -1101,6 +1119,7 @@ describe('update()', () => {
"params": Object {
"foo": true,
},
"uuid": "108",
},
],
"alertTypeId": "myType",
@ -2113,6 +2132,7 @@ describe('update()', () => {
params: {
foo: true,
},
uuid: '144',
},
],
alertTypeId: 'myType',
@ -2522,4 +2542,166 @@ describe('update()', () => {
);
});
});
test('updates an action with uuid and adds uuid to an action without it', async () => {
actionsClient.getBulk.mockReset();
actionsClient.getBulk.mockResolvedValue([
{
id: '1',
actionTypeId: 'test',
config: {
from: 'me@me.com',
hasAuth: false,
host: 'hello',
port: 22,
secure: null,
service: null,
},
isMissingSecrets: false,
name: 'email connector',
isPreconfigured: false,
isDeprecated: false,
},
]);
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
enabled: true,
schedule: { interval: '1m' },
params: {
bar: true,
},
actions: [
{
group: 'default',
actionRef: 'action_0',
actionTypeId: 'test',
params: {
foo: true,
},
frequency: {
notifyWhen: 'onActiveAlert',
throttle: null,
summary: false,
},
uuid: '123-456',
},
{
group: 'default',
actionRef: 'action_1',
actionTypeId: 'test',
params: {
foo: true,
},
frequency: {
notifyWhen: 'onActiveAlert',
throttle: null,
summary: false,
},
uuid: '111-111',
},
],
scheduledTaskId: 'task-123',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
references: [
{
name: 'action_0',
type: 'action',
id: '1',
},
{
name: 'action_1',
type: 'action',
id: '2',
},
],
});
await rulesClient.update({
id: '1',
data: {
schedule: { interval: '1m' },
name: 'abc',
tags: ['foo'],
params: {
bar: true,
risk_score: 40,
severity: 'low',
},
actions: [
{
group: 'default',
id: '1',
params: {
foo: true,
},
frequency: {
notifyWhen: 'onActiveAlert',
throttle: null,
summary: false,
},
uuid: '123-456',
},
{
group: 'default',
id: '2',
params: {
foo: true,
},
frequency: {
notifyWhen: 'onActiveAlert',
throttle: null,
summary: false,
},
},
],
},
});
expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith(
'alert',
{
actions: [
{
actionRef: 'action_0',
actionTypeId: 'test',
frequency: { notifyWhen: 'onActiveAlert', summary: false, throttle: null },
group: 'default',
params: { foo: true },
uuid: '123-456',
},
{
actionRef: '',
actionTypeId: '',
frequency: { notifyWhen: 'onActiveAlert', summary: false, throttle: null },
group: 'default',
params: { foo: true },
uuid: '151',
},
],
alertTypeId: 'myType',
apiKey: null,
apiKeyOwner: null,
consumer: 'myApp',
enabled: true,
mapped_params: { risk_score: 40, severity: '20-low' },
meta: { versionApiKeyLastmodified: 'v7.10.0' },
name: 'abc',
notifyWhen: null,
params: { bar: true, risk_score: 40, severity: 'low' },
schedule: { interval: '1m' },
scheduledTaskId: 'task-123',
tags: ['foo'],
updatedAt: '2019-02-12T21:01:22.479Z',
updatedBy: 'elastic',
},
{
id: '1',
overwrite: true,
references: [{ id: '1', name: 'action_0', type: 'action' }],
version: '123',
}
);
});
});

View file

@ -73,6 +73,10 @@ export interface RulesClientContext {
export type NormalizedAlertAction = Omit<RuleAction, 'actionTypeId'>;
export type NormalizedAlertActionWithUuid = Omit<RuleAction, 'actionTypeId' | 'uuid'> & {
uuid: string;
};
export interface RegistryAlertTypeWithAuth extends RegistryRuleType {
authorizedConsumers: string[];
}

View file

@ -41,6 +41,7 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = {
type: 'keyword',
},
actions: {
dynamic: false,
type: 'nested',
properties: {
group: {

View file

@ -7,6 +7,7 @@
import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server';
import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server';
import { v4 as uuidv4 } from 'uuid';
import { extractedSavedObjectParamReferenceNamePrefix } from '../../../rules_client/common/constants';
import {
createEsoMigration,
@ -37,6 +38,27 @@ function addGroupByToEsQueryRule(
return doc;
}
function addActionUuid(
doc: SavedObjectUnsanitizedDoc<RawRule>
): SavedObjectUnsanitizedDoc<RawRule> {
const {
attributes: { actions },
} = doc;
return {
...doc,
attributes: {
...doc.attributes,
actions: actions
? actions.map((action) => ({
...action,
uuid: uuidv4(),
}))
: [],
},
};
}
function addLogViewRefToLogThresholdRule(
doc: SavedObjectUnsanitizedDoc<RawRule>
): SavedObjectUnsanitizedDoc<RawRule> {
@ -94,5 +116,10 @@ export const getMigrations870 = (encryptedSavedObjects: EncryptedSavedObjectsPlu
createEsoMigration(
encryptedSavedObjects,
(doc: SavedObjectUnsanitizedDoc<RawRule>): doc is SavedObjectUnsanitizedDoc<RawRule> => true,
pipeMigrations(addGroupByToEsQueryRule, addLogViewRefToLogThresholdRule, addOutcomeOrder)
pipeMigrations(
addGroupByToEsQueryRule,
addLogViewRefToLogThresholdRule,
addOutcomeOrder,
addActionUuid
)
);

View file

@ -8,7 +8,7 @@
import sinon from 'sinon';
import { v4 as uuidv4 } from 'uuid';
import { getMigrations } from '.';
import { RawRule } from '../../types';
import { RawRule, RawRuleAction } from '../../types';
import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from '@kbn/core/server';
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
import { migrationMocks } from '@kbn/core/server/mocks';
@ -2628,6 +2628,28 @@ describe('successful migrations', () => {
outcomeOrder: 0,
});
});
test('adds uuid to rule actions', () => {
const migration870 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.7.0'];
const rule = getMockData(
{
params: { foo: true },
alertTypeId: '.not-es-query',
},
true
);
const migratedAlert870 = migration870(rule, migrationContext);
expect(migratedAlert870.attributes.actions).toEqual([
{
group: 'default',
actionRef: '1',
actionTypeId: '1',
params: { foo: true },
uuid: expect.stringMatching(/.*\S.*/), // non-empty string
},
]);
});
});
describe('Metrics Inventory Threshold rule', () => {
@ -2974,7 +2996,7 @@ function getMockData(
params: {
foo: true,
},
},
} as unknown as RawRuleAction,
],
...overwrites,
},

View file

@ -50,6 +50,7 @@ export const RULE_ACTIONS = [
params: {
foo: true,
},
uuid: '111-111',
},
{
actionTypeId: 'action',
@ -58,6 +59,7 @@ export const RULE_ACTIONS = [
params: {
isResolved: true,
},
uuid: '222-222',
},
];
@ -190,6 +192,7 @@ export const mockedRuleTypeSavedObject: Rule<RuleTypeParams> = {
params: {
foo: true,
},
uuid: '111-111',
},
{
group: RecoveredActionGroup.id,
@ -198,6 +201,7 @@ export const mockedRuleTypeSavedObject: Rule<RuleTypeParams> = {
params: {
isResolved: true,
},
uuid: '222-222',
},
],
executionStatus: {

View file

@ -20,6 +20,7 @@ const mockOldAction: RuleAction = {
group: 'default',
actionTypeId: 'slack',
params: {},
uuid: '123-456',
};
const mockAction: RuleAction = {
@ -32,6 +33,7 @@ const mockAction: RuleAction = {
notifyWhen: 'onActiveAlert',
throttle: null,
},
uuid: '123-456',
};
const mockSummaryAction: RuleAction = {

View file

@ -1284,6 +1284,7 @@ describe('Task Runner', () => {
params: {
foo: true,
},
uuid: '111-111',
},
{
group: recoveryActionGroup.id,
@ -1292,6 +1293,7 @@ describe('Task Runner', () => {
params: {
isResolved: true,
},
uuid: '222-222',
},
],
});
@ -1388,6 +1390,7 @@ describe('Task Runner', () => {
params: {
foo: true,
},
uuid: '111-111',
},
],
});
@ -1464,6 +1467,7 @@ describe('Task Runner', () => {
params: {
foo: true,
},
uuid: '111-111',
},
],
});

View file

@ -235,6 +235,7 @@ export type UntypedRuleType = RuleType<
>;
export interface RawRuleAction extends SavedObjectAttributes {
uuid: string;
group: string;
actionRef: string;
actionTypeId: string;

View file

@ -36,6 +36,7 @@ describe('transform_actions', () => {
group: 'group',
actionTypeId: 'actionTypeId',
params: {},
uuid: '111',
};
const ruleAction = transformAlertToRuleAction(alertAction);
expect(ruleAction).toEqual({
@ -43,6 +44,7 @@ describe('transform_actions', () => {
group: alertAction.group,
action_type_id: alertAction.actionTypeId,
params: alertAction.params,
uuid: '111',
});
});
test('it should transform ResponseAction[] to RuleResponseAction[]', () => {

View file

@ -12,13 +12,15 @@ import type { RuleAlertAction } from './types';
export const transformRuleToAlertAction = ({
group,
id,
action_type_id, // eslint-disable-line @typescript-eslint/naming-convention
action_type_id: actionTypeId,
params,
uuid,
}: RuleAlertAction): RuleAction => ({
group,
id,
params,
actionTypeId: action_type_id,
actionTypeId,
...(uuid && { uuid }),
});
export const transformAlertToRuleAction = ({
@ -26,11 +28,13 @@ export const transformAlertToRuleAction = ({
id,
actionTypeId,
params,
uuid,
}: RuleAction): RuleAlertAction => ({
group,
id,
params,
action_type_id: actionTypeId,
...(uuid && { uuid }),
});
export const transformRuleToAlertResponseAction = ({

View file

@ -43,6 +43,7 @@ describe('cloneRule', () => {
summary: false,
},
connector_type_id: '.server-log',
uuid: '123456',
},
],
scheduled_task_id: '1',
@ -74,6 +75,7 @@ describe('cloneRule', () => {
"level": "info",
"message": "alert ",
},
"uuid": "123456",
},
],
"activeSnoozes": undefined,

View file

@ -9,6 +9,7 @@ import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common';
import { Rule, RuleAction, ResolvedRule, RuleLastRun } from '../../../types';
const transformAction: RewriteRequestCase<RuleAction> = ({
uuid,
group,
id,
connector_type_id: actionTypeId,
@ -28,6 +29,7 @@ const transformAction: RewriteRequestCase<RuleAction> = ({
},
}
: {}),
...(uuid && { uuid }),
});
const transformExecutionStatus: RewriteRequestCase<RuleExecutionStatus> = ({

View file

@ -17,7 +17,7 @@ type RuleUpdatesBody = Pick<
>;
const rewriteBodyRequest: RewriteResponseCase<RuleUpdatesBody> = ({ actions, ...res }): any => ({
...res,
actions: actions.map(({ group, id, params, frequency }) => ({
actions: actions.map(({ group, id, params, frequency, uuid }) => ({
group,
id,
params,
@ -26,6 +26,7 @@ const rewriteBodyRequest: RewriteResponseCase<RuleUpdatesBody> = ({ actions, ...
throttle: frequency!.throttle,
summary: frequency!.summary,
},
...(uuid && { uuid }),
})),
});

View file

@ -345,6 +345,7 @@ describe('getRuleWithInvalidatedFields', () => {
field: {},
},
},
uuid: '123-456',
},
],
tags: [],
@ -390,6 +391,7 @@ describe('getRuleWithInvalidatedFields', () => {
field: {},
},
},
uuid: '111-111',
},
{
actionTypeId: 'test',
@ -402,6 +404,7 @@ describe('getRuleWithInvalidatedFields', () => {
},
},
},
uuid: '222-222',
},
],
tags: [],

View file

@ -45,6 +45,7 @@ describe('connectors_selection', () => {
group: 'group',
class: 'test class',
},
uuid: '123-456',
};
const actionTypeIndex: Record<string, ActionType> = {

View file

@ -111,6 +111,7 @@ describe('rule reducer', () => {
actionTypeId: 'testId',
group: 'Rule',
params: {},
uuid: '123-456',
});
const updatedRule = ruleReducer(
{ rule: initialRule },
@ -151,6 +152,7 @@ describe('rule reducer', () => {
params: {
testActionParam: 'some value',
},
uuid: '123-456',
});
const updatedRule = ruleReducer(
{ rule: initialRule },
@ -172,6 +174,7 @@ describe('rule reducer', () => {
actionTypeId: 'testId',
group: 'Rule',
params: {},
uuid: '123-456',
});
const updatedRule = ruleReducer(
{ rule: initialRule },
@ -193,6 +196,7 @@ describe('rule reducer', () => {
actionTypeId: 'testId',
group: 'Rule',
params: {},
uuid: '123-456',
});
const updatedRule = ruleReducer(
{ rule: initialRule },

View file

@ -106,6 +106,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
connector_type_id: createdAction.connector_type_id,
group: 'default',
params: {},
uuid: response.body.actions[0].uuid,
},
],
enabled: true,

View file

@ -321,6 +321,7 @@ const findTestUtils = (
group: 'default',
connector_type_id: 'test.noop',
params: {},
uuid: createdAlert.actions[0].uuid,
},
],
params: {},

View file

@ -74,12 +74,13 @@ export default function alertTests({ getService }: FtrProviderContext) {
updatedBy: user.fullName,
actions: actions.map((action: any) => {
/* eslint-disable @typescript-eslint/naming-convention */
const { connector_type_id, group, id, params } = action;
const { connector_type_id, group, id, params, uuid } = action;
return {
actionTypeId: connector_type_id,
group,
id,
params,
uuid,
};
}),
producer: 'alertsFixture',
@ -421,12 +422,13 @@ instanceStateValue: true
updatedBy: Superuser.fullName,
actions: response2.body.actions.map((action: any) => {
/* eslint-disable @typescript-eslint/naming-convention */
const { connector_type_id, group, id, params } = action;
const { connector_type_id, group, id, params, uuid } = action;
return {
actionTypeId: connector_type_id,
group,
id,
params,
uuid,
};
}),
producer: 'alertsFixture',

View file

@ -127,6 +127,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
connector_type_id: 'test.noop',
group: 'default',
params: {},
uuid: response.body.actions[0].uuid,
},
],
scheduled_task_id: createdAlert.scheduled_task_id,

View file

@ -116,6 +116,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
group: 'default',
params: {},
connector_type_id: 'test.noop',
uuid: response.body.rules[0].actions[0].uuid,
},
]);
expect(response.statusCode).to.eql(200);

View file

@ -145,6 +145,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
connector_type_id: response.body.actions[0].connector_type_id,
group: 'default',
params: {},
uuid: response.body.actions[0].uuid,
},
],
enabled: true,

View file

@ -113,12 +113,13 @@ export function alertTests({ getService }: FtrProviderContext, space: Space) {
updatedBy: null,
actions: response.body.actions.map((action: any) => {
/* eslint-disable @typescript-eslint/naming-convention */
const { connector_type_id, group, id, params } = action;
const { connector_type_id, group, id, params, uuid } = action;
return {
actionTypeId: connector_type_id,
group,
id,
params,
uuid,
};
}),
producer: 'alertsFixture',

View file

@ -76,6 +76,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
connector_type_id: createdAction.connector_type_id,
group: 'default',
params: {},
uuid: response.body.actions[0].uuid,
},
],
enabled: true,
@ -169,6 +170,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
connector_type_id: createdAction.connector_type_id,
group: 'default',
params: {},
uuid: response.body.actions[0].uuid,
},
{
id: 'my-slack1',
@ -177,6 +179,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
params: {
message: 'something important happened!',
},
uuid: response.body.actions[1].uuid,
},
],
enabled: true,
@ -219,6 +222,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
actionTypeId: 'test.noop',
group: 'default',
params: {},
uuid: rawActions[0].uuid,
},
{
actionRef: 'preconfigured:my-slack1',
@ -227,6 +231,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
params: {
message: 'something important happened!',
},
uuid: rawActions[1].uuid,
},
]);
@ -479,6 +484,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
actionTypeId: createdAction.connector_type_id,
group: 'default',
params: {},
uuid: response.body.actions[0].uuid,
},
],
enabled: true,

View file

@ -102,6 +102,7 @@ const findTestUtils = (
notify_when: 'onThrottleInterval',
throttle: '1m',
},
uuid: match.actions[0].uuid,
},
],
params: {},

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import expect from '@kbn/expect';
import expect from 'expect';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { RawRule, RawRuleAction } from '@kbn/alerting-plugin/server/types';
import { FILEBEAT_7X_INDICATOR_PATH } from '@kbn/alerting-plugin/server/saved_objects/migrations';
@ -33,8 +33,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
`${getUrlPrefix(``)}/api/alerting/rule/74f3e6d7-b7bb-477d-ac28-92ee22728e6e`
);
expect(response.status).to.eql(200);
expect(response.body.consumer).to.equal('alerts');
expect(response.status).toBe(200);
expect(response.body.consumer).toEqual('alerts');
});
it('7.10.0 migrates the `metrics` consumer to be the `infrastructure`', async () => {
@ -42,8 +42,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
`${getUrlPrefix(``)}/api/alerting/rule/74f3e6d7-b7bb-477d-ac28-fdf248d5f2a4`
);
expect(response.status).to.eql(200);
expect(response.body.consumer).to.equal('infrastructure');
expect(response.status).toEqual(200);
expect(response.body.consumer).toEqual('infrastructure');
});
it('7.10.0 migrates PagerDuty actions to have a default dedupKey', async () => {
@ -51,10 +51,10 @@ export default function createGetTests({ getService }: FtrProviderContext) {
`${getUrlPrefix(``)}/api/alerting/rule/b6087f72-994f-46fb-8120-c6e5c50d0f8f`
);
expect(response.status).to.eql(200);
expect(response.status).toEqual(200);
expect(response.body.actions).to.eql([
{
expect(response.body.actions).toEqual([
expect.objectContaining({
connector_type_id: '.pagerduty',
id: 'a6a8ab7a-35cf-445e-ade3-215a029c2ee3',
group: 'default',
@ -63,8 +63,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
eventAction: 'trigger',
summary: 'fired {{alertInstanceId}}',
},
},
{
}),
expect.objectContaining({
connector_type_id: '.pagerduty',
id: 'a6a8ab7a-35cf-445e-ade3-215a029c2ee3',
group: 'default',
@ -74,8 +74,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
eventAction: 'resolve',
summary: 'fired {{alertInstanceId}}',
},
},
{
}),
expect.objectContaining({
connector_type_id: '.pagerduty',
id: 'a6a8ab7a-35cf-445e-ade3-215a029c2ee3',
group: 'default',
@ -85,7 +85,7 @@ export default function createGetTests({ getService }: FtrProviderContext) {
eventAction: 'resolve',
summary: 'fired {{alertInstanceId}}',
},
},
}),
]);
});
@ -94,8 +94,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
`${getUrlPrefix(``)}/api/alerting/rule/74f3e6d7-b7bb-477d-ac28-92ee22728e6e`
);
expect(response.status).to.eql(200);
expect(response.body.updated_at).to.eql('2020-06-17T15:35:39.839Z');
expect(response.status).toEqual(200);
expect(response.body.updated_at).toEqual('2020-06-17T15:35:39.839Z');
});
it('7.11.0 migrates alerts to contain `notifyWhen` field', async () => {
@ -103,8 +103,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
`${getUrlPrefix(``)}/api/alerting/rule/74f3e6d7-b7bb-477d-ac28-92ee22728e6e`
);
expect(response.status).to.eql(200);
expect(response.body.notify_when).to.eql('onActiveAlert');
expect(response.status).toEqual(200);
expect(response.body.notify_when).toEqual('onActiveAlert');
});
it('7.11.2 migrates alerts with case actions, case fields are nested in an incident object', async () => {
@ -112,9 +112,9 @@ export default function createGetTests({ getService }: FtrProviderContext) {
`${getUrlPrefix(``)}/api/alerting/rule/99f3e6d7-b7bb-477d-ac28-92ee22726969`
);
expect(response.status).to.eql(200);
expect(response.body.actions).to.eql([
{
expect(response.status).toEqual(200);
expect(response.body.actions).toEqual([
expect.objectContaining({
id: '66a8ab7a-35cf-445e-ade3-215a029c6969',
connector_type_id: '.servicenow',
group: 'threshold met',
@ -131,8 +131,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
comments: [{ commentId: '1', comment: 'sn comment' }],
},
},
},
{
}),
expect.objectContaining({
id: '66a8ab7a-35cf-445e-ade3-215a029c6969',
connector_type_id: '.jira',
group: 'threshold met',
@ -155,8 +155,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
],
},
},
},
{
}),
expect.objectContaining({
id: '66a8ab7a-35cf-445e-ade3-215a029c6969',
connector_type_id: '.resilient',
group: 'threshold met',
@ -177,7 +177,7 @@ export default function createGetTests({ getService }: FtrProviderContext) {
],
},
},
},
}),
]);
});
@ -190,8 +190,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.references).to.eql([
expect(response.statusCode).toEqual(200);
expect(response.body._source?.references).toEqual([
{
name: 'param:exceptionsList_0',
id: 'endpoint_list',
@ -219,10 +219,10 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(searchResult.statusCode).to.equal(200);
expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).to.equal(1);
expect(searchResult.statusCode).toEqual(200);
expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).toEqual(1);
const hit = searchResult.body.hits.hits[0];
expect((hit!._source!.alert! as RawRule).legacyId).to.equal(
expect((hit!._source!.alert! as RawRule).legacyId).toEqual(
'74f3e6d7-b7bb-477d-ac28-92ee22728e6e'
);
});
@ -241,31 +241,31 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(searchResult.statusCode).to.equal(200);
expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).to.equal(1);
expect(searchResult.statusCode).toEqual(200);
expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).toEqual(1);
const hit = searchResult.body.hits.hits[0];
expect((hit!._source!.alert! as RawRule).actions! as RawRuleAction[]).to.eql([
{
expect((hit!._source!.alert! as RawRule).actions! as RawRuleAction[]).toEqual([
expect.objectContaining({
actionRef: 'action_0',
actionTypeId: 'test.noop',
group: 'default',
params: {},
},
{
}),
expect.objectContaining({
actionRef: 'preconfigured:my-slack1',
actionTypeId: '.slack',
group: 'default',
params: {
message: 'something happened!',
},
},
}),
]);
expect(hit!._source!.references!).to.eql([
{
expect(hit!._source!.references!).toEqual([
expect.objectContaining({
id: '66a8ab7a-35cf-445e-ade3-215a029c6969',
name: 'action_0',
type: 'action',
},
}),
]);
});
@ -278,8 +278,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.references).to.eql([
expect(response.statusCode).toEqual(200);
expect(response.body._source?.references).toEqual([
{
name: 'param:alert_0',
id: '1a4ed6ae-3c89-44b2-999d-db554144504c',
@ -302,8 +302,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.alert?.params?.threatIndicatorPath).to.eql(
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.params?.threatIndicatorPath).toEqual(
FILEBEAT_7X_INDICATOR_PATH
);
});
@ -322,8 +322,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.alert?.params?.threatIndicatorPath).to.eql(
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.params?.threatIndicatorPath).toEqual(
'custom.indicator.path'
);
});
@ -342,8 +342,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.alert?.params?.threatIndicatorPath).not.to.eql(
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.params?.threatIndicatorPath).not.toEqual(
FILEBEAT_7X_INDICATOR_PATH
);
});
@ -356,8 +356,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.alert?.actions?.[0].group).to.be(
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.actions?.[0].group).toBe(
'metrics.inventory_threshold.fired'
);
});
@ -370,9 +370,9 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.alert?.alertTypeId).to.be('siem.queryRule');
expect(response.body._source?.alert?.enabled).to.be(false);
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.alertTypeId).toBe('siem.queryRule');
expect(response.body._source?.alert?.enabled).toBe(false);
});
it('8.0.1 migrates and adds tags to disabled rules in 8.0', async () => {
@ -383,7 +383,7 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(responseEnabledBeforeMigration.statusCode).to.eql(200);
expect(responseEnabledBeforeMigration.statusCode).toEqual(200);
const responseDisabledBeforeMigration = await es.get<{ alert: RawRule }>(
{
index: '.kibana',
@ -391,17 +391,17 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(responseDisabledBeforeMigration.statusCode).to.eql(200);
expect(responseDisabledBeforeMigration.statusCode).toEqual(200);
// Both should be disabled
expect(responseEnabledBeforeMigration.body._source?.alert?.enabled).to.be(false);
expect(responseDisabledBeforeMigration.body._source?.alert?.enabled).to.be(false);
expect(responseEnabledBeforeMigration.body._source?.alert?.enabled).toBe(false);
expect(responseDisabledBeforeMigration.body._source?.alert?.enabled).toBe(false);
// Only the rule that was enabled should be tagged
expect(responseEnabledBeforeMigration.body._source?.alert?.tags).to.eql([
expect(responseEnabledBeforeMigration.body._source?.alert?.tags).toEqual([
'auto_disabled_8.0',
]);
expect(responseDisabledBeforeMigration.body._source?.alert?.tags).to.eql([]);
expect(responseDisabledBeforeMigration.body._source?.alert?.tags).toEqual([]);
});
it('8.2.0 migrates params to mapped_params for specific params properties', async () => {
@ -413,8 +413,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
{ meta: true }
);
expect(response.statusCode).to.equal(200);
expect(response.body._source?.alert?.mapped_params).to.eql({
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.mapped_params).toEqual({
risk_score: 90,
severity: '80-critical',
});
@ -428,8 +428,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.equal(200);
expect(response.body._source?.alert?.params.searchType).to.eql('esQuery');
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.params.searchType).toEqual('esQuery');
});
it('8.3.0 removes internal tags in Security Solution rule', async () => {
@ -441,8 +441,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
{ meta: true }
);
expect(response.statusCode).to.equal(200);
expect(response.body._source?.alert?.tags).to.eql(['test-tag-1', 'foo-tag']);
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.tags).toEqual(['test-tag-1', 'foo-tag']);
});
it('8.4.1 removes IsSnoozedUntil', async () => {
@ -460,9 +460,9 @@ export default function createGetTests({ getService }: FtrProviderContext) {
{ meta: true }
);
expect(searchResult.statusCode).to.equal(200);
expect(searchResult.statusCode).toEqual(200);
const hit = searchResult.body.hits.hits[0];
expect((hit!._source!.alert! as RawRule).isSnoozedUntil).to.be(undefined);
expect((hit!._source!.alert! as RawRule).isSnoozedUntil).toBe(undefined);
});
it('8.5.0 removes runtime and field params from older ES Query rules', async () => {
@ -479,8 +479,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.alert?.params?.esQuery).to.eql(
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.params?.esQuery).toEqual(
JSON.stringify({ query: { match_all: {} } }, null, 4)
);
});
@ -499,8 +499,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.alert?.params?.esQuery).to.eql(
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.params?.esQuery).toEqual(
'{\n\t"query":\n{\n\t"match_all":\n\t{}\n}\n}'
);
});
@ -519,8 +519,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.alert?.params?.esQuery).to.eql('{"query":}');
expect(response.statusCode).toEqual(200);
expect(response.body._source?.alert?.params?.esQuery).toEqual('{"query":}');
});
it('8.6.0 migrates executionStatus and monitoring', async () => {
@ -533,7 +533,7 @@ export default function createGetTests({ getService }: FtrProviderContext) {
);
const alert = response.body._source?.alert;
expect(alert?.monitoring).to.eql({
expect(alert?.monitoring).toEqual({
run: {
history: [
{
@ -557,7 +557,7 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
});
expect(alert?.lastRun).to.eql({
expect(alert?.lastRun).toEqual({
outcome: 'succeeded',
outcomeMsg: null,
outcomeOrder: 0,
@ -565,7 +565,7 @@ export default function createGetTests({ getService }: FtrProviderContext) {
alertsCount: {},
});
expect(alert?.nextRun).to.eql(undefined);
expect(alert?.nextRun).toEqual(undefined);
});
it('8.6 migrates executionStatus warnings and errors', async () => {
@ -579,9 +579,9 @@ export default function createGetTests({ getService }: FtrProviderContext) {
const alert = response.body._source?.alert;
expect(alert?.lastRun?.outcome).to.eql('warning');
expect(alert?.lastRun?.warning).to.eql('warning reason');
expect(alert?.lastRun?.outcomeMsg).to.eql('warning message');
expect(alert?.lastRun?.outcome).toEqual('warning');
expect(alert?.lastRun?.warning).toEqual('warning reason');
expect(alert?.lastRun?.outcomeMsg).toEqual('warning message');
});
it('8.7.0 adds aggType and groupBy to ES query rules', async () => {
@ -604,10 +604,10 @@ export default function createGetTests({ getService }: FtrProviderContext) {
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.statusCode).toEqual(200);
response.body.hits.hits.forEach((hit) => {
expect((hit?._source?.alert as RawRule)?.params?.aggType).to.eql('count');
expect((hit?._source?.alert as RawRule)?.params?.groupBy).to.eql('all');
expect((hit?._source?.alert as RawRule)?.params?.aggType).toEqual('count');
expect((hit?._source?.alert as RawRule)?.params?.groupBy).toEqual('all');
});
});
@ -635,8 +635,39 @@ export default function createGetTests({ getService }: FtrProviderContext) {
{ meta: true }
);
expect(response.body._source?.alert?.params.logView).to.eql(logView);
expect(response.body._source?.references).to.eql(references);
expect(response.body._source?.alert?.params.logView).toEqual(logView);
expect(response.body._source?.references).toEqual(references);
});
it('8.7 adds uuid to the actions', async () => {
const response = await es.get<{ alert: RawRule }>(
{
index: '.kibana',
id: 'alert:9c003b00-00ee-11ec-b067-2524946ba327',
},
{ meta: true }
);
const alert = response.body._source?.alert;
expect(alert?.actions).toEqual([
{
actionRef: 'action_0',
actionTypeId: 'test.noop',
group: 'default',
params: {},
uuid: expect.any(String),
},
{
actionRef: 'preconfigured:my-slack1',
actionTypeId: '.slack',
group: 'default',
params: {
message: 'something happened!',
},
uuid: expect.any(String),
},
]);
});
});
}

View file

@ -57,7 +57,10 @@ export default ({ getService }: FtrProviderContext) => {
const rule = await createRule(supertest, log, getRuleWithWebHookAction(hookAction.id));
const bodyToCompare = removeServerGeneratedProperties(rule);
expect(bodyToCompare).to.eql(
getSimpleRuleOutputWithWebHookAction(`${bodyToCompare?.actions?.[0].id}`)
getSimpleRuleOutputWithWebHookAction(
`${bodyToCompare?.actions?.[0].id}`,
`${bodyToCompare?.actions?.[0].uuid}`
)
);
});

View file

@ -165,7 +165,10 @@ export default ({ getService }: FtrProviderContext): void => {
const outputRule1: ReturnType<typeof getSimpleRuleOutput> = {
...getSimpleRuleOutput('rule-1'),
actions: [action1, action2],
actions: [
{ ...action1, uuid: firstRule.actions[0].uuid },
{ ...action2, uuid: firstRule.actions[1].uuid },
],
throttle: 'rule',
};
expect(firstRule).to.eql(outputRule1);
@ -213,12 +216,12 @@ export default ({ getService }: FtrProviderContext): void => {
const outputRule1: ReturnType<typeof getSimpleRuleOutput> = {
...getSimpleRuleOutput('rule-2'),
actions: [action],
actions: [{ ...action, uuid: firstRule.actions[0].uuid }],
throttle: 'rule',
};
const outputRule2: ReturnType<typeof getSimpleRuleOutput> = {
...getSimpleRuleOutput('rule-1'),
actions: [action],
actions: [{ ...action, uuid: secondRule.actions[0].uuid }],
throttle: 'rule',
};
expect(firstRule).to.eql(outputRule1);

View file

@ -126,7 +126,7 @@ export default ({ getService }: FtrProviderContext): void => {
const ruleWithActions: ReturnType<typeof getSimpleRuleOutput> = {
...getSimpleRuleOutput(),
actions: [action],
actions: [{ ...action, uuid: body.data[0].actions[0].uuid }],
throttle: 'rule',
};
@ -171,7 +171,7 @@ export default ({ getService }: FtrProviderContext): void => {
const ruleWithActions: ReturnType<typeof getSimpleRuleOutput> = {
...getSimpleRuleOutput(),
actions: [action],
actions: [{ ...action, uuid: body.data[0].actions[0].uuid }],
throttle: '1h', // <-- throttle makes this a scheduled action
};

View file

@ -71,7 +71,10 @@ export default ({ getService }: FtrProviderContext) => {
const bodyToCompare = removeServerGeneratedProperties(updatedRule);
const expected = {
...getSimpleRuleOutputWithWebHookAction(`${bodyToCompare.actions?.[0].id}`),
...getSimpleRuleOutputWithWebHookAction(
`${bodyToCompare.actions?.[0].id}`,
`${bodyToCompare.actions?.[0].uuid}`
),
version: 2, // version bump is required since this is an updated rule and this is part of the testing that we do bump the version number on update
};
expect(bodyToCompare).to.eql(expected);
@ -147,7 +150,10 @@ export default ({ getService }: FtrProviderContext) => {
const updatedRule = await updateRule(supertest, log, ruleToUpdate);
const bodyToCompare = removeServerGeneratedProperties(updatedRule);
const expected = getSimpleRuleOutputWithWebHookAction(`${bodyToCompare.actions?.[0].id}`);
const expected = getSimpleRuleOutputWithWebHookAction(
`${bodyToCompare.actions?.[0].id}`,
`${bodyToCompare.actions?.[0].uuid}`
);
expect(bodyToCompare.actions).to.eql(expected.actions);
expect(bodyToCompare.throttle).to.eql(expected.throttle);
@ -180,7 +186,10 @@ export default ({ getService }: FtrProviderContext) => {
expect(body.data.length).to.eql(1); // should have only one length to the data set, otherwise we have duplicates or the tags were removed and that is incredibly bad.
const bodyToCompare = removeServerGeneratedProperties(body.data[0]);
const expected = getSimpleRuleOutputWithWebHookAction(`${bodyToCompare.actions?.[0].id}`);
const expected = getSimpleRuleOutputWithWebHookAction(
`${bodyToCompare.actions?.[0].id}`,
`${bodyToCompare.actions?.[0].uuid}`
);
expect(bodyToCompare.actions).to.eql(expected.actions);
expect(bodyToCompare.immutable).to.be(true);

View file

@ -121,6 +121,7 @@ export default ({ getService }: FtrProviderContext) => {
subject: 'Test Actions',
to: ['test@test.com'],
},
uuid: ruleSO?.alert.actions[0].uuid,
},
]);
expect(ruleSO?.alert.throttle).to.eql(null);
@ -179,6 +180,7 @@ export default ({ getService }: FtrProviderContext) => {
},
actionRef: 'action_0',
group: 'default',
uuid: ruleSO?.alert.actions[0].uuid,
},
{
actionTypeId: '.slack',
@ -187,6 +189,7 @@ export default ({ getService }: FtrProviderContext) => {
},
actionRef: 'action_1',
group: 'default',
uuid: ruleSO?.alert.actions[1].uuid,
},
]);
expect(ruleSO?.alert.throttle).to.eql('1h');
@ -249,6 +252,7 @@ export default ({ getService }: FtrProviderContext) => {
subject: 'Test Actions',
to: ['test@test.com'],
},
uuid: ruleSO?.alert.actions[0].uuid,
},
]);
expect(ruleSO?.alert.throttle).to.eql('1d');
@ -306,6 +310,7 @@ export default ({ getService }: FtrProviderContext) => {
subject: 'Test Actions',
to: ['test@test.com'],
},
uuid: ruleSO?.alert.actions[0].uuid,
},
]);
expect(ruleSO?.alert.throttle).to.eql('7d');

View file

@ -359,6 +359,7 @@ export default ({ getService }: FtrProviderContext) => {
.send({ id: rule.id, enabled: false })
.expect(200);
const bodyToCompare = removeServerGeneratedProperties(patchResponse.body);
const outputRule = getSimpleRuleOutput();
outputRule.actions = [
{
@ -369,10 +370,11 @@ export default ({ getService }: FtrProviderContext) => {
message:
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
},
uuid: bodyToCompare.actions[0].uuid,
},
];
outputRule.throttle = '1h';
const bodyToCompare = removeServerGeneratedProperties(patchResponse.body);
expect(bodyToCompare).to.eql(outputRule);
});

View file

@ -181,6 +181,7 @@ export default ({ getService }: FtrProviderContext) => {
// @ts-expect-error
body.forEach((response) => {
const bodyToCompare = removeServerGeneratedProperties(response);
const outputRule = getSimpleRuleOutput(response.rule_id, false);
outputRule.actions = [
{
@ -191,10 +192,10 @@ export default ({ getService }: FtrProviderContext) => {
message:
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
},
uuid: bodyToCompare.actions[0].uuid,
},
];
outputRule.throttle = '1h';
const bodyToCompare = removeServerGeneratedProperties(response);
expect(bodyToCompare).to.eql(outputRule);
});
});

View file

@ -173,6 +173,7 @@ export default ({ getService }: FtrProviderContext): void => {
params: {
body: '{"test":"a default action"}',
},
uuid: rule.actions[0].uuid,
},
],
});
@ -325,6 +326,7 @@ export default ({ getService }: FtrProviderContext): void => {
params: {
message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
},
uuid: ruleBody.actions[0].uuid,
},
]);
});
@ -394,6 +396,7 @@ export default ({ getService }: FtrProviderContext): void => {
params: {
message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
},
uuid: ruleBody.actions[0].uuid,
},
]);
});
@ -484,6 +487,7 @@ export default ({ getService }: FtrProviderContext): void => {
message:
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
},
uuid: rule.actions[0].uuid,
},
]);
});
@ -1113,6 +1117,7 @@ export default ({ getService }: FtrProviderContext): void => {
message:
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
},
uuid: setTagsRule.actions[0].uuid,
},
]);
});
@ -1396,6 +1401,7 @@ export default ({ getService }: FtrProviderContext): void => {
...webHookActionMock,
id: webHookConnector.id,
action_type_id: '.webhook',
uuid: body.attributes.results.updated[0].actions[0].uuid,
},
];
@ -1452,6 +1458,7 @@ export default ({ getService }: FtrProviderContext): void => {
...webHookActionMock,
id: webHookConnector.id,
action_type_id: '.webhook',
uuid: body.attributes.results.updated[0].actions[0].uuid,
},
];
@ -1544,6 +1551,7 @@ export default ({ getService }: FtrProviderContext): void => {
...webHookActionMock,
id: webHookConnector.id,
action_type_id: '.webhook',
uuid: body.attributes.results.updated[0].actions[0].uuid,
},
];
@ -1598,11 +1606,12 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
const expectedRuleActions = [
defaultRuleAction,
{ ...defaultRuleAction, uuid: body.attributes.results.updated[0].actions[0].uuid },
{
...webHookActionMock,
id: webHookConnector.id,
action_type_id: '.webhook',
uuid: body.attributes.results.updated[0].actions[1].uuid,
},
];
@ -1665,11 +1674,12 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
const expectedRuleActions = [
defaultRuleAction,
{ ...defaultRuleAction, uuid: body.attributes.results.updated[0].actions[0].uuid },
{
...slackConnectorMockProps,
id: slackConnector.id,
action_type_id: '.slack',
uuid: body.attributes.results.updated[0].actions[1].uuid,
},
];
@ -1719,12 +1729,16 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(200);
// Check that the updated rule is returned with the response
expect(body.attributes.results.updated[0].actions).to.eql([defaultRuleAction]);
expect(body.attributes.results.updated[0].actions).to.eql([
{ ...defaultRuleAction, uuid: createdRule.actions[0].uuid },
]);
// Check that the updates have been persisted
const { body: readRule } = await fetchRule(ruleId).expect(200);
expect(readRule.actions).to.eql([defaultRuleAction]);
expect(readRule.actions).to.eql([
{ ...defaultRuleAction, uuid: createdRule.actions[0].uuid },
]);
});
it('should change throttle if actions list in payload is empty', async () => {
@ -1816,6 +1830,7 @@ export default ({ getService }: FtrProviderContext): void => {
...webHookActionMock,
id: webHookConnector.id,
action_type_id: '.webhook',
uuid: editedRule.actions[0].uuid,
},
]);
// version of prebuilt rule should not change
@ -1829,6 +1844,7 @@ export default ({ getService }: FtrProviderContext): void => {
...webHookActionMock,
id: webHookConnector.id,
action_type_id: '.webhook',
uuid: readRule.actions[0].uuid,
},
]);
expect(prebuiltRule.version).to.be(readRule.version);

View file

@ -135,7 +135,7 @@ export default ({ getService }: FtrProviderContext) => {
const bodyToCompare = removeServerGeneratedProperties(body);
const ruleWithActions: ReturnType<typeof getSimpleRuleOutput> = {
...getSimpleRuleOutput(),
actions: [action],
actions: [{ ...action, uuid: bodyToCompare.actions[0].uuid }],
throttle: 'rule',
};
expect(bodyToCompare).to.eql(ruleWithActions);
@ -174,7 +174,7 @@ export default ({ getService }: FtrProviderContext) => {
const bodyToCompare = removeServerGeneratedProperties(body);
const ruleWithActions: ReturnType<typeof getSimpleRuleOutput> = {
...getSimpleRuleOutput(),
actions: [action],
actions: [{ ...action, uuid: bodyToCompare.actions[0].uuid }],
throttle: '1h', // <-- throttle makes this a scheduled action
};
expect(bodyToCompare).to.eql(ruleWithActions);

View file

@ -229,6 +229,8 @@ export default ({ getService }: FtrProviderContext) => {
.send(updatedRule)
.expect(200);
const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body);
const outputRule = getSimpleRuleOutputWithoutRuleId();
outputRule.name = 'some other name';
outputRule.version = 2;
@ -241,10 +243,11 @@ export default ({ getService }: FtrProviderContext) => {
message:
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
},
uuid: bodyToCompare.actions![0].uuid,
},
];
outputRule.throttle = '1d';
const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body);
expect(bodyToCompare).to.eql(outputRule);
});

View file

@ -166,6 +166,7 @@ export default ({ getService }: FtrProviderContext) => {
.expect(200);
body.forEach((response) => {
const bodyToCompare = removeServerGeneratedProperties(response);
const outputRule = getSimpleRuleOutput(response.rule_id);
outputRule.name = 'some other name';
outputRule.version = 2;
@ -178,10 +179,11 @@ export default ({ getService }: FtrProviderContext) => {
message:
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
},
uuid: bodyToCompare.actions[0].uuid,
},
];
outputRule.throttle = '1d';
const bodyToCompare = removeServerGeneratedProperties(response);
expect(bodyToCompare).to.eql(outputRule);
});
});

View file

@ -9,7 +9,8 @@ import { getSimpleRuleOutput } from './get_simple_rule_output';
import { RuleWithoutServerGeneratedProperties } from './remove_server_generated_properties';
export const getSimpleRuleOutputWithWebHookAction = (
actionId: string
actionId: string,
uuid: string
): RuleWithoutServerGeneratedProperties => ({
...getSimpleRuleOutput(),
throttle: 'rule',
@ -21,6 +22,7 @@ export const getSimpleRuleOutputWithWebHookAction = (
params: {
body: '{}',
},
uuid,
},
],
});

View file

@ -100,6 +100,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
message: MonitorStatusTranslations.defaultRecoveryMessage,
},
id: 'my-slack1',
uuid: actions[0].uuid,
},
{
actionTypeId: '.slack',
@ -108,6 +109,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
message: MonitorStatusTranslations.defaultActionMessage,
},
id: 'my-slack1',
uuid: actions[1].uuid,
},
]);
expect(alertTypeId).to.eql('xpack.uptime.alerts.monitorStatus');