[RAM][HTTP Versioning] Version GET Rule Route (#181304)

## Summary

Parent Issue: https://github.com/elastic/kibana/issues/157883
Issue: https://github.com/elastic/kibana/issues/181263

Versions the GET rule endpoint with added input and output validation.

```
GET /internal/alerting/rule/{id}
GET /api/alerting/rule/{id}
```
### Checklist

Delete any items that are not applicable to this PR.
- [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
This commit is contained in:
Jiawei Wu 2024-04-30 22:27:32 +09:00 committed by GitHub
parent 5814f2e0ce
commit a1ada76026
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 707 additions and 299 deletions

View file

@ -0,0 +1,15 @@
/*
* 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.
*/
export { getRuleRequestParamsSchema } from './schemas/latest';
export type { GetRuleRequestParams, GetRuleResponse } from './types/latest';
export { getRuleRequestParamsSchema as getRuleRequestParamsSchemaV1 } from './schemas/v1';
export type {
GetRuleRequestParams as GetRuleRequestParamsV1,
GetRuleResponse as GetRuleResponseV1,
} from './types/v1';

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './v1';

View file

@ -0,0 +1,12 @@
/*
* 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 { schema } from '@kbn/config-schema';
export const getRuleRequestParamsSchema = schema.object({
id: schema.string(),
});

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './v1';

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 type { TypeOf } from '@kbn/config-schema';
import { RuleParamsV1, RuleResponseV1 } from '../../../response';
import { getRuleRequestParamsSchemaV1 } from '..';
export type GetRuleRequestParams = TypeOf<typeof getRuleRequestParamsSchemaV1>;
export interface GetRuleResponse<Params extends RuleParamsV1 = never> {
body: RuleResponseV1<Params>;
}

View file

@ -43,7 +43,7 @@ export interface RuleResponse<Params extends RuleParams = never> {
mute_all: RuleResponseSchemaType['mute_all'];
notify_when?: RuleResponseSchemaType['notify_when'];
muted_alert_ids: RuleResponseSchemaType['muted_alert_ids'];
execution_status: RuleResponseSchemaType['execution_status'];
execution_status?: RuleResponseSchemaType['execution_status'];
monitoring?: RuleResponseSchemaType['monitoring'];
snooze_schedule?: RuleResponseSchemaType['snooze_schedule'];
active_snoozes?: RuleResponseSchemaType['active_snoozes'];

View file

@ -400,6 +400,7 @@ describe('scheduleBackfill()', () => {
rules: [
{
id: existingDecryptedRule1.id,
legacyId: null,
actions: existingDecryptedRule1.attributes.actions,
alertTypeId: existingDecryptedRule1.attributes.alertTypeId,
apiKey: existingDecryptedRule1.attributes.apiKey,
@ -430,6 +431,7 @@ describe('scheduleBackfill()', () => {
},
{
id: existingDecryptedRule2.id,
legacyId: null,
actions: existingDecryptedRule2.attributes.actions,
alertTypeId: existingDecryptedRule2.attributes.alertTypeId,
apiKey: existingDecryptedRule2.attributes.apiKey,

View file

@ -6,7 +6,7 @@
*/
import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../rules_client';
import { RulesClient, ConstructorOptions } from '../../../../rules_client/rules_client';
import {
savedObjectsClientMock,
loggingSystemMock,
@ -14,21 +14,21 @@ import {
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { AlertingAuthorization } from '../../../../authorization/alerting_authorization';
import { ActionsAuthorization } from '@kbn/actions-plugin/server';
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
import { getBeforeSetup, setGlobalDate } from './lib';
import { RecoveredActionGroup } from '../../../common';
import { formatLegacyActions } from '../lib';
import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry';
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
import { backfillClientMock } from '../../backfill_client/backfill_client.mock';
import { getBeforeSetup, setGlobalDate } from '../../../../rules_client/tests/lib';
import { RecoveredActionGroup } from '../../../../../common';
import { formatLegacyActions } from '../../../../rules_client/lib';
import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry';
import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects';
import { backfillClientMock } from '../../../../backfill_client/backfill_client.mock';
jest.mock('../lib/siem_legacy_actions/format_legacy_actions', () => {
jest.mock('../../../../rules_client/lib/siem_legacy_actions/format_legacy_actions', () => {
return {
formatLegacyActions: jest.fn(),
};
@ -91,6 +91,10 @@ describe('get()', () => {
params: {
bar: true,
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
actions: [
@ -128,6 +132,10 @@ describe('get()', () => {
],
"alertTypeId": "123",
"createdAt": 2019-02-12T21:01:22.479Z,
"executionStatus": Object {
"lastExecutionDate": 2020-08-20T19:23:38.000Z,
"status": "unknown",
},
"id": "1",
"notifyWhen": "onActiveAlert",
"params": Object {
@ -143,11 +151,12 @@ describe('get()', () => {
`);
expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
expect(unsecuredSavedObjectsClient.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alert",
"1",
]
`);
Array [
"alert",
"1",
undefined,
]
`);
});
test('gets rule with actions using preconfigured connectors', async () => {
@ -161,6 +170,10 @@ describe('get()', () => {
params: {
bar: true,
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
actions: [
@ -214,6 +227,10 @@ describe('get()', () => {
],
"alertTypeId": "123",
"createdAt": 2019-02-12T21:01:22.479Z,
"executionStatus": Object {
"lastExecutionDate": 2020-08-20T19:23:38.000Z,
"status": "unknown",
},
"id": "1",
"notifyWhen": "onActiveAlert",
"params": Object {
@ -229,11 +246,12 @@ describe('get()', () => {
`);
expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
expect(unsecuredSavedObjectsClient.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alert",
"1",
]
`);
Array [
"alert",
"1",
undefined,
]
`);
});
test('gets rule with actions using system connectors', async () => {
@ -247,6 +265,10 @@ describe('get()', () => {
params: {
bar: true,
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
actions: [
@ -289,6 +311,10 @@ describe('get()', () => {
],
"alertTypeId": "123",
"createdAt": 2019-02-12T21:01:22.479Z,
"executionStatus": Object {
"lastExecutionDate": 2020-08-20T19:23:38.000Z,
"status": "unknown",
},
"id": "1",
"notifyWhen": "onActiveAlert",
"params": Object {
@ -311,11 +337,12 @@ describe('get()', () => {
`);
expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
expect(unsecuredSavedObjectsClient.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alert",
"1",
]
`);
Array [
"alert",
"1",
undefined,
]
`);
});
test('should call useSavedObjectReferences.injectReferences if defined for rule type', async () => {
@ -356,6 +383,10 @@ describe('get()', () => {
bar: true,
parameterThatIsSavedObjectRef: 'soRef_0',
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
actions: [
@ -406,6 +437,10 @@ describe('get()', () => {
],
"alertTypeId": "123",
"createdAt": 2019-02-12T21:01:22.479Z,
"executionStatus": Object {
"lastExecutionDate": 2020-08-20T19:23:38.000Z,
"status": "unknown",
},
"id": "1",
"notifyWhen": "onActiveAlert",
"params": Object {
@ -487,6 +522,10 @@ describe('get()', () => {
bar: true,
parameterThatIsSavedObjectRef: 'soRef_0',
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
actions: [
@ -530,6 +569,10 @@ describe('get()', () => {
params: {
bar: true,
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
actions: [
{
group: 'default',
@ -592,6 +635,10 @@ describe('get()', () => {
params: {
bar: true,
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
actions: [],
},
references: [],
@ -648,6 +695,10 @@ describe('get()', () => {
params: {
bar: true,
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
actions: [

View file

@ -0,0 +1,113 @@
/*
* 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 Boom from '@hapi/boom';
import { AlertConsumers } from '@kbn/rule-data-utils';
import {
SanitizedRule,
SanitizedRuleWithLegacyId,
Rule as DeprecatedRule,
} from '../../../../types';
import { ReadOperations, AlertingAuthorizationEntity } from '../../../../authorization';
import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events';
import { RulesClientContext } from '../../../../rules_client/types';
import { formatLegacyActions } from '../../../../rules_client/lib';
import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects';
import { GetRuleParams } from './types';
import { getRuleParamsSchema } from './schemas';
import { getRuleSo } from '../../../../data/rule';
import { transformRuleAttributesToRuleDomain, transformRuleDomainToRule } from '../../transforms';
import { RuleParams } from '../../types';
import { ruleDomainSchema } from '../../schemas';
export async function getRule<Params extends RuleParams = never>(
context: RulesClientContext,
params: GetRuleParams
): Promise<SanitizedRule<Params> | SanitizedRuleWithLegacyId<Params>> {
const {
id,
includeLegacyId = false,
includeSnoozeData = false,
excludeFromPublicApi = false,
} = params;
try {
getRuleParamsSchema.validate(params);
} catch (error) {
throw Boom.badRequest(`Error validating get data - ${error.message}`);
}
const result = await getRuleSo({
savedObjectsClient: context.unsecuredSavedObjectsClient,
id,
});
try {
await context.authorization.ensureAuthorized({
ruleTypeId: result.attributes.alertTypeId,
consumer: result.attributes.consumer,
operation: ReadOperations.Get,
entity: AlertingAuthorizationEntity.Rule,
});
} catch (error) {
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.GET,
savedObject: { type: RULE_SAVED_OBJECT_TYPE, id },
error,
})
);
throw error;
}
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.GET,
savedObject: { type: RULE_SAVED_OBJECT_TYPE, id },
})
);
const ruleType = context.ruleTypeRegistry.get(result.attributes.alertTypeId);
const ruleDomain = transformRuleAttributesToRuleDomain<Params>(
result.attributes,
{
id: result.id,
logger: context.logger,
ruleType,
references: result.references,
includeSnoozeData,
},
context.isSystemAction
);
// Try to validate created rule, but don't throw.
try {
ruleDomainSchema.validate(ruleDomain);
} catch (e) {
context.logger.warn(`Error validating get rule domain object for id: ${id}, ${e}`);
}
// Convert domain rule to rule (Remove certain properties)
const rule = transformRuleDomainToRule<Params>(ruleDomain, {
isPublic: excludeFromPublicApi,
includeLegacyId,
});
// format legacy actions for SIEM rules
if (result.attributes.consumer === AlertConsumers.SIEM) {
const [migratedRule] = await formatLegacyActions([rule as DeprecatedRule], {
savedObjectsClient: context.unsecuredSavedObjectsClient,
logger: context.logger,
});
return migratedRule;
}
// TODO (http-versioning): Remove this cast, this enables us to move forward
// without fixing all of other solution types
return rule as SanitizedRule<Params>;
}

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export type { GetRuleParams } from './types';
export { getRule } from './get_rule';

View file

@ -0,0 +1,15 @@
/*
* 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 { schema } from '@kbn/config-schema';
export const getRuleParamsSchema = schema.object({
id: schema.string(),
includeLegacyId: schema.maybe(schema.boolean()),
includeSnoozeData: schema.maybe(schema.boolean()),
excludeFromPublicApi: schema.maybe(schema.boolean()),
});

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './get_rule_params_schema';

View file

@ -0,0 +1,11 @@
/*
* 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 { TypeOf } from '@kbn/config-schema';
import { getRuleParamsSchema } from '../schemas';
export type GetRuleParams = TypeOf<typeof getRuleParamsSchema>;

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './get_rule_params';

View file

@ -174,6 +174,7 @@ export const ruleDomainSchema = schema.object({
running: schema.maybe(schema.nullable(schema.boolean())),
viewInAppRelativeUrl: schema.maybe(schema.nullable(schema.string())),
alertDelay: schema.maybe(alertDelaySchema),
legacyId: schema.maybe(schema.nullable(schema.string())),
});
/**
@ -213,4 +214,5 @@ export const ruleSchema = schema.object({
running: schema.maybe(schema.nullable(schema.boolean())),
viewInAppRelativeUrl: schema.maybe(schema.nullable(schema.string())),
alertDelay: schema.maybe(alertDelaySchema),
legacyId: schema.maybe(schema.nullable(schema.string())),
});

View file

@ -10,7 +10,7 @@ import { SavedObjectReference } from '@kbn/core/server';
import { ruleExecutionStatusValues } from '../constants';
import { getRuleSnoozeEndTime } from '../../../lib';
import { RuleDomain, Monitoring, RuleParams } from '../types';
import { RuleAttributes } from '../../../data/rule/types';
import { RuleAttributes, RuleExecutionStatusAttributes } from '../../../data/rule/types';
import { PartialRule } from '../../../types';
import { UntypedNormalizedRuleType } from '../../../rule_type_registry';
import { injectReferencesIntoParams } from '../../../rules_client/common';
@ -32,7 +32,7 @@ const INITIAL_LAST_RUN_METRICS = {
const transformEsExecutionStatus = (
logger: Logger,
ruleId: string,
esRuleExecutionStatus: RuleAttributes['executionStatus']
esRuleExecutionStatus: RuleExecutionStatusAttributes
): RuleDomain['executionStatus'] => {
const {
lastExecutionDate,
@ -204,7 +204,9 @@ export const transformRuleAttributesToRuleDomain = <Params extends RuleParams =
muteAll: esRule.muteAll,
notifyWhen: esRule.notifyWhen,
mutedInstanceIds: esRule.mutedInstanceIds,
executionStatus: transformEsExecutionStatus(logger, id, executionStatus),
...(executionStatus
? { executionStatus: transformEsExecutionStatus(logger, id, executionStatus) }
: {}),
...(monitoring ? { monitoring: transformEsMonitoring(logger, id, monitoring) } : {}),
snoozeSchedule: snoozeScheduleDates ?? [],
...(includeSnoozeData
@ -229,6 +231,7 @@ export const transformRuleAttributesToRuleDomain = <Params extends RuleParams =
revision: esRule.revision,
running: esRule.running,
...(esRule.alertDelay ? { alertDelay: esRule.alertDelay } : {}),
...(esRule.legacyId !== undefined ? { legacyId: esRule.legacyId } : {}),
};
// Bad casts, but will fix once we fix all rule types

View file

@ -0,0 +1,177 @@
/*
* 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 { transformRuleDomainToRule } from './transform_rule_domain_to_rule';
import { RuleDomain } from '../types';
describe('transformRuleDomainToRule', () => {
const MOCK_API_KEY = Buffer.from('123:abc').toString('base64');
const defaultAction = {
uuid: '1',
group: 'default',
id: '1',
actionTypeId: '.test',
params: {},
frequency: {
summary: false,
notifyWhen: 'onThrottleInterval' as const,
throttle: '1m',
},
alertsFilter: {
query: {
kql: 'test:1',
dsl: '{}',
filters: [],
},
},
};
const systemAction = {
id: '2',
uuid: '123',
actionTypeId: '.test-system-action',
params: {},
};
const rule: RuleDomain<{}> = {
id: 'test',
enabled: false,
name: 'my rule name',
tags: ['foo'],
alertTypeId: 'myType',
consumer: 'myApp',
schedule: { interval: '1m' },
actions: [defaultAction],
systemActions: [systemAction],
params: {},
mapped_params: {},
createdBy: 'user',
createdAt: new Date('2019-02-12T21:01:22.479Z'),
updatedAt: new Date('2019-02-12T21:01:22.479Z'),
legacyId: 'legacyId',
muteAll: false,
mutedInstanceIds: [],
snoozeSchedule: [],
scheduledTaskId: 'task-123',
executionStatus: {
lastExecutionDate: new Date('2019-02-12T21:01:22.479Z'),
status: 'pending' as const,
},
throttle: null,
notifyWhen: null,
revision: 0,
updatedBy: 'user',
apiKey: MOCK_API_KEY,
apiKeyOwner: 'user',
};
it('should transform rule domain to rule', () => {
const result = transformRuleDomainToRule(rule);
expect(result).toEqual({
id: 'test',
enabled: false,
name: 'my rule name',
tags: ['foo'],
alertTypeId: 'myType',
consumer: 'myApp',
schedule: { interval: '1m' },
actions: [defaultAction],
systemActions: [systemAction],
params: {},
mapped_params: {},
createdBy: 'user',
createdAt: new Date('2019-02-12T21:01:22.479Z'),
updatedAt: new Date('2019-02-12T21:01:22.479Z'),
muteAll: false,
mutedInstanceIds: [],
snoozeSchedule: [],
scheduledTaskId: 'task-123',
executionStatus: {
lastExecutionDate: new Date('2019-02-12T21:01:22.479Z'),
status: 'pending' as const,
},
throttle: null,
notifyWhen: null,
revision: 0,
updatedBy: 'user',
apiKeyOwner: 'user',
});
});
it('should remove public fields if isPublic is true', () => {
const result = transformRuleDomainToRule(rule, {
isPublic: true,
});
expect(result).toEqual({
id: 'test',
enabled: false,
name: 'my rule name',
tags: ['foo'],
alertTypeId: 'myType',
consumer: 'myApp',
schedule: { interval: '1m' },
actions: [defaultAction],
systemActions: [systemAction],
params: {},
mapped_params: {},
createdBy: 'user',
createdAt: new Date('2019-02-12T21:01:22.479Z'),
updatedAt: new Date('2019-02-12T21:01:22.479Z'),
muteAll: false,
mutedInstanceIds: [],
scheduledTaskId: 'task-123',
executionStatus: {
lastExecutionDate: new Date('2019-02-12T21:01:22.479Z'),
status: 'pending' as const,
},
throttle: null,
notifyWhen: null,
revision: 0,
updatedBy: 'user',
apiKeyOwner: 'user',
});
});
it('should include legacy id if includeLegacyId is true', () => {
const result = transformRuleDomainToRule(rule, {
includeLegacyId: true,
});
expect(result).toEqual({
id: 'test',
enabled: false,
name: 'my rule name',
tags: ['foo'],
alertTypeId: 'myType',
consumer: 'myApp',
schedule: { interval: '1m' },
legacyId: 'legacyId',
actions: [defaultAction],
systemActions: [systemAction],
params: {},
mapped_params: {},
createdBy: 'user',
createdAt: new Date('2019-02-12T21:01:22.479Z'),
updatedAt: new Date('2019-02-12T21:01:22.479Z'),
muteAll: false,
mutedInstanceIds: [],
snoozeSchedule: [],
scheduledTaskId: 'task-123',
executionStatus: {
lastExecutionDate: new Date('2019-02-12T21:01:22.479Z'),
status: 'pending' as const,
},
throttle: null,
notifyWhen: null,
revision: 0,
updatedBy: 'user',
apiKeyOwner: 'user',
});
});
});

View file

@ -9,13 +9,14 @@ import { RuleDomain, Rule, RuleParams } from '../types';
interface TransformRuleDomainToRuleOptions {
isPublic?: boolean;
includeLegacyId?: boolean;
}
export const transformRuleDomainToRule = <Params extends RuleParams = never>(
ruleDomain: RuleDomain<Params>,
options?: TransformRuleDomainToRuleOptions
): Rule<Params> => {
const { isPublic = false } = options || {};
const { isPublic = false, includeLegacyId = false } = options || {};
const rule: Rule<Params> = {
id: ruleDomain.id,
@ -51,6 +52,7 @@ export const transformRuleDomainToRule = <Params extends RuleParams = never>(
running: ruleDomain.running,
viewInAppRelativeUrl: ruleDomain.viewInAppRelativeUrl,
alertDelay: ruleDomain.alertDelay,
legacyId: ruleDomain.legacyId,
};
if (isPublic) {
@ -61,6 +63,10 @@ export const transformRuleDomainToRule = <Params extends RuleParams = never>(
delete rule.viewInAppRelativeUrl;
}
if (!includeLegacyId) {
delete rule.legacyId;
}
// Remove all undefined keys to clean up the object
type RuleKeys = keyof Rule;
for (const key in rule) {

View file

@ -53,17 +53,23 @@ export const transformRuleDomainToRuleAttributes = ({
muteAll: rule.muteAll,
mutedInstanceIds: rule.mutedInstanceIds,
...(meta ? { meta } : {}),
executionStatus: {
status: rule.executionStatus.status,
lastExecutionDate: rule.executionStatus.lastExecutionDate.toISOString(),
...(rule.executionStatus.lastDuration
? { lastDuration: rule.executionStatus.lastDuration }
: {}),
...(rule.executionStatus.error !== undefined ? { error: rule.executionStatus.error } : {}),
...(rule.executionStatus.warning !== undefined
? { warning: rule.executionStatus.warning }
: {}),
},
...(rule.executionStatus
? {
executionStatus: {
status: rule.executionStatus.status,
lastExecutionDate: rule.executionStatus.lastExecutionDate.toISOString(),
...(rule.executionStatus.lastDuration
? { lastDuration: rule.executionStatus.lastDuration }
: {}),
...(rule.executionStatus.error !== undefined
? { error: rule.executionStatus.error }
: {}),
...(rule.executionStatus.warning !== undefined
? { warning: rule.executionStatus.warning }
: {}),
},
}
: {}),
...(rule.monitoring ? { monitoring: rule.monitoring } : {}),
...(rule.snoozeSchedule ? { snoozeSchedule: rule.snoozeSchedule } : {}),
...(rule.isSnoozedUntil !== undefined

View file

@ -72,7 +72,7 @@ export interface Rule<Params extends RuleParams = never> {
muteAll: RuleSchemaType['muteAll'];
notifyWhen?: RuleSchemaType['notifyWhen'];
mutedInstanceIds: RuleSchemaType['mutedInstanceIds'];
executionStatus: RuleExecutionStatus;
executionStatus?: RuleExecutionStatus;
monitoring?: RuleSchemaType['monitoring'];
snoozeSchedule?: RuleSchemaType['snoozeSchedule'];
activeSnoozes?: RuleSchemaType['activeSnoozes'];
@ -83,6 +83,7 @@ export interface Rule<Params extends RuleParams = never> {
running?: RuleSchemaType['running'];
viewInAppRelativeUrl?: RuleSchemaType['viewInAppRelativeUrl'];
alertDelay?: RuleSchemaType['alertDelay'];
legacyId?: RuleSchemaType['legacyId'];
}
export interface RuleDomain<Params extends RuleParams = never> {
@ -109,7 +110,7 @@ export interface RuleDomain<Params extends RuleParams = never> {
muteAll: RuleDomainSchemaType['muteAll'];
notifyWhen?: RuleDomainSchemaType['notifyWhen'];
mutedInstanceIds: RuleDomainSchemaType['mutedInstanceIds'];
executionStatus: RuleExecutionStatus;
executionStatus?: RuleExecutionStatus;
monitoring?: RuleDomainSchemaType['monitoring'];
snoozeSchedule?: RuleDomainSchemaType['snoozeSchedule'];
activeSnoozes?: RuleDomainSchemaType['activeSnoozes'];
@ -120,4 +121,5 @@ export interface RuleDomain<Params extends RuleParams = never> {
running?: RuleDomainSchemaType['running'];
viewInAppRelativeUrl?: RuleDomainSchemaType['viewInAppRelativeUrl'];
alertDelay?: RuleSchemaType['alertDelay'];
legacyId?: RuleSchemaType['legacyId'];
}

View file

@ -171,7 +171,7 @@ export interface RuleAttributes {
muteAll: boolean;
mutedInstanceIds: string[];
meta?: RuleMetaAttributes;
executionStatus: RuleExecutionStatusAttributes;
executionStatus?: RuleExecutionStatusAttributes;
monitoring?: RuleMonitoringAttributes;
snoozeSchedule?: RuleSnoozeScheduleAttributes[];
isSnoozedUntil?: string | null;

View file

@ -1,129 +0,0 @@
/*
* 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 { omit } from 'lodash';
import { schema } from '@kbn/config-schema';
import { IRouter } from '@kbn/core/server';
import { ILicenseState } from '../lib';
import { verifyAccessAndContext, rewriteRuleLastRun } from './lib';
import {
RuleTypeParams,
AlertingRequestHandlerContext,
BASE_ALERTING_API_PATH,
INTERNAL_BASE_ALERTING_API_PATH,
SanitizedRule,
} from '../types';
import { transformRuleActions } from './rule/transforms';
const paramSchema = schema.object({
id: schema.string(),
});
const rewriteBodyRes = ({
alertTypeId,
createdBy,
updatedBy,
createdAt,
updatedAt,
apiKeyOwner,
apiKeyCreatedByUser,
notifyWhen,
muteAll,
mutedInstanceIds,
executionStatus,
actions,
systemActions,
scheduledTaskId,
snoozeSchedule,
isSnoozedUntil,
lastRun,
nextRun,
viewInAppRelativeUrl,
...rest
}: SanitizedRule<RuleTypeParams>) => ({
...rest,
rule_type_id: alertTypeId,
created_by: createdBy,
updated_by: updatedBy,
created_at: createdAt,
updated_at: updatedAt,
api_key_owner: apiKeyOwner,
notify_when: notifyWhen,
muted_alert_ids: mutedInstanceIds,
mute_all: muteAll,
...(isSnoozedUntil !== undefined ? { is_snoozed_until: isSnoozedUntil } : {}),
snooze_schedule: snoozeSchedule,
scheduled_task_id: scheduledTaskId,
execution_status: executionStatus && {
...omit(executionStatus, 'lastExecutionDate', 'lastDuration'),
last_execution_date: executionStatus.lastExecutionDate,
last_duration: executionStatus.lastDuration,
},
actions: transformRuleActions(actions, systemActions),
...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}),
...(nextRun ? { next_run: nextRun } : {}),
...(viewInAppRelativeUrl ? { view_in_app_relative_url: viewInAppRelativeUrl } : {}),
...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}),
});
interface BuildGetRulesRouteParams {
licenseState: ILicenseState;
path: string;
router: IRouter<AlertingRequestHandlerContext>;
excludeFromPublicApi?: boolean;
}
const buildGetRuleRoute = ({
licenseState,
path,
router,
excludeFromPublicApi = false,
}: BuildGetRulesRouteParams) => {
router.get(
{
path,
validate: {
params: paramSchema,
},
},
router.handleLegacyErrors(
verifyAccessAndContext(licenseState, async function (context, req, res) {
const rulesClient = (await context.alerting).getRulesClient();
const { id } = req.params;
const rule = await rulesClient.get({
id,
excludeFromPublicApi,
includeSnoozeData: true,
});
return res.ok({
body: rewriteBodyRes(rule),
});
})
)
);
};
export const getRuleRoute = (
router: IRouter<AlertingRequestHandlerContext>,
licenseState: ILicenseState
) =>
buildGetRuleRoute({
excludeFromPublicApi: true,
licenseState,
path: `${BASE_ALERTING_API_PATH}/rule/{id}`,
router,
});
export const getInternalRuleRoute = (
router: IRouter<AlertingRequestHandlerContext>,
licenseState: ILicenseState
) =>
buildGetRuleRoute({
excludeFromPublicApi: false,
licenseState,
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}`,
router,
});

View file

@ -14,7 +14,7 @@ import { GetAlertIndicesAlias, ILicenseState } from '../lib';
import { defineLegacyRoutes } from './legacy';
import { AlertingRequestHandlerContext } from '../types';
import { createRuleRoute } from './rule/apis/create';
import { getRuleRoute, getInternalRuleRoute } from './get_rule';
import { getRuleRoute, getInternalRuleRoute } from './rule/apis/get/get_rule_route';
import { updateRuleRoute } from './rule/apis/update/update_rule_route';
import { deleteRuleRoute } from './delete_rule';
import { aggregateRulesRoute } from './rule/apis/aggregate/aggregate_rules_route';

View file

@ -6,16 +6,16 @@
*/
import { pick } from 'lodash';
import { getRuleRoute } from './get_rule';
import { getRuleRoute } from './get_rule_route';
import { httpServiceMock } from '@kbn/core/server/mocks';
import { licenseStateMock } from '../lib/license_state.mock';
import { verifyApiAccess } from '../lib/license_api_access';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { rulesClientMock } from '../rules_client.mock';
import { RuleAction, RuleSystemAction, SanitizedRule } from '../types';
import { licenseStateMock } from '../../../../lib/license_state.mock';
import { verifyApiAccess } from '../../../../lib/license_api_access';
import { mockHandlerArguments } from '../../../_mock_handler_arguments';
import { rulesClientMock } from '../../../../rules_client.mock';
import { RuleAction, RuleSystemAction, SanitizedRule } from '../../../../types';
const rulesClient = rulesClientMock.create();
jest.mock('../lib/license_api_access', () => ({
jest.mock('../../../../lib/license_api_access', () => ({
verifyApiAccess: jest.fn(),
}));
@ -64,8 +64,8 @@ describe('getRuleRoute', () => {
params: {
bar: true,
},
createdAt: new Date(),
updatedAt: new Date(),
createdAt: new Date('2020-08-20T19:23:38Z'),
updatedAt: new Date('2020-08-20T19:23:38Z'),
actions: [action],
consumer: 'bar',
name: 'abc',
@ -95,13 +95,13 @@ describe('getRuleRoute', () => {
updated_by: mockedAlert.updatedBy,
api_key_owner: mockedAlert.apiKeyOwner,
muted_alert_ids: mockedAlert.mutedInstanceIds,
created_at: mockedAlert.createdAt,
updated_at: mockedAlert.updatedAt,
created_at: mockedAlert.createdAt.toISOString(),
updated_at: mockedAlert.updatedAt.toISOString(),
id: mockedAlert.id,
revision: mockedAlert.revision,
execution_status: {
status: mockedAlert.executionStatus.status,
last_execution_date: mockedAlert.executionStatus.lastExecutionDate,
last_execution_date: mockedAlert.executionStatus.lastExecutionDate.toISOString(),
},
actions: [
{

View file

@ -0,0 +1,87 @@
/*
* 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 { IRouter } from '@kbn/core/server';
import { ILicenseState } from '../../../../lib';
import { verifyAccessAndContext } from '../../../lib';
import type { RuleParamsV1 } from '../../../../../common/routes/rule/response';
import { Rule } from '../../../../application/rule/types';
import {
AlertingRequestHandlerContext,
BASE_ALERTING_API_PATH,
INTERNAL_BASE_ALERTING_API_PATH,
} from '../../../../types';
import { transformRuleToRuleResponseV1 } from '../../transforms';
import type {
GetRuleRequestParamsV1,
GetRuleResponseV1,
} from '../../../../../common/routes/rule/apis/get';
import { getRuleRequestParamsSchemaV1 } from '../../../../../common/routes/rule/apis/get';
interface BuildGetRulesRouteParams {
licenseState: ILicenseState;
path: string;
router: IRouter<AlertingRequestHandlerContext>;
excludeFromPublicApi?: boolean;
}
const buildGetRuleRoute = ({
licenseState,
path,
router,
excludeFromPublicApi = false,
}: BuildGetRulesRouteParams) => {
router.get(
{
path,
validate: {
params: getRuleRequestParamsSchemaV1,
},
},
router.handleLegacyErrors(
verifyAccessAndContext(licenseState, async function (context, req, res) {
const rulesClient = (await context.alerting).getRulesClient();
const params: GetRuleRequestParamsV1 = req.params;
// TODO (http-versioning): Remove this cast, this enables us to move forward
// without fixing all of other solution types
const rule: Rule<RuleParamsV1> = (await rulesClient.get({
id: params.id,
excludeFromPublicApi,
includeSnoozeData: true,
})) as Rule<RuleParamsV1>;
const response: GetRuleResponseV1<RuleParamsV1> = {
body: transformRuleToRuleResponseV1<RuleParamsV1>(rule),
};
return res.ok(response);
})
)
);
};
export const getRuleRoute = (
router: IRouter<AlertingRequestHandlerContext>,
licenseState: ILicenseState
) =>
buildGetRuleRoute({
excludeFromPublicApi: true,
licenseState,
path: `${BASE_ALERTING_API_PATH}/rule/{id}`,
router,
});
export const getInternalRuleRoute = (
router: IRouter<AlertingRequestHandlerContext>,
licenseState: ILicenseState
) =>
buildGetRuleRoute({
excludeFromPublicApi: false,
licenseState,
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}`,
router,
});

View file

@ -102,8 +102,8 @@ describe('resolveRuleRoute', () => {
id: mockedRule.id,
revision: mockedRule.revision,
execution_status: {
status: mockedRule.executionStatus.status,
last_execution_date: mockedRule.executionStatus.lastExecutionDate.toISOString(),
status: mockedRule.executionStatus!.status,
last_execution_date: mockedRule.executionStatus!.lastExecutionDate.toISOString(),
},
actions: [
{

View file

@ -115,15 +115,19 @@ export const transformRuleToRuleResponse = <Params extends RuleParams = never>(
...(rule.notifyWhen !== undefined ? { notify_when: rule.notifyWhen } : {}),
muted_alert_ids: rule.mutedInstanceIds,
...(rule.scheduledTaskId !== undefined ? { scheduled_task_id: rule.scheduledTaskId } : {}),
execution_status: {
status: rule.executionStatus.status,
...(rule.executionStatus.error ? { error: rule.executionStatus.error } : {}),
...(rule.executionStatus.warning ? { warning: rule.executionStatus.warning } : {}),
last_execution_date: rule.executionStatus.lastExecutionDate?.toISOString(),
...(rule.executionStatus.lastDuration !== undefined
? { last_duration: rule.executionStatus.lastDuration }
: {}),
},
...(rule.executionStatus
? {
execution_status: {
status: rule.executionStatus.status,
...(rule.executionStatus.error ? { error: rule.executionStatus.error } : {}),
...(rule.executionStatus.warning ? { warning: rule.executionStatus.warning } : {}),
last_execution_date: rule.executionStatus.lastExecutionDate?.toISOString(),
...(rule.executionStatus.lastDuration !== undefined
? { last_duration: rule.executionStatus.lastDuration }
: {}),
},
}
: {}),
...(rule.monitoring ? { monitoring: transformMonitoring(rule.monitoring) } : {}),
...(rule.snoozeSchedule ? { snooze_schedule: rule.snoozeSchedule } : {}),
...(rule.activeSnoozes ? { active_snoozes: rule.activeSnoozes } : {}),

View file

@ -1,79 +0,0 @@
/*
* 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 { AlertConsumers } from '@kbn/rule-data-utils';
import { RawRule, SanitizedRule, RuleTypeParams, SanitizedRuleWithLegacyId } from '../../types';
import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { getAlertFromRaw } from '../lib/get_alert_from_raw';
import { RulesClientContext } from '../types';
import { formatLegacyActions } from '../lib';
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
export interface GetParams {
id: string;
includeLegacyId?: boolean;
includeSnoozeData?: boolean;
excludeFromPublicApi?: boolean;
}
export async function get<Params extends RuleTypeParams = never>(
context: RulesClientContext,
{
id,
includeLegacyId = false,
includeSnoozeData = false,
excludeFromPublicApi = false,
}: GetParams
): Promise<SanitizedRule<Params> | SanitizedRuleWithLegacyId<Params>> {
const result = await context.unsecuredSavedObjectsClient.get<RawRule>(RULE_SAVED_OBJECT_TYPE, id);
try {
await context.authorization.ensureAuthorized({
ruleTypeId: result.attributes.alertTypeId,
consumer: result.attributes.consumer,
operation: ReadOperations.Get,
entity: AlertingAuthorizationEntity.Rule,
});
} catch (error) {
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.GET,
savedObject: { type: RULE_SAVED_OBJECT_TYPE, id },
error,
})
);
throw error;
}
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.GET,
savedObject: { type: RULE_SAVED_OBJECT_TYPE, id },
})
);
const rule = getAlertFromRaw<Params>(
context,
result.id,
result.attributes.alertTypeId,
result.attributes,
result.references,
includeLegacyId,
excludeFromPublicApi,
includeSnoozeData
);
// format legacy actions for SIEM rules
if (result.attributes.consumer === AlertConsumers.SIEM) {
const [migratedRule] = await formatLegacyActions([rule], {
savedObjectsClient: context.unsecuredSavedObjectsClient,
logger: context.logger,
});
return migratedRule;
}
return rule;
}

View file

@ -19,7 +19,7 @@ import { IExecutionErrorsResult } from '../../../common';
import { formatExecutionErrorsResult } from '../../lib/format_execution_log_errors';
import { parseDate } from '../common';
import { RulesClientContext } from '../types';
import { get } from './get';
import { getRule } from '../../application/rule/methods/get/get_rule';
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
const actionErrorLogDefaultFilter =
@ -41,7 +41,7 @@ export async function getActionErrorLog(
{ id, dateStart, dateEnd, filter, page, perPage, sort }: GetActionErrorLogByIdParams
): Promise<IExecutionErrorsResult> {
context.logger.debug(`getActionErrorLog(): getting action error logs for rule ${id}`);
const rule = (await get(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId;
const rule = (await getRule(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId;
try {
await context.authorization.ensureAuthorized({

View file

@ -10,7 +10,7 @@ import { RuleTaskState } from '../../types';
import { taskInstanceToAlertTaskInstance } from '../../task_runner/alert_task_instance';
import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization';
import { RulesClientContext } from '../types';
import { get } from './get';
import { getRule } from '../../application/rule/methods/get/get_rule';
export interface GetAlertStateParams {
id: string;
@ -19,7 +19,7 @@ export async function getAlertState(
context: RulesClientContext,
{ id }: GetAlertStateParams
): Promise<RuleTaskState | void> {
const rule = await get(context, { id });
const rule = await getRule(context, { id });
await context.authorization.ensureAuthorized({
ruleTypeId: rule.alertTypeId,
consumer: rule.consumer,

View file

@ -12,7 +12,7 @@ import { alertSummaryFromEventLog } from '../../lib/alert_summary_from_event_log
import { parseDuration } from '../../../common/parse_duration';
import { parseDate } from '../common';
import { RulesClientContext } from '../types';
import { get } from './get';
import { getRule } from '../../application/rule/methods/get/get_rule';
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
export interface GetAlertSummaryParams {
@ -26,7 +26,7 @@ export async function getAlertSummary(
{ id, dateStart, numberOfExecutions }: GetAlertSummaryParams
): Promise<AlertSummary> {
context.logger.debug(`getAlertSummary(): getting alert ${id}`);
const rule = (await get(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId;
const rule = (await getRule(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId;
await context.authorization.ensureAuthorized({
ruleTypeId: rule.alertTypeId,

View file

@ -19,7 +19,7 @@ import {
} from '../../lib/get_execution_log_aggregation';
import { RulesClientContext } from '../types';
import { parseDate } from '../common';
import { get } from './get';
import { getRule } from '../../application/rule/methods/get/get_rule';
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
export interface GetRuleExecutionKPIParams {
@ -41,7 +41,7 @@ export async function getRuleExecutionKPI(
{ id, dateStart, dateEnd, filter }: GetRuleExecutionKPIParams
) {
context.logger.debug(`getRuleExecutionKPI(): getting execution KPI for rule ${id}`);
const rule = (await get(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId;
const rule = (await getRule(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId;
try {
// Make sure user has access to this rule

View file

@ -21,7 +21,7 @@ import {
import { IExecutionLogResult } from '../../../common';
import { parseDate } from '../common';
import { RulesClientContext } from '../types';
import { get } from './get';
import { getRule } from '../../application/rule/methods/get/get_rule';
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
export interface GetExecutionLogByIdParams {
@ -49,7 +49,7 @@ export async function getExecutionLogForRule(
{ id, dateStart, dateEnd, filter, page, perPage, sort }: GetExecutionLogByIdParams
): Promise<IExecutionLogResult> {
context.logger.debug(`getExecutionLogForRule(): getting execution log for rule ${id}`);
const rule = (await get(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId;
const rule = (await getRule(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId;
try {
// Make sure user has access to this rule

View file

@ -15,7 +15,7 @@ import { createRule, CreateRuleParams } from '../application/rule/methods/create
import { updateRule, UpdateRuleParams } from '../application/rule/methods/update';
import { snoozeRule, SnoozeRuleOptions } from '../application/rule/methods/snooze';
import { unsnoozeRule, UnsnoozeParams } from '../application/rule/methods/unsnooze';
import { get, GetParams } from './methods/get';
import { getRule, GetRuleParams } from '../application/rule/methods/get';
import { resolveRule, ResolveParams } from '../application/rule/methods/resolve';
import { getAlertState, GetAlertStateParams } from './methods/get_alert_state';
import { getAlertSummary, GetAlertSummaryParams } from './methods/get_alert_summary';
@ -134,8 +134,8 @@ export class RulesClient {
public delete = (params: { id: string }) => deleteRule(this.context, params);
public find = <Params extends RuleTypeParams = never>(params?: FindParams) =>
find<Params>(this.context, params);
public get = <Params extends RuleTypeParams = never>(params: GetParams) =>
get<Params>(this.context, params);
public get = <Params extends RuleTypeParams = never>(params: GetRuleParams) =>
getRule<Params>(this.context, params);
public resolve = <Params extends RuleTypeParams = never>(params: ResolveParams) =>
resolveRule<Params>(this.context, params);
public update = <Params extends RuleTypeParams = never>(params: UpdateRuleParams<Params>) =>

View file

@ -80,6 +80,10 @@ describe('getAlertState()', () => {
params: {
bar: true,
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
actions: [
{
group: 'default',
@ -116,11 +120,12 @@ describe('getAlertState()', () => {
await rulesClient.getAlertState({ id: '1' });
expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
expect(unsecuredSavedObjectsClient.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alert",
"1",
]
`);
Array [
"alert",
"1",
undefined,
]
`);
});
test('gets the underlying task from TaskManager', async () => {
@ -137,6 +142,10 @@ describe('getAlertState()', () => {
params: {
bar: true,
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
actions: [
{
group: 'default',
@ -195,6 +204,10 @@ describe('getAlertState()', () => {
params: {
bar: true,
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
actions: [],
enabled: true,
scheduledTaskId,
@ -208,8 +221,7 @@ describe('getAlertState()', () => {
await rulesClient.getAlertState({ id: '1' });
expect(rulesClientParams.logger.warn).toHaveBeenCalledTimes(1);
expect(rulesClientParams.logger.warn).toHaveBeenCalledWith('Task (task-123) not found');
expect(rulesClientParams.logger.warn).toHaveBeenNthCalledWith(2, 'Task (task-123) not found');
});
test('logs a warning if the taskManager throws an error', async () => {
@ -226,6 +238,10 @@ describe('getAlertState()', () => {
params: {
bar: true,
},
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
actions: [],
enabled: true,
scheduledTaskId,
@ -239,8 +255,8 @@ describe('getAlertState()', () => {
await rulesClient.getAlertState({ id: '1' });
expect(rulesClientParams.logger.warn).toHaveBeenCalledTimes(1);
expect(rulesClientParams.logger.warn).toHaveBeenCalledWith(
expect(rulesClientParams.logger.warn).toHaveBeenNthCalledWith(
2,
'An error occurred when getting the task state for (task-123): Bad Request'
);
});
@ -257,6 +273,11 @@ describe('getAlertState()', () => {
params: {
bar: true,
},
enabled: true,
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
actions: [
{
group: 'default',

View file

@ -27,6 +27,7 @@
"tags": [
],
"throttle": null,
"updatedAt": "2020-06-17T15:35:38.497Z",
"updatedBy": "elastic"
},
"migrationVersion": {
@ -133,6 +134,7 @@
"tags": [
],
"throttle": null,
"updatedAt": "2020-09-22T15:16:07.451Z",
"updatedBy": "elastic"
},
"migrationVersion": {
@ -210,6 +212,7 @@
"tags": [
],
"throttle": null,
"updatedAt": "2020-06-17T15:35:38.497Z",
"updatedBy": "elastic"
},
"migrationVersion": {
@ -306,6 +309,7 @@
"foo"
],
"throttle": "1m",
"updatedAt": "2020-09-22T15:16:07.451Z",
"updatedBy": null
},
"migrationVersion": {
@ -372,6 +376,7 @@
"apiKey" : null,
"createdBy" : "elastic",
"updatedBy" : "elastic",
"updatedAt" : "2021-07-27T20:42:55.896Z",
"createdAt" : "2021-07-27T20:42:55.896Z",
"muteAll" : false,
"mutedInstanceIds" : [ ],
@ -431,6 +436,7 @@
"tags": [
],
"throttle": null,
"updatedAt": "2020-09-22T15:16:07.451Z",
"updatedBy": "elastic"
},
"migrationVersion": {
@ -478,6 +484,7 @@
"createdBy" : "elastic",
"updatedBy" : "elastic",
"createdAt" : "2021-07-27T20:42:55.896Z",
"updatedAt" : "2021-07-27T20:42:55.896Z",
"muteAll" : false,
"mutedInstanceIds" : [ ],
"scheduledTaskId" : null,
@ -518,6 +525,7 @@
"createdBy" : "elastic",
"updatedBy" : "elastic",
"createdAt" : "2021-07-27T20:42:55.896Z",
"updatedAt" : "2021-07-27T20:42:55.896Z",
"muteAll" : false,
"mutedInstanceIds" : [ ],
"scheduledTaskId" : null,
@ -558,6 +566,7 @@
"apiKey" : null,
"createdBy" : "elastic",
"updatedBy" : "elastic",
"updatedAt" : "2021-07-27T20:42:55.896Z",
"createdAt" : "2021-07-27T20:42:55.896Z",
"muteAll" : false,
"mutedInstanceIds" : [ ],
@ -598,6 +607,7 @@
"apiKey" : null,
"createdBy" : "elastic",
"updatedBy" : "elastic",
"updatedAt" : "2021-07-27T20:42:55.896Z",
"createdAt" : "2021-07-27T20:42:55.896Z",
"muteAll" : false,
"mutedInstanceIds" : [ ],
@ -647,6 +657,7 @@
"createdBy" : "elastic",
"updatedBy" : "elastic",
"createdAt" : "2021-07-27T20:42:55.896Z",
"updatedAt" : "2021-07-27T20:42:55.896Z",
"muteAll" : false,
"mutedInstanceIds" : [ ],
"scheduledTaskId" : null,
@ -740,6 +751,7 @@
"createdBy":"3270256467",
"updatedBy":"3270256467",
"createdAt":"2022-02-16T01:24:02.121Z",
"updatedAt":"2021-07-27T20:42:55.896Z",
"muteAll":true,
"mutedInstanceIds":[
@ -833,6 +845,7 @@
"createdBy":"3270256467",
"updatedBy":"3270256467",
"createdAt":"2022-02-16T01:25:02.121Z",
"updatedAt":"2022-02-16T01:25:02.121Z",
"muteAll":true,
"mutedInstanceIds":[
@ -875,6 +888,7 @@
"createdBy" : "elastic",
"updatedBy" : "elastic",
"createdAt" : "2021-07-27T20:42:55.896Z",
"updatedAt": "2021-07-27T20:42:55.896Z",
"muteAll" : false,
"mutedInstanceIds" : [ ],
"scheduledTaskId" : null,
@ -926,6 +940,7 @@
"createdBy" : "elastic",
"updatedBy" : "elastic",
"createdAt": "2022-03-26T16:04:50.698Z",
"updatedAt": "2022-03-26T16:04:50.698Z",
"muteAll": false,
"mutedInstanceIds": [],
"scheduledTaskId": "776cb5c0-ad1e-11ec-ab9e-5f5932f4fad8",
@ -1023,6 +1038,7 @@
"createdBy":"elastic",
"updatedBy":"elastic",
"createdAt":"2021-07-27T20:42:55.896Z",
"updatedAt":"2021-07-27T20:42:55.896Z",
"muteAll":true,
"mutedInstanceIds":[
@ -1082,6 +1098,7 @@
"createdBy" : "elastic",
"updatedBy" : "elastic",
"createdAt": "2022-03-26T16:04:50.698Z",
"updatedAt":"2022-03-26T16:04:50.698Z",
"muteAll": false,
"mutedInstanceIds": [],
"scheduledTaskId": "c8b39c29-d860-43b6-8817-b8058d80ddbc",
@ -1133,6 +1150,7 @@
"createdBy" : "elastic",
"updatedBy" : "elastic",
"createdAt": "2022-03-26T16:04:50.698Z",
"updatedAt": "2022-03-26T16:04:50.698Z",
"muteAll": false,
"mutedInstanceIds": [],
"scheduledTaskId": "62c62b7f-8bf3-4104-a064-6247b7bda44f",
@ -1184,6 +1202,7 @@
"createdBy" : "elastic",
"updatedBy" : "elastic",
"createdAt": "2022-03-26T16:04:50.698Z",
"updatedAt": "2022-03-26T16:04:50.698Z",
"muteAll": false,
"mutedInstanceIds": [],
"scheduledTaskId": "f0d13f4d-35ae-4554-897a-6392e97bb84c",
@ -1225,6 +1244,7 @@
"tags": [],
"throttle": null,
"updatedBy": "elastic",
"updatedAt": "2022-08-24T19:02:30.889Z",
"isSnoozedUntil": "2022-08-24T19:05:49.817Z"
},
"migrationVersion": {
@ -1263,6 +1283,7 @@
"tags": [],
"throttle": null,
"updatedBy": "elastic",
"updatedAt": "2022-08-24T19:02:30.889Z",
"isSnoozedUntil": "2022-08-24T19:05:49.817Z",
"executionStatus": {
"status": "ok",
@ -1321,6 +1342,7 @@
"tags": [],
"throttle": null,
"updatedBy": "elastic",
"updatedAt": "2022-08-24T19:02:30.889Z",
"isSnoozedUntil": "2022-08-24T19:05:49.817Z",
"executionStatus": {
"status": "warning",
@ -1394,6 +1416,7 @@
"createdBy" : "elastic",
"updatedBy" : "elastic",
"createdAt": "2023-01-26T14:20:13.449Z",
"updatedAt": "2023-01-26T14:20:13.449Z",
"muteAll": false,
"mutedInstanceIds": [],
"scheduledTaskId": "8bd01ff0-9d84-11ed-994d-f1971f849da5",
@ -1457,6 +1480,7 @@
"createdBy":"elastic",
"updatedBy":"elastic",
"createdAt":"2023-03-27T20:42:55.896Z",
"updatedAt":"2023-03-27T20:42:55.896Z",
"muteAll":true,
"mutedInstanceIds":[],
"scheduledTaskId":null,

View file

@ -173,6 +173,9 @@
"updatedBy": {
"type": "keyword"
},
"updatedAt": {
"type": "date"
},
"isSnoozedUntil": {
"type": "date"
},

View file

@ -27,6 +27,7 @@
"tags": [
],
"throttle": null,
"updatedAt": "2020-06-17T15:35:38.497Z",
"updatedBy": "elastic"
},
"migrationVersion": {
@ -69,6 +70,7 @@
"tags": [
],
"throttle": null,
"updatedAt": "2020-06-17T15:35:38.497Z",
"updatedBy": "elastic"
},
"migrationVersion": {

View file

@ -172,6 +172,9 @@
},
"updatedBy": {
"type": "keyword"
},
"updatedAt": {
"type": "date"
}
}
},