[RAM] Adds auto-incrementing revision field to rules (#147398)

## Summary

Resolves https://github.com/elastic/kibana/issues/137164. 

This PR adds a new `revision` field (number) to Alerting Rules that
auto-increments when relevant content changes have been made via the
`Rules Client`. _Relevant content changes_ are defined as any content
change that the user may either want to be notified about, or have the
option to later revert to. This will include most all changes, except
the enabling/disabling of the rule, or updates to metadata fields like
`executionStatus` or `monitoring`. This `revision` field is not intended
to be user editable, and should be ignored if ever provided via the API.

See https://github.com/elastic/kibana/issues/136213 for additional
details. To be followed-up by
https://github.com/elastic/kibana/issues/137168, which will remove the
version bump logic from security solution.

## Details

### Migrations

Includes SO migration to default `revision` to `0` when upgrading a
cluster, or when importing `pre-8.8.0` rules via the SO Management UI.
For consistency, security rule import follows the same logic as SO
Management and will not reset the `revision` to `0` when overriding or
creating a new rule.

### Downstream Index Updates

* EventLog _has not_ been updated to include `revision` along with basic
rule fields currently being written. Should we?
* AAD Schema will be updated in
https://github.com/elastic/kibana/pull/151388 (as this one is getting
pretty big) to include `revision` so alerts written will include which
specific revision of the rule created the alert.

### Reference Fields

Any creation of or modification to `actions` will result in a revision
increment. More typical reference fields like `exception lists` on the
security side will only result in a revision increment when the list is
initially associated/deleted from the rule (as subsequent updates will
be done directly against the list).

### RuleClient Updates

The following methods within the RuleClient have been updated to support
incrementing revision when relevant field changes have been detected:

* `clone()` - resets to 0 currently (see open question)
* `update()` - increments `revision` so long a change has been made to
relevant fields (fields not in [ignore
list](https://github.com/elastic/kibana/pull/147398/files#diff-6736e143ede2dc06e825bddcdc23b4d088a6620805751db4eddc5900d586c4dfR69-R85))
* `bulkEdit()` - increments `revision` for relevant fields (all current
bulk edit fields minus api key/snooze/mute)

Mutation methods not updated to include revision log:
* `snooze()`
* `unsnooze()`
*  `clearExpiredSnoozes()`
*  `muteAll()`
*  `unmuteAll()`
*  `muteInstance()`
*  `unmuteInstance()`
* `updateApiKey()` - increments revision as rule functionality could be
impacted


## Open questions:
- [X] Should `clone()` in RulesClient reset revision to 0 as if it's a
new rule, or keep the current value? (see
[comment](https://github.com/elastic/kibana/pull/147398/files#r1106484105))
- [X] What about snooze/un-snooze, and mute/unmute? Should we update
revision on these field changes as well? (see
[comment](https://github.com/elastic/kibana/pull/147398/files#r1106431966))
- Discussed with @XavierM and determined to not update on
snooze/mute/API key changes as this actions could be plentiful and don't
necessarily represent a version of the rule a user would want to revert
to, thus polluting the revision history.
- [ ] Should we write `revision` to EventLog?


---

### Checklist

Delete any items that are not applicable to this PR.

- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
  - [ ] To work with docs team 
- [X] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios


### For maintainers

- [X] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Garrett Spong 2023-03-10 15:12:22 -07:00 committed by GitHub
parent a564ca5fe3
commit 15f1f64ace
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
112 changed files with 663 additions and 20 deletions

View file

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

View file

@ -45,6 +45,7 @@ const mockRule: RuleTableItem = {
ruleType: 'Test Rule Type',
isEditable: true,
enabledInLicense: true,
revision: 0,
};
export const RulesListNotifyBadgeSandbox = ({ triggersActionsUi }: SandboxProps) => {

View file

@ -161,6 +161,7 @@ export interface Rule<Params extends RuleTypeParams = never> {
isSnoozedUntil?: Date | null;
lastRun?: RuleLastRun | null;
nextRun?: Date | null;
revision: number;
running?: boolean | null;
viewInAppRelativeUrl?: string;
}

View file

@ -132,6 +132,7 @@ describe('loadRule', () => {
"params": Object {
"x": 42,
},
"revision": 0,
"schedule": Object {
"interval": "1s",
},
@ -268,6 +269,7 @@ function getApiRule() {
updated_by: '2889684073',
mute_all: false,
muted_alert_ids: [],
revision: 0,
schedule: {
interval: '1s',
},
@ -333,5 +335,6 @@ function getRule(): Rule<{ x: number }> {
lastExecutionDate: RuleExecuteDate,
lastDuration: 1194,
},
revision: 0,
};
}

View file

@ -46,6 +46,7 @@ describe('common_transformations', () => {
notify_when: 'onActiveAlert',
mute_all: false,
muted_alert_ids: ['bob', 'jim'],
revision: 0,
execution_status: {
last_execution_date: dateExecuted.toISOString(),
last_duration: 42,
@ -185,6 +186,7 @@ describe('common_transformations', () => {
],
},
},
"revision": 0,
"schedule": Object {
"interval": "1s",
},
@ -229,6 +231,7 @@ describe('common_transformations', () => {
notify_when: 'onActiveAlert',
mute_all: false,
muted_alert_ids: ['bob', 'jim'],
revision: 0,
execution_status: {
last_execution_date: dateExecuted.toISOString(),
status: 'error',
@ -344,6 +347,7 @@ describe('common_transformations', () => {
"nextRun": 2021-12-15T12:34:55.789Z,
"notifyWhen": "onActiveAlert",
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1s",
},

View file

@ -784,4 +784,5 @@ const BaseRule: SanitizedRule<{ bar: boolean }> = {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
};

View file

@ -59,6 +59,7 @@ describe('bulkEditInternalRulesRoute', () => {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
};
const mockedAlerts: Array<SanitizedRule<{}>> = [mockedAlert];

View file

@ -65,6 +65,7 @@ describe('cloneRuleRoute', () => {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
};
const ruleToClone: AsApiContract<CreateOptions<{ bar: boolean }>['data']> = {
@ -90,6 +91,7 @@ describe('cloneRuleRoute', () => {
created_at: mockedRule.createdAt,
updated_at: mockedRule.updatedAt,
id: mockedRule.id,
revision: 0,
execution_status: {
status: mockedRule.executionStatus.status,
last_execution_date: mockedRule.executionStatus.lastExecutionDate,

View file

@ -68,6 +68,7 @@ describe('createRuleRoute', () => {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
};
const ruleToCreate: AsApiContract<CreateOptions<{ bar: boolean }>['data']> = {
@ -93,6 +94,7 @@ describe('createRuleRoute', () => {
created_at: mockedAlert.createdAt,
updated_at: mockedAlert.updatedAt,
id: mockedAlert.id,
revision: mockedAlert.revision,
execution_status: {
status: mockedAlert.executionStatus.status,
last_execution_date: mockedAlert.executionStatus.lastExecutionDate,

View file

@ -62,6 +62,7 @@ describe('getRuleRoute', () => {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
};
const getResult: AsApiContract<SanitizedRule<{ bar: boolean }>> = {
@ -76,6 +77,7 @@ describe('getRuleRoute', () => {
created_at: mockedAlert.createdAt,
updated_at: mockedAlert.updatedAt,
id: mockedAlert.id,
revision: mockedAlert.revision,
execution_status: {
status: mockedAlert.executionStatus.status,
last_execution_date: mockedAlert.executionStatus.lastExecutionDate,

View file

@ -80,6 +80,7 @@ describe('createAlertRoute', () => {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
};
it('creates an alert with proper parameters', async () => {

View file

@ -66,6 +66,7 @@ describe('getAlertRoute', () => {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
};
it('gets an alert with proper parameters', async () => {

View file

@ -60,6 +60,7 @@ const sampleRule: SanitizedRule<RuleTypeParams> & { activeSnoozes?: string[] } =
},
},
nextRun: DATE_2020,
revision: 0,
};
describe('rewriteRule', () => {

View file

@ -64,6 +64,7 @@ describe('resolveRuleRoute', () => {
},
outcome: 'aliasMatch',
alias_target_id: '2',
revision: 0,
};
const resolveResult: AsApiContract<ResolvedSanitizedRule<{ bar: boolean }>> = {
@ -88,6 +89,7 @@ describe('resolveRuleRoute', () => {
created_at: mockedRule.createdAt,
updated_at: mockedRule.updatedAt,
id: mockedRule.id,
revision: mockedRule.revision,
execution_status: {
status: mockedRule.executionStatus.status,
last_execution_date: mockedRule.executionStatus.lastExecutionDate,

View file

@ -0,0 +1,100 @@
/*
* 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 { UpdateOptions } from '..';
import { mockedDateString } from '../tests/lib';
import { incrementRevision } from './increment_revision';
import { SavedObject } from '@kbn/core/server';
import { RawRule, RuleTypeParams } from '../../types';
describe('incrementRevision', () => {
const currentRule: SavedObject<RawRule> = {
id: '1',
type: 'alert',
attributes: {
enabled: true,
name: 'rule-name',
tags: ['tag-1', 'tag-2'],
alertTypeId: '123',
consumer: 'rule-consumer',
legacyId: null,
schedule: { interval: '1s' },
actions: [],
params: {},
createdBy: null,
updatedBy: null,
createdAt: mockedDateString,
updatedAt: mockedDateString,
apiKey: null,
apiKeyOwner: null,
throttle: null,
notifyWhen: null,
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'unknown',
lastExecutionDate: '2020-08-20T19:23:38Z',
error: null,
warning: null,
},
revision: 0,
},
references: [],
};
const updateOptions: UpdateOptions<RuleTypeParams> = {
id: '1',
data: {
schedule: {
interval: '1m',
},
name: 'abc',
tags: ['foo'],
params: {
bar: true,
risk_score: 40,
severity: 'low',
},
throttle: null,
notifyWhen: 'onActiveAlert',
actions: [],
},
};
const updatedParams: RuleTypeParams = { bar: true, risk_score: 40, severity: 'low' };
it('should return the current revision if no attrs or params are updated', () => {
// @ts-expect-error
expect(incrementRevision(currentRule, { data: {} }, {})).toBe(0);
});
it('should increment the revision if a root level attr is updated', () => {
expect(incrementRevision(currentRule, updateOptions, {})).toBe(1);
});
it('should increment the revision if a rule param is updated', () => {
// @ts-expect-error
expect(incrementRevision(currentRule, { data: {} }, updatedParams)).toBe(1);
});
it('should not increment the revision if an excluded attr is updated', () => {
// @ts-expect-error
expect(incrementRevision(currentRule, { data: { activeSnoozes: 'excludedValue' } }, {})).toBe(
0
);
});
it('should not increment the revision if an excluded param is updated', () => {
expect(
incrementRevision(
currentRule,
// @ts-expect-error
{ data: {} },
{ isSnoozedUntil: '1970-01-02T00:00:00.000Z' }
)
).toBe(0);
});
});

View file

@ -0,0 +1,38 @@
/*
* 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 { SavedObject } from '@kbn/core/server';
import { get, isEqual } from 'lodash';
import { RawRule, RuleTypeParams } from '../../types';
import { fieldsToExcludeFromRevisionUpdates, UpdateOptions } from '..';
export function incrementRevision<Params extends RuleTypeParams>(
currentRule: SavedObject<RawRule>,
{ data }: UpdateOptions<Params>,
updatedParams: RuleTypeParams
): number {
// Diff root level attrs
for (const [field, value] of Object.entries(data).filter(([key]) => key !== 'params')) {
if (
!fieldsToExcludeFromRevisionUpdates.has(field) &&
!isEqual(value, get(currentRule.attributes, field))
) {
return currentRule.attributes.revision + 1;
}
}
// Diff rule params
for (const [field, value] of Object.entries(updatedParams)) {
if (
!fieldsToExcludeFromRevisionUpdates.has(field) &&
!isEqual(value, get(currentRule.attributes.params, field))
) {
return currentRule.attributes.revision + 1;
}
}
return currentRule.attributes.revision;
}

View file

@ -16,3 +16,4 @@ export { scheduleTask } from './schedule_task';
export { createNewAPIKeySet } from './create_new_api_key_set';
export { recoverRuleAlerts } from './recover_rule_alerts';
export { addUuid } from './add_uuid';
export { incrementRevision } from './increment_revision';

View file

@ -69,6 +69,9 @@ export type BulkEditFields = keyof Pick<
'actions' | 'tags' | 'schedule' | 'throttle' | 'notifyWhen' | 'snoozeSchedule' | 'apiKey'
>;
export const bulkEditFieldsToExcludeFromRevisionUpdates: ReadonlySet<BulkEditOperation['field']> =
new Set(['snoozeSchedule', 'apiKey']);
export type BulkEditOperation =
| {
operation: 'add' | 'delete' | 'set';
@ -126,16 +129,22 @@ export type RuleParamsModifier<Params extends RuleTypeParams> = (
params: Params
) => Promise<RuleParamsModifierResult<Params>>;
export type ShouldIncrementRevision<Params extends RuleTypeParams> = (
params?: RuleTypeParams
) => boolean;
export interface BulkEditOptionsFilter<Params extends RuleTypeParams> {
filter?: string | KueryNode;
operations: BulkEditOperation[];
paramsModifier?: RuleParamsModifier<Params>;
shouldIncrementRevision?: ShouldIncrementRevision<Params>;
}
export interface BulkEditOptionsIds<Params extends RuleTypeParams> {
ids: string[];
operations: BulkEditOperation[];
paramsModifier?: RuleParamsModifier<Params>;
shouldIncrementRevision?: ShouldIncrementRevision<Params>;
}
export type BulkEditOptions<Params extends RuleTypeParams> =
@ -244,12 +253,13 @@ export async function bulkEdit<Params extends RuleTypeParams>(
context.logger,
`rulesClient.update('operations=${JSON.stringify(options.operations)}, paramsModifier=${
options.paramsModifier ? '[Function]' : undefined
}')`,
}', shouldIncrementRevision=${options.shouldIncrementRevision ? '[Function]' : undefined}')`,
(filterKueryNode: KueryNode | null) =>
bulkEditOcc(context, {
filter: filterKueryNode,
operations: options.operations,
paramsModifier: options.paramsModifier,
shouldIncrementRevision: options.shouldIncrementRevision,
}),
qNodeFilterWithAuth
);
@ -284,10 +294,12 @@ async function bulkEditOcc<Params extends RuleTypeParams>(
filter,
operations,
paramsModifier,
shouldIncrementRevision,
}: {
filter: KueryNode | null;
operations: BulkEditOptions<Params>['operations'];
paramsModifier: BulkEditOptions<Params>['paramsModifier'];
shouldIncrementRevision?: BulkEditOptions<Params>['shouldIncrementRevision'];
}
): Promise<{
apiKeysToInvalidate: string[];
@ -326,6 +338,7 @@ async function bulkEditOcc<Params extends RuleTypeParams>(
skipped,
errors,
username,
shouldIncrementRevision,
}),
{ concurrency: API_KEY_GENERATE_CONCURRENCY }
);
@ -395,6 +408,7 @@ async function updateRuleAttributesAndParamsInMemory<Params extends RuleTypePara
skipped,
errors,
username,
shouldIncrementRevision = () => true,
}: {
context: RulesClientContext;
rule: SavedObjectsFindResult<RawRule>;
@ -405,6 +419,7 @@ async function updateRuleAttributesAndParamsInMemory<Params extends RuleTypePara
skipped: BulkActionSkipResult[];
errors: BulkOperationError[];
username: string | null;
shouldIncrementRevision: BulkEditOptions<Params>['shouldIncrementRevision'];
}): Promise<void> {
try {
if (rule.attributes.apiKey) {
@ -427,6 +442,15 @@ async function updateRuleAttributesAndParamsInMemory<Params extends RuleTypePara
isParamsUpdateSkipped: true,
};
// Increment revision if params ended up being modified AND it wasn't already incremented as part of attribute update
if (
shouldIncrementRevision(ruleParams) &&
!isParamsUpdateSkipped &&
rule.attributes.revision === attributes.revision
) {
attributes.revision += 1;
}
// If neither attributes nor parameters were updated, mark
// the rule as skipped and continue to the next rule.
if (isAttributesUpdateSkipped && isParamsUpdateSkipped) {
@ -646,6 +670,14 @@ async function getUpdatedAttributesFromOperations(
}
}
}
// Only increment revision if update wasn't skipped and `operation.field` should result in a revision increment
if (
!isAttributesUpdateSkipped &&
!bulkEditFieldsToExcludeFromRevisionUpdates.has(operation.field) &&
rule.attributes.revision - attributes.revision === 0
) {
attributes.revision += 1;
}
}
return {
attributes,

View file

@ -117,6 +117,7 @@ export async function clone<Params extends RuleTypeParams = never>(
mutedInstanceIds: [],
executionStatus: getRuleExecutionStatusPending(lastRunTimestamp.toISOString()),
monitoring: getDefaultMonitoring(lastRunTimestamp.toISOString()),
revision: 0,
scheduledTaskId: null,
running: false,
};

View file

@ -43,6 +43,7 @@ export interface CreateOptions<Params extends RuleTypeParams> {
| 'isSnoozedUntil'
| 'lastRun'
| 'nextRun'
| 'revision'
> & { actions: NormalizedAlertAction[] };
options?: SavedObjectOptions;
allowMissingConnectorSecrets?: boolean;
@ -153,6 +154,7 @@ export async function create<Params extends RuleTypeParams = never>(
throttle,
executionStatus: getRuleExecutionStatusPending(lastRunTimestamp.toISOString()),
monitoring: getDefaultMonitoring(lastRunTimestamp.toISOString()),
revision: 0,
running: false,
};

View file

@ -9,6 +9,7 @@ import Boom from '@hapi/boom';
import { isEqual, omit } from 'lodash';
import { SavedObject } from '@kbn/core/server';
import { AlertConsumers } from '@kbn/rule-data-utils';
import type { ShouldIncrementRevision } from './bulk_edit';
import {
PartialRule,
RawRule,
@ -30,6 +31,7 @@ import {
updateMeta,
getPartialRuleFromRaw,
addUuid,
incrementRevision,
} from '../lib';
import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common';
@ -45,22 +47,29 @@ export interface UpdateOptions<Params extends RuleTypeParams> {
notifyWhen?: RuleNotifyWhenType | null;
};
allowMissingConnectorSecrets?: boolean;
shouldIncrementRevision?: ShouldIncrementRevision<Params>;
}
export async function update<Params extends RuleTypeParams = never>(
context: RulesClientContext,
{ id, data, allowMissingConnectorSecrets }: UpdateOptions<Params>
{ id, data, allowMissingConnectorSecrets, shouldIncrementRevision }: UpdateOptions<Params>
): Promise<PartialRule<Params>> {
return await retryIfConflicts(
context.logger,
`rulesClient.update('${id}')`,
async () => await updateWithOCC<Params>(context, { id, data, allowMissingConnectorSecrets })
async () =>
await updateWithOCC<Params>(context, {
id,
data,
allowMissingConnectorSecrets,
shouldIncrementRevision,
})
);
}
async function updateWithOCC<Params extends RuleTypeParams>(
context: RulesClientContext,
{ id, data, allowMissingConnectorSecrets }: UpdateOptions<Params>
{ id, data, allowMissingConnectorSecrets, shouldIncrementRevision }: UpdateOptions<Params>
): Promise<PartialRule<Params>> {
let alertSavedObject: SavedObject<RawRule>;
@ -108,7 +117,7 @@ async function updateWithOCC<Params extends RuleTypeParams>(
const updateResult = await updateAlert<Params>(
context,
{ id, data, allowMissingConnectorSecrets },
{ id, data, allowMissingConnectorSecrets, shouldIncrementRevision },
alertSavedObject
);
@ -149,9 +158,15 @@ async function updateWithOCC<Params extends RuleTypeParams>(
async function updateAlert<Params extends RuleTypeParams>(
context: RulesClientContext,
{ id, data: initialData, allowMissingConnectorSecrets }: UpdateOptions<Params>,
{ attributes, version }: SavedObject<RawRule>
{
id,
data: initialData,
allowMissingConnectorSecrets,
shouldIncrementRevision = () => true,
}: UpdateOptions<Params>,
currentRule: SavedObject<RawRule>
): Promise<PartialRule<Params>> {
const { attributes, version } = currentRule;
const data = { ...initialData, actions: addUuid(initialData.actions) };
const ruleType = context.ruleTypeRegistry.get(attributes.alertTypeId);
@ -203,6 +218,15 @@ async function updateAlert<Params extends RuleTypeParams>(
const apiKeyAttributes = apiKeyAsAlertAttributes(createdAPIKey, username);
const notifyWhen = getRuleNotifyWhenType(data.notifyWhen ?? null, data.throttle ?? null);
// Increment revision if applicable field has changed
const revision = shouldIncrementRevision(updatedParams)
? incrementRevision<Params>(
currentRule,
{ id, data, allowMissingConnectorSecrets },
updatedParams
)
: currentRule.attributes.revision;
let updatedObject: SavedObject<RawRule>;
const createAttributes = updateMeta(context, {
...attributes,
@ -211,6 +235,7 @@ async function updateAlert<Params extends RuleTypeParams>(
params: updatedParams as RawRule['params'],
actions,
notifyWhen,
revision,
updatedBy: username,
updatedAt: new Date().toISOString(),
});

View file

@ -66,6 +66,30 @@ const fieldsToExcludeFromPublicApi: Array<keyof SanitizedRule> = [
'activeSnoozes',
];
export const fieldsToExcludeFromRevisionUpdates: ReadonlySet<keyof RuleTypeParams> = new Set([
'activeSnoozes',
'alertTypeId',
'apiKey',
'apiKeyOwner',
'consumer',
'createdAt',
'createdBy',
'enabled',
'executionStatus',
'id',
'isSnoozedUntil',
'lastRun',
'monitoring',
'muteAll',
'mutedInstanceIds',
'nextRun',
'revision',
'running',
'snoozeSchedule',
'updatedBy',
'updatedAt',
]);
export class RulesClient {
private readonly context: RulesClientContext;

View file

@ -94,6 +94,7 @@ describe('bulkEdit()', () => {
notifyWhen: null,
actions: [],
name: 'my rule name',
revision: 0,
},
references: [],
version: '123',
@ -220,6 +221,7 @@ describe('bulkEdit()', () => {
throttle: null,
notifyWhen: null,
actions: [],
revision: 0,
},
references: [],
version: '123',
@ -251,6 +253,7 @@ describe('bulkEdit()', () => {
type: 'alert',
attributes: expect.objectContaining({
tags: ['foo', 'test-1'],
revision: 1,
}),
}),
],
@ -275,6 +278,7 @@ describe('bulkEdit()', () => {
throttle: null,
notifyWhen: null,
actions: [],
revision: 0,
},
references: [],
version: '123',
@ -302,6 +306,7 @@ describe('bulkEdit()', () => {
type: 'alert',
attributes: expect.objectContaining({
tags: [],
revision: 1,
}),
}),
],
@ -326,6 +331,7 @@ describe('bulkEdit()', () => {
throttle: null,
notifyWhen: null,
actions: [],
revision: 0,
},
references: [],
version: '123',
@ -354,6 +360,7 @@ describe('bulkEdit()', () => {
type: 'alert',
attributes: expect.objectContaining({
tags: ['test-1', 'test-2'],
revision: 1,
}),
}),
],
@ -579,6 +586,7 @@ describe('bulkEdit()', () => {
updatedAt: '2019-02-12T21:01:22.479Z',
updatedBy: 'elastic',
tags: ['foo'],
revision: 1,
},
references: [{ id: '1', name: 'action_0', type: 'action' }],
},
@ -592,6 +600,50 @@ describe('bulkEdit()', () => {
snoozeSchedule: [],
});
});
test('should only increment revision once for multiple operations', async () => {
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [
{
...existingRule,
attributes: {
...existingRule.attributes,
revision: 1,
},
},
],
});
const result = await rulesClient.bulkEdit({
filter: '',
operations: [
{
field: 'actions',
operation: 'add',
value: [
{
id: '687300e0-b882-11ed-ad70-c74a8cf8f386',
group: 'default',
params: {
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
},
},
],
},
{
field: 'throttle',
operation: 'set',
value: null,
},
{
field: 'notifyWhen',
operation: 'set',
value: 'onActiveAlert',
},
],
});
expect(result.rules[0]).toHaveProperty('revision', 1);
});
});
describe('index pattern operations', () => {
@ -628,6 +680,7 @@ describe('bulkEdit()', () => {
throttle: null,
notifyWhen: null,
actions: [],
revision: 0,
},
references: [],
version: '123',
@ -665,6 +718,7 @@ describe('bulkEdit()', () => {
params: expect.objectContaining({
index: ['test-1', 'test-2', 'test-4', 'test-5'],
}),
revision: 1,
}),
}),
],
@ -691,6 +745,7 @@ describe('bulkEdit()', () => {
throttle: null,
notifyWhen: null,
actions: [],
revision: 0,
},
references: [],
version: '123',
@ -723,6 +778,7 @@ describe('bulkEdit()', () => {
params: expect.objectContaining({
index: ['test-1'],
}),
revision: 1,
}),
}),
],
@ -819,6 +875,7 @@ describe('bulkEdit()', () => {
type: 'alert',
attributes: expect.objectContaining({
snoozeSchedule: [snoozePayload],
revision: 0,
}),
}),
],
@ -848,6 +905,7 @@ describe('bulkEdit()', () => {
id: '1',
type: 'alert',
attributes: expect.objectContaining({
revision: 0,
snoozeSchedule: [snoozePayload],
}),
}),
@ -893,6 +951,7 @@ describe('bulkEdit()', () => {
id: '1',
type: 'alert',
attributes: expect.objectContaining({
revision: 0,
snoozeSchedule: [...existingSnooze, snoozePayload],
}),
}),
@ -938,6 +997,7 @@ describe('bulkEdit()', () => {
type: 'alert',
attributes: expect.objectContaining({
muteAll: true,
revision: 0,
snoozeSchedule: [snoozePayload],
}),
}),
@ -981,6 +1041,7 @@ describe('bulkEdit()', () => {
id: '1',
type: 'alert',
attributes: expect.objectContaining({
revision: 0,
snoozeSchedule: [existingSnooze[1], existingSnooze[2]],
}),
}),
@ -1025,6 +1086,7 @@ describe('bulkEdit()', () => {
id: '1',
type: 'alert',
attributes: expect.objectContaining({
revision: 0,
snoozeSchedule: [],
}),
}),
@ -1069,6 +1131,7 @@ describe('bulkEdit()', () => {
id: '1',
type: 'alert',
attributes: expect.objectContaining({
revision: 0,
snoozeSchedule: [existingSnooze[0]],
}),
}),
@ -1181,7 +1244,7 @@ describe('bulkEdit()', () => {
expect(createAPIKeyMock).not.toHaveBeenCalled();
// Explicitly bulk editing the apiKey will set the api key, even if the rule is disabled
await rulesClient.bulkEdit({
const result = await rulesClient.bulkEdit({
filter: 'alert.attributes.tags: "APM"',
operations: [
{
@ -1192,6 +1255,9 @@ describe('bulkEdit()', () => {
});
expect(createAPIKeyMock).toHaveBeenCalled();
// Just API key updates do not result in an increment to revision
expect(result.rules[0]).toHaveProperty('revision', 0);
});
});
@ -1211,7 +1277,7 @@ describe('bulkEdit()', () => {
});
});
it('should succesfully update tags and index patterns and return updated rule', async () => {
it('should successfully update tags and index patterns and return updated rule', async () => {
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [
{
@ -1230,6 +1296,7 @@ describe('bulkEdit()', () => {
throttle: null,
notifyWhen: null,
actions: [],
revision: 0,
},
references: [],
version: '123',
@ -1270,6 +1337,7 @@ describe('bulkEdit()', () => {
params: {
index: ['index-1', 'index-2', 'index-3'],
},
revision: 1,
}),
}),
],
@ -1277,7 +1345,7 @@ describe('bulkEdit()', () => {
);
});
it('should succesfully update rule if tags are updated but index patterns are not', async () => {
it('should successfully update rule if tags are updated but index patterns are not', async () => {
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [
{
@ -1296,6 +1364,7 @@ describe('bulkEdit()', () => {
throttle: null,
notifyWhen: null,
actions: [],
revision: 0,
},
references: [],
version: '123',
@ -1337,6 +1406,7 @@ describe('bulkEdit()', () => {
params: {
index: ['index-1', 'index-2'],
},
revision: 1,
}),
}),
],
@ -1344,7 +1414,7 @@ describe('bulkEdit()', () => {
);
});
it('should succesfully update rule if index patterns are updated but tags are not', async () => {
it('should successfully update rule if index patterns are updated but tags are not', async () => {
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [
{
@ -1363,6 +1433,7 @@ describe('bulkEdit()', () => {
throttle: null,
notifyWhen: null,
actions: [],
revision: 0,
},
references: [],
version: '123',
@ -1404,6 +1475,7 @@ describe('bulkEdit()', () => {
params: {
index: ['index-1', 'index-2', 'index-3'],
},
revision: 1,
}),
}),
],

View file

@ -457,6 +457,7 @@ describe('create()', () => {
"params": Object {
"bar": true,
},
"revision": 0,
"running": false,
"schedule": Object {
"interval": "1m",
@ -677,6 +678,7 @@ describe('create()', () => {
"params": Object {
"bar": true,
},
"revision": 0,
"running": false,
"schedule": Object {
"interval": "1m",
@ -1105,6 +1107,7 @@ describe('create()', () => {
name: 'abc',
notifyWhen: null,
params: { bar: true },
revision: 0,
running: false,
schedule: { interval: '1m' },
tags: ['foo'],
@ -1314,6 +1317,7 @@ describe('create()', () => {
name: 'abc',
notifyWhen: null,
params: { bar: true, parameterThatIsSavedObjectRef: 'soRef_0' },
revision: 0,
running: false,
schedule: { interval: '1m' },
tags: ['foo'],
@ -1493,6 +1497,7 @@ describe('create()', () => {
name: 'abc',
notifyWhen: null,
params: { bar: true, parameterThatIsSavedObjectRef: 'action_0' },
revision: 0,
running: false,
schedule: { interval: '1m' },
tags: ['foo'],
@ -1668,6 +1673,7 @@ describe('create()', () => {
warning: null,
},
monitoring: getDefaultMonitoring('2019-02-12T21:01:22.479Z'),
revision: 0,
running: false,
},
{
@ -1804,6 +1810,7 @@ describe('create()', () => {
warning: null,
},
monitoring: getDefaultMonitoring('2019-02-12T21:01:22.479Z'),
revision: 0,
running: false,
},
{
@ -1940,6 +1947,7 @@ describe('create()', () => {
warning: null,
},
monitoring: getDefaultMonitoring('2019-02-12T21:01:22.479Z'),
revision: 0,
running: false,
},
{
@ -2116,6 +2124,7 @@ describe('create()', () => {
meta: {
versionApiKeyLastmodified: 'v8.0.0',
},
revision: 0,
running: false,
},
{
@ -2470,6 +2479,7 @@ describe('create()', () => {
warning: null,
},
monitoring: getDefaultMonitoring('2019-02-12T21:01:22.479Z'),
revision: 0,
running: false,
},
{
@ -2575,6 +2585,7 @@ describe('create()', () => {
warning: null,
},
monitoring: getDefaultMonitoring('2019-02-12T21:01:22.479Z'),
revision: 0,
running: false,
},
{

View file

@ -90,6 +90,7 @@ const BaseRuleSavedObject: SavedObject<RawRule> = {
error: null,
warning: null,
},
revision: 0,
},
references: [],
};

View file

@ -94,6 +94,7 @@ const BaseRuleSavedObject: SavedObject<RawRule> = {
error: null,
warning: null,
},
revision: 0,
},
references: [],
};

View file

@ -91,6 +91,7 @@ const BaseRuleSavedObject: SavedObject<RawRule> = {
error: null,
warning: null,
},
revision: 0,
},
references: [],
};

View file

@ -90,6 +90,7 @@ describe('update()', () => {
alertTypeId: 'myType',
schedule: { interval: '1m' },
consumer: 'myApp',
revision: 0,
scheduledTaskId: 'task-123',
params: {},
actions: [
@ -235,6 +236,7 @@ describe('update()', () => {
},
],
notifyWhen: 'onActiveAlert',
revision: 1,
scheduledTaskId: 'task-123',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@ -330,6 +332,7 @@ describe('update()', () => {
"params": Object {
"bar": true,
},
"revision": 1,
"schedule": Object {
"interval": "1m",
},
@ -395,6 +398,7 @@ describe('update()', () => {
"risk_score": 40,
"severity": "low",
},
"revision": 1,
"schedule": Object {
"interval": "1m",
},
@ -527,6 +531,7 @@ describe('update()', () => {
},
],
notifyWhen: 'onActiveAlert',
revision: 1,
scheduledTaskId: 'task-123',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@ -623,6 +628,7 @@ describe('update()', () => {
name: 'abc',
notifyWhen: 'onActiveAlert',
params: { bar: true },
revision: 1,
schedule: { interval: '1m' },
scheduledTaskId: 'task-123',
tags: ['foo'],
@ -673,6 +679,7 @@ describe('update()', () => {
"params": Object {
"bar": true,
},
"revision": 1,
"schedule": Object {
"interval": "1m",
},
@ -747,6 +754,7 @@ describe('update()', () => {
},
],
notifyWhen: 'onActiveAlert',
revision: 1,
scheduledTaskId: 'task-123',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@ -807,6 +815,7 @@ describe('update()', () => {
name: 'abc',
notifyWhen: 'onActiveAlert',
params: { bar: true, parameterThatIsSavedObjectRef: 'soRef_0' },
revision: 1,
schedule: { interval: '1m' },
scheduledTaskId: 'task-123',
tags: ['foo'],
@ -852,6 +861,7 @@ describe('update()', () => {
"bar": true,
"parameterThatIsSavedObjectId": "9",
},
"revision": 1,
"schedule": Object {
"interval": "1m",
},
@ -889,6 +899,7 @@ describe('update()', () => {
},
],
apiKey: Buffer.from('123:abc').toString('base64'),
revision: 1,
scheduledTaskId: 'task-123',
},
updated_at: new Date().toISOString(),
@ -942,6 +953,7 @@ describe('update()', () => {
"params": Object {
"bar": true,
},
"revision": 1,
"schedule": Object {
"interval": "1m",
},
@ -986,6 +998,7 @@ describe('update()', () => {
"params": Object {
"bar": true,
},
"revision": 1,
"schedule": Object {
"interval": "1m",
},
@ -1044,6 +1057,7 @@ describe('update()', () => {
},
},
],
revision: 1,
scheduledTaskId: 'task-123',
apiKey: null,
},
@ -1099,6 +1113,7 @@ describe('update()', () => {
"params": Object {
"bar": true,
},
"revision": 1,
"schedule": Object {
"interval": "1m",
},
@ -1135,6 +1150,7 @@ describe('update()', () => {
"params": Object {
"bar": true,
},
"revision": 1,
"schedule": Object {
"interval": "1m",
},
@ -2079,6 +2095,7 @@ describe('update()', () => {
},
],
notifyWhen: 'onActiveAlert',
revision: 1,
scheduledTaskId: 'task-123',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@ -2144,6 +2161,7 @@ describe('update()', () => {
name: 'abc',
notifyWhen: 'onActiveAlert',
params: { bar: true },
revision: 1,
schedule: { interval: '1m' },
scheduledTaskId: 'task-123',
tags: ['foo'],
@ -2178,6 +2196,7 @@ describe('update()', () => {
"params": Object {
"bar": true,
},
"revision": 1,
"schedule": Object {
"interval": "1m",
},
@ -2690,6 +2709,7 @@ describe('update()', () => {
name: 'abc',
notifyWhen: null,
params: { bar: true, risk_score: 40, severity: 'low' },
revision: 1,
schedule: { interval: '1m' },
scheduledTaskId: 'task-123',
tags: ['foo'],

View file

@ -64,6 +64,7 @@ describe('updateApiKey()', () => {
id: '1',
type: 'alert',
attributes: {
revision: 0,
schedule: { interval: '10s' },
alertTypeId: 'myType',
consumer: 'myApp',
@ -117,6 +118,7 @@ describe('updateApiKey()', () => {
enabled: true,
apiKey: Buffer.from('234:abc').toString('base64'),
apiKeyOwner: 'elastic',
revision: 0,
updatedBy: 'elastic',
updatedAt: '2019-02-12T21:01:22.479Z',
actions: [
@ -177,6 +179,7 @@ describe('updateApiKey()', () => {
enabled: true,
apiKey: Buffer.from('234:abc').toString('base64'),
apiKeyOwner: 'elastic',
revision: 0,
updatedAt: '2019-02-12T21:01:22.479Z',
updatedBy: 'elastic',
actions: [

View file

@ -40,6 +40,7 @@ export const AlertAttributesExcludedFromAAD = [
'isSnoozedUntil',
'lastRun',
'nextRun',
'revision',
'running',
];
@ -60,6 +61,7 @@ export type AlertAttributesExcludedFromAADType =
| 'isSnoozedUntil'
| 'lastRun'
| 'nextRun'
| 'revision'
| 'running';
export function setupSavedObjects(

View file

@ -198,6 +198,10 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = {
},
},
},
revision: {
index: true, // Explicitly setting to `true` as there is need to query for a rule by a specific revision
type: 'long',
},
snoozeSchedule: {
type: 'nested',
properties: {

View file

@ -11,6 +11,16 @@ import { v4 as uuidv4 } from 'uuid';
import { createEsoMigration, pipeMigrations } from '../utils';
import { RawRule } from '../../../types';
function addRevision(doc: SavedObjectUnsanitizedDoc<RawRule>): SavedObjectUnsanitizedDoc<RawRule> {
return {
...doc,
attributes: {
...doc.attributes,
revision: 0,
},
};
}
function addActionUuid(
doc: SavedObjectUnsanitizedDoc<RawRule>
): SavedObjectUnsanitizedDoc<RawRule> {
@ -36,5 +46,5 @@ export const getMigrations880 = (encryptedSavedObjects: EncryptedSavedObjectsPlu
createEsoMigration(
encryptedSavedObjects,
(doc: SavedObjectUnsanitizedDoc<RawRule>): doc is SavedObjectUnsanitizedDoc<RawRule> => true,
pipeMigrations(addActionUuid)
pipeMigrations(addActionUuid, addRevision)
);

View file

@ -2652,6 +2652,14 @@ describe('successful migrations', () => {
},
]);
});
test('migrates rule to include revision and defaults revision to 0', () => {
const migration880 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.8.0'];
const rule = getMockData();
const migratedAlert880 = migration880(rule, migrationContext);
expect(migratedAlert880.attributes.revision).toEqual(0);
});
});
describe('Metrics Inventory Threshold rule', () => {

View file

@ -37,6 +37,7 @@ const alert: SanitizedRule<{
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
};
describe('Alert Task Instance', () => {

View file

@ -210,6 +210,7 @@ export const mockedRuleTypeSavedObject: Rule<RuleTypeParams> = {
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
monitoring: getDefaultMonitoring('2020-08-20T19:23:38Z'),
revision: 0,
};
export const mockTaskInstance = () => ({

View file

@ -350,6 +350,7 @@ export interface RawRule extends SavedObjectAttributes {
isSnoozedUntil?: string | null;
lastRun?: RawRuleLastRun | null;
nextRun?: string | null;
revision: number;
running?: boolean | null;
}

View file

@ -87,6 +87,7 @@ Array [
"name": "monitoring_alert_cpu_usage_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -250,6 +251,7 @@ Array [
"name": "monitoring_alert_jvm_memory_usage_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -310,6 +312,7 @@ Array [
"name": "monitoring_alert_jvm_memory_usage_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -602,6 +605,7 @@ Array [
"name": "monitoring_alert_nodes_changed_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -662,6 +666,7 @@ Array [
"name": "monitoring_alert_nodes_changed_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -722,6 +727,7 @@ Array [
"name": "monitoring_alert_disk_usage_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -782,6 +788,7 @@ Array [
"name": "monitoring_alert_license_expiration_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -842,6 +849,7 @@ Array [
"name": "monitoring_alert_license_expiration_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -931,6 +939,7 @@ Array [
"name": "monitoring_alert_jvm_memory_usage_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -1032,6 +1041,7 @@ Array [
"name": "monitoring_alert_nodes_changed_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -1068,6 +1078,7 @@ Array [
"name": "monitoring_alert_disk_usage_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -1104,6 +1115,7 @@ Array [
"name": "monitoring_alert_license_expiration_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -1205,6 +1217,7 @@ Array [
"name": "monitoring_alert_logstash_version_mismatch_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -1241,6 +1254,7 @@ Array [
"name": "monitoring_alert_cpu_usage_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -1277,6 +1291,7 @@ Array [
"name": "monitoring_alert_thread_pool_write_rejections_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},

View file

@ -114,6 +114,7 @@ Array [
"name": "monitoring_alert_jvm_memory_usage_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -174,6 +175,7 @@ Array [
"name": "monitoring_alert_jvm_memory_usage_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -407,6 +409,7 @@ Array [
"name": "monitoring_alert_nodes_changed_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -467,6 +470,7 @@ Array [
"name": "monitoring_alert_disk_usage_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -527,6 +531,7 @@ Array [
"name": "monitoring_alert_license_expiration_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -587,6 +592,7 @@ Array [
"name": "monitoring_alert_nodes_changed_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -647,6 +653,7 @@ Array [
"name": "monitoring_alert_license_expiration_label",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},
@ -707,6 +714,7 @@ Array [
"name": "monitoring_alert_nodes_changed_label_2",
"notifyWhen": null,
"params": Object {},
"revision": 0,
"schedule": Object {
"interval": "1m",
},

View file

@ -58,6 +58,7 @@ const mockAlert = {
lastExecutionDate: new Date('2020-12-08'),
},
notifyWhen: null,
revision: 0,
};
describe('getAlertPanelsByCategory', () => {

View file

@ -57,6 +57,7 @@ const mockAlert = {
lastExecutionDate: new Date('2020-12-08'),
},
notifyWhen: null,
revision: 0,
};
describe('getAlertPanelsByNode', () => {

View file

@ -17,7 +17,7 @@ import {
BaseCreateProps,
TypeSpecificCreateProps,
} from '../../../rule_schema';
import { created_at, updated_at, created_by, updated_by } from '../../../schemas/common';
import { created_at, updated_at, created_by, updated_by, revision } from '../../../schemas/common';
/**
* Differences from this and the createRulesSchema are
@ -44,6 +44,7 @@ export const RuleToImport = t.intersection([
created_by,
related_integrations: RelatedIntegrationArray,
required_fields: RequiredFieldArray,
revision,
setup: SetupGuide,
})
),

View file

@ -31,6 +31,7 @@ const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE): SharedResponse
immutable: false,
name: 'Query with a rule id',
references: ['test 1', 'test 2'],
revision: 0,
severity: 'high' as const,
severity_mapping: [],
updated_by: 'elastic_kibana',

View file

@ -36,6 +36,7 @@ import {
updated_by,
created_at,
created_by,
revision,
} from '../../schemas/common';
import {
@ -154,6 +155,7 @@ const responseRequiredFields = {
updated_by,
created_at,
created_by,
revision,
// NOTE: For now, Related Integrations, Required Fields and Setup Guide are supported for prebuilt
// rules only. We don't want to allow users to edit these 3 fields via the API. If we added them

View file

@ -59,6 +59,7 @@ export const status_code = PositiveInteger;
export const message = t.string;
export const perPage = PositiveInteger;
export const total = PositiveInteger;
export const revision = PositiveInteger;
export const success = t.boolean;
export const success_count = PositiveInteger;

View file

@ -499,6 +499,7 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response<RuleResponse
tags,
interval,
enabled,
revision: 0,
description,
risk_score: riskScore,
severity,

View file

@ -375,6 +375,7 @@ export const getRuleMock = <T extends RuleParams>(params: T): SanitizedRule<T> =
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
});
export const resolveRuleMock = <T extends RuleParams>(params: T): ResolvedSanitizedRule<T> => ({
@ -552,6 +553,7 @@ export const legacyGetNotificationResult = ({
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
});
/**
@ -600,6 +602,7 @@ export const legacyGetHourlyNotificationResult = (
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
});
/**
@ -648,6 +651,7 @@ export const legacyGetDailyNotificationResult = (
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
});
/**
@ -696,6 +700,7 @@ export const legacyGetWeeklyNotificationResult = (
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
});
/**

View file

@ -86,6 +86,7 @@ export const getOutputRuleAlertForRest = (): RuleResponse => ({
type: 'query',
note: '# Investigative notes',
version: 1,
revision: 0,
execution_summary: undefined,
related_integrations: [],
required_fields: [],

View file

@ -544,6 +544,7 @@ export const performBulkActionRoute = (
exceptionsList: exceptions,
},
},
shouldIncrementRevision: () => false,
});
// TODO: figureout why types can't return just updatedRule

View file

@ -76,6 +76,7 @@ describe('duplicateRule', () => {
mutedInstanceIds: [],
updatedAt: new Date(2021, 0),
createdAt: new Date(2021, 0),
revision: 0,
scheduledTaskId: undefined,
executionStatus: {
lastExecutionDate: new Date(2021, 0),

View file

@ -17,6 +17,8 @@ export interface PatchRulesOptions {
nextParams: PatchRuleRequestBody;
existingRule: RuleAlertType | null | undefined;
allowMissingConnectorSecrets?: boolean;
shouldIncrementRevision?: boolean;
}
export const patchRules = async ({
@ -24,6 +26,7 @@ export const patchRules = async ({
existingRule,
nextParams,
allowMissingConnectorSecrets,
shouldIncrementRevision = true,
}: PatchRulesOptions): Promise<PartialRule<RuleParams> | null> => {
if (existingRule == null) {
return null;
@ -35,6 +38,7 @@ export const patchRules = async ({
id: existingRule.id,
data: patchedRule,
allowMissingConnectorSecrets,
shouldIncrementRevision: () => shouldIncrementRevision,
});
if (nextParams.throttle !== undefined) {

View file

@ -93,6 +93,7 @@ describe('getExportAll', () => {
references: ['http://example.com', 'https://example.com'],
related_integrations: [],
required_fields: [],
revision: 0,
setup: '',
timeline_id: 'some-timeline-id',
timeline_title: 'some-timeline-title',
@ -287,6 +288,7 @@ describe('getExportAll', () => {
throttle: 'rule',
note: '# Investigative notes',
version: 1,
revision: 0,
exceptions_list: getListArrayMock(),
});
expect(detailsJson).toEqual({

View file

@ -91,6 +91,7 @@ describe('get_export_by_object_ids', () => {
references: ['http://example.com', 'https://example.com'],
related_integrations: [],
required_fields: [],
revision: 0,
setup: '',
timeline_id: 'some-timeline-id',
timeline_title: 'some-timeline-title',
@ -297,6 +298,7 @@ describe('get_export_by_object_ids', () => {
throttle: 'rule',
note: '# Investigative notes',
version: 1,
revision: 0,
exceptions_list: getListArrayMock(),
});
expect(detailsJson).toEqual({
@ -399,6 +401,7 @@ describe('get_export_by_object_ids', () => {
throttle: 'no_actions',
note: '# Investigative notes',
version: 1,
revision: 0,
exceptions_list: getListArrayMock(),
execution_summary: undefined,
outcome: undefined,

View file

@ -141,6 +141,7 @@ export const importRules = async ({
exceptions_list: [...exceptions],
},
allowMissingConnectorSecrets,
shouldIncrementRevision: false,
});
resolve({
rule_id: parsedRule.rule_id,

View file

@ -692,6 +692,7 @@ export const internalRuleToAPIResponse = (
tags: rule.tags,
interval: rule.schedule.interval,
enabled: rule.enabled,
revision: rule.revision,
// Security solution shared rule params
...commonParamsCamelToSnake(rule.params),
// Type specific security solution rule params

View file

@ -46,6 +46,7 @@ export const ruleOutput = (): RuleResponse => ({
throttle: 'no_actions',
threat: getThreatMock(),
version: 1,
revision: 0,
filters: [
{
query: {

View file

@ -497,6 +497,7 @@ export const sampleSignalHit = (): SignalHit => ({
related_integrations: [],
required_fields: [],
response_actions: undefined,
revision: 0,
setup: '',
throttle: 'no_actions',
actions: [],

View file

@ -20,7 +20,8 @@ export * from './detail_flyout';
export * from './alert_rules/default_status_alert.journey';
export * from './test_now_mode.journey';
export * from './data_retention.journey';
export * from './monitor_details_page/monitor_summary.journey';
// Additional flake skip along with https://github.com/elastic/kibana/pull/151936
// export * from './monitor_details_page/monitor_summary.journey';
export * from './test_run_details.journey';
export * from './step_details.journey';
export * from './project_monitor_read_only.journey';

View file

@ -63,6 +63,7 @@ type NewMonitorStatusAlert = Omit<
| 'muteAll'
| 'mutedInstanceIds'
| 'executionStatus'
| 'revision'
| 'ruleTypeId'
| 'notifyWhen'
| 'actions'

View file

@ -95,6 +95,7 @@ describe('createRule', () => {
createdAt: new Date('2021-04-01T21:33:13.247Z'),
updatedAt: new Date('2021-04-01T21:33:13.247Z'),
apiKeyOwner: '',
revision: 0,
};
http.post.mockResolvedValueOnce(resolvedValue);

View file

@ -26,6 +26,7 @@ describe('updateRule', () => {
updatedAt: new Date('1970-01-01T00:00:00.000Z'),
apiKey: null,
apiKeyOwner: null,
revision: 0,
};
const resolvedValue: Rule = {
...ruleToUpdate,
@ -40,6 +41,7 @@ describe('updateRule', () => {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 1,
};
http.put.mockResolvedValueOnce(resolvedValue);

View file

@ -275,6 +275,7 @@ describe('getRuleWithInvalidatedFields', () => {
throttle: '',
updatedAt: new Date(),
updatedBy: '',
revision: 0,
};
const baseAlertErrors = {};
const actionsErrors: IErrorObject[] = [];
@ -313,6 +314,7 @@ describe('getRuleWithInvalidatedFields', () => {
throttle: '',
updatedAt: new Date(),
updatedBy: '',
revision: 0,
};
const baseAlertErrors = {};
const actionsErrors: IErrorObject[] = [];
@ -363,6 +365,7 @@ describe('getRuleWithInvalidatedFields', () => {
throttle: '',
updatedAt: new Date(),
updatedBy: '',
revision: 0,
};
const baseAlertErrors = {};
const actionsErrors = [{ 'incident.field.name': ['Name is required.'] }];
@ -422,6 +425,7 @@ describe('getRuleWithInvalidatedFields', () => {
throttle: '',
updatedAt: new Date(),
updatedBy: '',
revision: 0,
};
const baseAlertErrors = {};
const actionsErrors = [

View file

@ -385,6 +385,7 @@ function mockRule(overloads: Partial<Rule> = {}): Rule {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
...overloads,
};
}

View file

@ -40,6 +40,7 @@ describe('rule_actions_popover', () => {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
...overloads,
};
}

View file

@ -215,6 +215,7 @@ function mockRule(overwrite = {}): Rule {
updatedAt: new Date(),
consumer: 'alerts',
notifyWhen: 'onActiveAlert',
revision: 0,
executionStatus: {
status: 'active',
lastDuration: 500,

View file

@ -818,6 +818,7 @@ describe('rule_details', () => {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
...overloads,
};
}

View file

@ -494,6 +494,7 @@ function mockRule(overloads: Partial<Rule> = {}): Rule {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
...overloads,
};
}

View file

@ -117,6 +117,7 @@ const mockRule: Rule = {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
};
const loadActionErrorLogMock = jest.fn();

View file

@ -125,6 +125,7 @@ function mockRule(overloads: Partial<Rule> = {}): Rule {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
...overloads,
};
}

View file

@ -54,6 +54,7 @@ export function mockRule(overloads: Partial<Rule> = {}): Rule {
notifyWhen: null,
muteAll: false,
mutedInstanceIds: [],
revision: 0,
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),

View file

@ -93,6 +93,7 @@ function mockRule(overloads: Partial<Rule> = {}): Rule {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
...overloads,
};
}

View file

@ -397,6 +397,7 @@ function mockRule(overloads: Partial<Rule> = {}): Rule {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
...overloads,
};
}

View file

@ -174,6 +174,7 @@ describe('rule_edit', () => {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
...initialRuleFields,
};
actionTypeRegistry.get.mockReturnValueOnce(actionTypeModel);

View file

@ -266,6 +266,7 @@ function mockRule(overloads: Partial<Rule> = {}): Rule {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
revision: 0,
...overloads,
};
}

View file

@ -83,6 +83,7 @@ describe('CollapsedItemActions', () => {
ruleType: 'Test Rule Type',
isEditable: true,
enabledInLicense: true,
revision: 0,
...overrides,
};

View file

@ -53,6 +53,7 @@ const getRule = (overrides = {}): RuleTableItem => ({
ruleType: 'Test Rule Type',
isEditable: true,
enabledInLicense: true,
revision: 0,
...overrides,
});

View file

@ -42,6 +42,7 @@ describe('RuleEnabledSwitch', () => {
notifyWhen: null,
index: 0,
updatedAt: new Date('2020-08-20T19:23:38Z'),
revision: 0,
},
onRuleChanged: jest.fn(),
};
@ -86,6 +87,7 @@ describe('RuleEnabledSwitch', () => {
notifyWhen: null,
index: 0,
updatedAt: new Date('2020-08-20T19:23:38Z'),
revision: 0,
},
}}
/>

View file

@ -126,6 +126,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
mute_all: false,
muted_alert_ids: [],
execution_status: response.body.execution_status,
revision: 0,
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
});

View file

@ -123,6 +123,7 @@ const findTestUtils = (
api_key_owner: 'elastic',
mute_all: false,
muted_alert_ids: [],
revision: 0,
execution_status: match.execution_status,
...(match.next_run ? { next_run: match.next_run } : {}),
...(match.last_run ? { last_run: match.last_run } : {}),
@ -335,6 +336,7 @@ const findTestUtils = (
created_at: match.created_at,
updated_at: match.updated_at,
execution_status: match.execution_status,
revision: 0,
...(match.next_run ? { next_run: match.next_run } : {}),
...(match.last_run ? { last_run: match.last_run } : {}),
...(describeType === 'internal'

View file

@ -82,6 +82,7 @@ const getTestUtils = (
mute_all: false,
muted_alert_ids: [],
execution_status: response.body.execution_status,
revision: 0,
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
...(describeType === 'internal'

View file

@ -134,6 +134,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
created_at: response.body.created_at,
updated_at: response.body.updated_at,
execution_status: response.body.execution_status,
revision: 1,
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
});
@ -224,6 +225,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
created_at: response.body.created_at,
updated_at: response.body.updated_at,
execution_status: response.body.execution_status,
revision: 1,
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
});
@ -325,6 +327,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
created_at: response.body.created_at,
updated_at: response.body.updated_at,
execution_status: response.body.execution_status,
revision: 1,
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
});
@ -426,6 +429,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
created_at: response.body.created_at,
updated_at: response.body.updated_at,
execution_status: response.body.execution_status,
revision: 1,
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
});
@ -525,6 +529,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
created_at: response.body.created_at,
updated_at: response.body.updated_at,
execution_status: response.body.execution_status,
revision: 1,
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
});

View file

@ -98,6 +98,8 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte
.auth(user.username, user.password)
.expect(200);
expect(updatedAlert.api_key_owner).to.eql(user.username);
// Ensure revision is not incremented when API key is updated
expect(updatedAlert.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest,
@ -152,6 +154,8 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte
.auth(user.username, user.password)
.expect(200);
expect(updatedAlert.api_key_owner).to.eql(user.username);
// Ensure revision is not incremented when API key is updated
expect(updatedAlert.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest,
@ -217,6 +221,8 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte
.auth(user.username, user.password)
.expect(200);
expect(updatedAlert.api_key_owner).to.eql(user.username);
// Ensure revision is not incremented when API key is updated
expect(updatedAlert.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest,
@ -282,6 +288,8 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte
.auth(user.username, user.password)
.expect(200);
expect(updatedAlert.api_key_owner).to.eql(user.username);
// Ensure revision is not incremented when API key is updated
expect(updatedAlert.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest,
@ -346,6 +354,8 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte
.auth(user.username, user.password)
.expect(200);
expect(updatedAlert.api_key_owner).to.eql(user.username);
// Ensure revision is not incremented when API key is updated
expect(updatedAlert.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest,

View file

@ -35,6 +35,7 @@ const getDefaultRules = (response: any) => ({
scheduledTaskId: response.body.rules[0].scheduledTaskId,
executionStatus: response.body.rules[0].executionStatus,
monitoring: response.body.rules[0].monitoring,
revision: 0,
...(response.body.rules[0].nextRun ? { nextRun: response.body.rules[0].nextRun } : {}),
...(response.body.rules[0].lastRun ? { lastRun: response.body.rules[0].lastRun } : {}),
});
@ -67,6 +68,7 @@ const getThreeRules = (response: any) => {
scheduledTaskId: response.body.rules[i].scheduledTaskId,
executionStatus: response.body.rules[i].executionStatus,
monitoring: response.body.rules[i].monitoring,
revision: 0,
...(response.body.rules[i].nextRun ? { nextRun: response.body.rules[i].nextRun } : {}),
...(response.body.rules[i].lastRun ? { lastRun: response.body.rules[i].lastRun } : {}),
});

View file

@ -34,6 +34,7 @@ const getDefaultRules = (response: any) => ({
scheduledTaskId: response.body.rules[0].scheduledTaskId,
executionStatus: response.body.rules[0].executionStatus,
monitoring: response.body.rules[0].monitoring,
revision: 0,
...(response.body.rules[0].nextRun ? { nextRun: response.body.rules[0].nextRun } : {}),
...(response.body.rules[0].lastRun ? { lastRun: response.body.rules[0].lastRun } : {}),
});

View file

@ -95,6 +95,7 @@ export default ({ getService }: FtrProviderContext) => {
snoozeSchedule: [],
updatedAt: response.body.rules[0].updatedAt,
createdAt: response.body.rules[0].createdAt,
revision: 0,
scheduledTaskId: response.body.rules[0].scheduledTaskId,
executionStatus: {
lastDuration: 0,

View file

@ -165,6 +165,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
mute_all: false,
muted_alert_ids: [],
execution_status: response.body.execution_status,
revision: 0,
last_run: {
alerts_count: {
active: 0,

View file

@ -81,6 +81,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
],
enabled: true,
rule_type_id: 'test.noop',
revision: 0,
running: false,
consumer: 'alertsFixture',
params: {},
@ -184,6 +185,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
],
enabled: true,
rule_type_id: 'test.noop',
revision: 0,
running: false,
consumer: 'alertsFixture',
params: {},
@ -503,6 +505,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
createdAt: response.body.createdAt,
updatedAt: response.body.updatedAt,
executionStatus: response.body.executionStatus,
revision: 0,
running: false,
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
...(response.body.last_run ? { last_run: response.body.last_run } : {}),

View file

@ -63,6 +63,14 @@ export default function createDisableRuleTests({ getService }: FtrProviderContex
expect(taskRecord.task.enabled).to.eql(false);
});
const { body: disabledRule } = await supertestWithoutAuth
.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdRule.id}`)
.set('kbn-xsrf', 'foo')
.expect(200);
// Ensure revision was not updated
expect(disabledRule.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest: supertestWithoutAuth,
@ -173,6 +181,14 @@ export default function createDisableRuleTests({ getService }: FtrProviderContex
});
await ruleUtils.disable(createdRule.id);
const { body: disabledRule } = await supertestWithoutAuth
.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdRule.id}`)
.set('kbn-xsrf', 'foo')
.expect(200);
// Ensure revision was not updated
expect(disabledRule.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest: supertestWithoutAuth,
@ -209,6 +225,14 @@ export default function createDisableRuleTests({ getService }: FtrProviderContex
expect(taskRecord.task.enabled).to.eql(false);
});
const { body: disabledRule } = await supertestWithoutAuth
.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdRule.id}`)
.set('kbn-xsrf', 'foo')
.expect(200);
// Ensure revision was not updated
expect(disabledRule.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest: supertestWithoutAuth,

View file

@ -61,6 +61,9 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex
});
expect(taskRecord.task.enabled).to.eql(true);
// Ensure revision was not updated
expect(updatedAlert.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest: supertestWithoutAuth,
@ -114,6 +117,9 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex
});
expect(taskRecord.task.enabled).to.eql(true);
// Ensure revision was not updated
expect(updatedAlert.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest: supertestWithoutAuth,

View file

@ -87,6 +87,7 @@ const findTestUtils = (
name: 'abc',
tags: ['foo'],
rule_type_id: 'test.noop',
revision: 0,
running: false,
consumer: 'alertsFixture',
schedule: { interval: '1m' },
@ -369,6 +370,7 @@ export default function createFindTests({ getService }: FtrProviderContext) {
createdAt: match.createdAt,
updatedAt: match.updatedAt,
executionStatus: match.executionStatus,
revision: 0,
running: false,
...(match.nextRun ? { nextRun: match.nextRun } : {}),
...(match.lastRun ? { lastRun: match.lastRun } : {}),

View file

@ -38,6 +38,7 @@ const getTestUtils = (
name: 'abc',
tags: ['foo'],
rule_type_id: 'test.noop',
revision: 0,
running: false,
consumer: 'alertsFixture',
schedule: { interval: '1m' },
@ -155,6 +156,7 @@ export default function createGetTests({ getService }: FtrProviderContext) {
createdAt: response.body.createdAt,
updatedAt: response.body.updatedAt,
executionStatus: response.body.executionStatus,
revision: 0,
running: false,
...(response.body.nextRun ? { nextRun: response.body.nextRun } : {}),
...(response.body.lastRun ? { lastRun: response.body.lastRun } : {}),

View file

@ -60,6 +60,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
mute_all: false,
muted_alert_ids: [],
notify_when: 'onThrottleInterval',
revision: 1,
scheduled_task_id: createdAlert.scheduled_task_id,
created_at: response.body.created_at,
updated_at: response.body.updated_at,
@ -169,6 +170,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
createdAt: response.body.createdAt,
updatedAt: response.body.updatedAt,
executionStatus: response.body.executionStatus,
revision: 1,
running: false,
...(response.body.nextRun ? { nextRun: response.body.nextRun } : {}),
...(response.body.lastRun ? { lastRun: response.body.lastRun } : {}),

View file

@ -46,6 +46,9 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte
.expect(200);
expect(updatedAlert.api_key_owner).to.eql(null);
// Ensure revision is not incremented when API key is updated
expect(updatedAlert.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest: supertestWithoutAuth,
@ -92,6 +95,9 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte
.expect(200);
expect(updatedAlert.api_key_owner).to.eql(null);
// Ensure revision is not incremented when API key is updated
expect(updatedAlert.revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
supertest: supertestWithoutAuth,

View file

@ -76,6 +76,9 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
expect(updatedRule.tags).to.eql(['default', 'tag-1']);
// Ensure revision is updated
expect(updatedRule.revision).to.eql(1);
// Ensure AAD isn't broken
await checkAAD({
supertest,
@ -137,6 +140,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
updatedRules.forEach((rule) => {
expect(rule.tags).to.eql([`rewritten`]);
expect(rule.revision).to.eql(1);
});
});
@ -174,6 +178,9 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
expect(updatedRule.schedule).to.eql({ interval: '1h' });
// Ensure revision is updated
expect(updatedRule.revision).to.eql(1);
// Ensure AAD isn't broken
await checkAAD({
supertest,
@ -217,6 +224,9 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
expect(updatedRule).property('throttle', '1h');
// Ensure revision is updated
expect(updatedRule.revision).to.eql(1);
// Ensure AAD isn't broken
await checkAAD({
supertest,
@ -260,6 +270,9 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
expect(updatedRule).property('notify_when', 'onActionGroupChange');
// Ensure revision is updated
expect(updatedRule.revision).to.eql(1);
// Ensure AAD isn't broken
await checkAAD({
supertest,
@ -305,6 +318,9 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
expect(bulkSnoozeResponse.body.rules[0].snooze_schedule.length).to.eql(1);
expect(bulkSnoozeResponse.body.rules[0].snooze_schedule[0].duration).to.eql(28800000);
// Ensure revision is NOT updated
expect(bulkSnoozeResponse.body.rules[0].revision).to.eql(0);
const bulkUnsnoozeResponse = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_bulk_edit`)
.set('kbn-xsrf', 'foo')
@ -378,6 +394,8 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
expect(bulkSnoozeResponse.body.errors).to.have.length(0);
expect(bulkSnoozeResponse.body.rules).to.have.length(1);
expect(bulkSnoozeResponse.body.rules[0].snooze_schedule.length).to.eql(5);
// Ensure revision is NOT updated
expect(bulkSnoozeResponse.body.rules[0].revision).to.eql(0);
// Try adding more than 5 schedules
const bulkSnoozeError = await supertest
@ -433,6 +451,8 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
expect(bulkSnoozeResponse.body.errors).to.have.length(0);
expect(bulkSnoozeResponse.body.rules).to.have.length(1);
expect(bulkSnoozeResponse.body.rules[0].snooze_schedule).empty();
// Ensure revision is NOT updated
expect(bulkSnoozeResponse.body.rules[0].revision).to.eql(0);
// Ensure AAD isn't broken
await checkAAD({
@ -473,6 +493,8 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
expect(bulkApiKeyResponse.body.errors).to.have.length(0);
expect(bulkApiKeyResponse.body.rules).to.have.length(1);
expect(bulkApiKeyResponse.body.rules[0].api_key_owner).to.eql(null);
// Ensure revision is updated
expect(bulkApiKeyResponse.body.rules[0].revision).to.eql(1);
});
it(`shouldn't bulk edit rule from another space`, async () => {

View file

@ -81,6 +81,7 @@ export default function ({ getService }: FtrProviderContext) {
executionStatus: { status: 'pending', lastExecutionDate: '2022-12-20T09:10:15.500Z' },
ruleTypeId: 'xpack.synthetics.alerts.monitorStatus',
running: false,
revision: 0,
},
omitFields
)

View file

@ -102,6 +102,7 @@ export default ({ getService }: FtrProviderContext) => {
throttle: 'no_actions',
exceptions_list: [],
version: 1,
revision: 0,
};
const { body } = await supertest

View file

@ -361,6 +361,7 @@ export default ({ getService }: FtrProviderContext): void => {
};
ruleOutput.name = 'some other name';
ruleOutput.version = 2;
ruleOutput.revision = 0;
expect(bodyToCompare).to.eql(ruleOutput);
});

View file

@ -50,6 +50,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body);
expect(bodyToCompare).to.eql(outputRule);
});
@ -86,6 +87,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutputWithoutRuleId();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body);
expect(bodyToCompare).to.eql(outputRule);
});
@ -103,6 +105,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body);
expect(bodyToCompare).to.eql(outputRule);
});
@ -138,6 +141,7 @@ export default ({ getService }: FtrProviderContext) => {
outputRule.enabled = false;
outputRule.severity = 'low';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body);
expect(bodyToCompare).to.eql(outputRule);
@ -165,6 +169,7 @@ export default ({ getService }: FtrProviderContext) => {
outputRule.timeline_title = 'some title';
outputRule.timeline_id = 'some id';
outputRule.version = 3;
outputRule.revision = 2;
const bodyToCompare = removeServerGeneratedProperties(body);
expect(bodyToCompare).to.eql(outputRule);

View file

@ -50,6 +50,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect(bodyToCompare).to.eql(outputRule);
});
@ -71,10 +72,12 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule1 = getSimpleRuleOutput();
outputRule1.name = 'some other name';
outputRule1.version = 2;
outputRule1.revision = 1;
const outputRule2 = getSimpleRuleOutput('rule-2');
outputRule2.name = 'some other name';
outputRule2.version = 2;
outputRule2.revision = 1;
const bodyToCompare1 = removeServerGeneratedProperties(body[0]);
const bodyToCompare2 = removeServerGeneratedProperties(body[1]);
@ -95,6 +98,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect(bodyToCompare).to.eql(outputRule);
});
@ -116,10 +120,12 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule1 = getSimpleRuleOutputWithoutRuleId('rule-1');
outputRule1.name = 'some other name';
outputRule1.version = 2;
outputRule1.revision = 1;
const outputRule2 = getSimpleRuleOutputWithoutRuleId('rule-2');
outputRule2.name = 'some other name';
outputRule2.version = 2;
outputRule2.revision = 1;
const bodyToCompare1 = removeServerGeneratedPropertiesIncludingRuleId(body[0]);
const bodyToCompare2 = removeServerGeneratedPropertiesIncludingRuleId(body[1]);
@ -140,6 +146,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect(bodyToCompare).to.eql(outputRule);
});
@ -175,6 +182,7 @@ export default ({ getService }: FtrProviderContext) => {
outputRule.enabled = false;
outputRule.severity = 'low';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect(bodyToCompare).to.eql(outputRule);
@ -202,6 +210,7 @@ export default ({ getService }: FtrProviderContext) => {
outputRule.timeline_title = 'some title';
outputRule.timeline_id = 'some id';
outputRule.version = 3;
outputRule.revision = 2;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect(bodyToCompare).to.eql(outputRule);
@ -256,6 +265,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect([bodyToCompare, body[1]]).to.eql([
@ -286,6 +296,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect([bodyToCompare, body[1]]).to.eql([

View file

@ -57,6 +57,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body);
expect(bodyToCompare).to.eql(outputRule);
});
@ -102,6 +103,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutputWithoutRuleId();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body);
expect(bodyToCompare).to.eql(outputRule);
});
@ -124,6 +126,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body);
expect(bodyToCompare).to.eql(outputRule);
});
@ -146,6 +149,7 @@ export default ({ getService }: FtrProviderContext) => {
outputRule.enabled = false;
outputRule.severity = 'low';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body);
expect(bodyToCompare).to.eql(outputRule);
@ -178,6 +182,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 3;
outputRule.revision = 2;
const bodyToCompare = removeServerGeneratedProperties(body);
expect(bodyToCompare).to.eql(outputRule);

View file

@ -57,6 +57,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect(bodyToCompare).to.eql(outputRule);
});
@ -87,10 +88,12 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule1 = getSimpleRuleOutput();
outputRule1.name = 'some other name';
outputRule1.version = 2;
outputRule1.revision = 1;
const outputRule2 = getSimpleRuleOutput('rule-2');
outputRule2.name = 'some other name';
outputRule2.version = 2;
outputRule2.revision = 1;
const bodyToCompare1 = removeServerGeneratedProperties(body[0]);
const bodyToCompare2 = removeServerGeneratedProperties(body[1]);
@ -116,6 +119,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect(bodyToCompare).to.eql(outputRule);
});
@ -144,10 +148,12 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule1 = getSimpleRuleOutputWithoutRuleId('rule-1');
outputRule1.name = 'some other name';
outputRule1.version = 2;
outputRule1.revision = 1;
const outputRule2 = getSimpleRuleOutputWithoutRuleId('rule-2');
outputRule2.name = 'some other name';
outputRule2.version = 2;
outputRule2.revision = 1;
const bodyToCompare1 = removeServerGeneratedPropertiesIncludingRuleId(body[0]);
const bodyToCompare2 = removeServerGeneratedPropertiesIncludingRuleId(body[1]);
@ -173,6 +179,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect(bodyToCompare).to.eql(outputRule);
});
@ -195,6 +202,7 @@ export default ({ getService }: FtrProviderContext) => {
outputRule.enabled = false;
outputRule.severity = 'low';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect(bodyToCompare).to.eql(outputRule);
@ -227,6 +235,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 3;
outputRule.revision = 2;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect(bodyToCompare).to.eql(outputRule);
@ -294,6 +303,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect([bodyToCompare, body[1]]).to.eql([
@ -331,6 +341,7 @@ export default ({ getService }: FtrProviderContext) => {
const outputRule = getSimpleRuleOutput();
outputRule.name = 'some other name';
outputRule.version = 2;
outputRule.revision = 1;
const bodyToCompare = removeServerGeneratedProperties(body[0]);
expect([bodyToCompare, body[1]]).to.eql([

Some files were not shown because too many files have changed in this diff Show more