[RAM] Add HTTP versioning to resolve, snooze, and unsnooze APIs (#168886)

## Summary

**REOPENED version of https://github.com/elastic/kibana/pull/163359, git
history got too complicated and triggered too many codeowners**

Part of #157883

Converts `_resolve`, `_snooze`, `_unsnooze` to new HTTP versioned model


### Checklist

- [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

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Zacqary Adam Xeper 2023-10-31 14:54:57 -05:00 committed by GitHub
parent 3cf19b2e4b
commit ce430e56f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
89 changed files with 1059 additions and 561 deletions

View file

@ -38,10 +38,10 @@ export const rRuleResponseSchema = schema.object({
byweekday: schema.maybe(schema.arrayOf(schema.oneOf([schema.string(), schema.number()]))),
bymonth: schema.maybe(schema.arrayOf(schema.number())),
bysetpos: schema.maybe(schema.arrayOf(schema.number())),
bymonthday: schema.arrayOf(schema.number()),
byyearday: schema.arrayOf(schema.number()),
byweekno: schema.arrayOf(schema.number()),
byhour: schema.arrayOf(schema.number()),
byminute: schema.arrayOf(schema.number()),
bysecond: schema.arrayOf(schema.number()),
bymonthday: schema.maybe(schema.arrayOf(schema.number())),
byyearday: schema.maybe(schema.arrayOf(schema.number())),
byweekno: schema.maybe(schema.arrayOf(schema.number())),
byhour: schema.maybe(schema.arrayOf(schema.number())),
byminute: schema.maybe(schema.arrayOf(schema.number())),
bysecond: schema.maybe(schema.arrayOf(schema.number())),
});

View file

@ -5,24 +5,14 @@
* 2.0.
*/
export {
ruleSnoozeScheduleSchema,
bulkEditOperationsSchema,
bulkEditRulesRequestBodySchema,
} from './schemas/latest';
export type {
RuleSnoozeSchedule,
BulkEditRulesRequestBody,
BulkEditRulesResponse,
} from './types/latest';
export { bulkEditOperationsSchema, bulkEditRulesRequestBodySchema } from './schemas/latest';
export type { BulkEditRulesRequestBody, BulkEditRulesResponse } from './types/latest';
export {
ruleSnoozeScheduleSchema as ruleSnoozeScheduleSchemaV1,
bulkEditOperationsSchema as bulkEditOperationsSchemaV1,
bulkEditRulesRequestBodySchema as bulkEditRulesRequestBodySchemaV1,
} from './schemas/v1';
export type {
RuleSnoozeSchedule as RuleSnoozeScheduleV1,
BulkEditRulesRequestBody as BulkEditRulesRequestBodyV1,
BulkEditRulesResponse as BulkEditRulesResponseV1,
} from './types/v1';

View file

@ -6,19 +6,10 @@
*/
import { schema } from '@kbn/config-schema';
import { validateDurationV1, validateNotifyWhenV1 } from '../../../validation';
import { validateSnoozeScheduleV1 } from '../validation';
import { validateDurationV1 } from '../../../validation';
import { rRuleRequestSchemaV1 } from '../../../../r_rule';
import { ruleNotifyWhenV1 } from '../../../response';
const notifyWhenSchema = schema.oneOf(
[
schema.literal(ruleNotifyWhenV1.CHANGE),
schema.literal(ruleNotifyWhenV1.ACTIVE),
schema.literal(ruleNotifyWhenV1.THROTTLE),
],
{ validate: validateNotifyWhenV1 }
);
import { notifyWhenSchemaV1, scheduleIdsSchemaV1 } from '../../../response';
import { ruleSnoozeScheduleSchemaV1 } from '../../../request';
export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string()));
@ -28,15 +19,6 @@ export const ruleSnoozeScheduleSchema = schema.object({
rRule: rRuleRequestSchemaV1,
});
const ruleSnoozeScheduleSchemaWithValidation = schema.object(
{
id: schema.maybe(schema.string()),
duration: schema.number(),
rRule: rRuleRequestSchemaV1,
},
{ validate: validateSnoozeScheduleV1 }
);
const ruleActionSchema = schema.object({
group: schema.string(),
id: schema.string(),
@ -46,7 +28,7 @@ const ruleActionSchema = schema.object({
schema.object({
summary: schema.boolean(),
throttle: schema.nullable(schema.string()),
notifyWhen: notifyWhenSchema,
notifyWhen: notifyWhenSchemaV1,
})
),
});
@ -80,17 +62,17 @@ export const bulkEditOperationsSchema = schema.arrayOf(
schema.object({
operation: schema.literal('set'),
field: schema.literal('notifyWhen'),
value: notifyWhenSchema,
value: notifyWhenSchemaV1,
}),
schema.object({
operation: schema.oneOf([schema.literal('set')]),
field: schema.literal('snoozeSchedule'),
value: ruleSnoozeScheduleSchemaWithValidation,
value: ruleSnoozeScheduleSchemaV1,
}),
schema.object({
operation: schema.oneOf([schema.literal('delete')]),
field: schema.literal('snoozeSchedule'),
value: schema.maybe(scheduleIdsSchema),
value: schema.maybe(scheduleIdsSchemaV1),
}),
schema.object({
operation: schema.literal('set'),

View file

@ -6,9 +6,8 @@
*/
import type { TypeOf } from '@kbn/config-schema';
import { RuleParamsV1, RuleResponseV1 } from '../../../response';
import { ruleSnoozeScheduleSchemaV1, bulkEditRulesRequestBodySchemaV1 } from '..';
import { bulkEditRulesRequestBodySchemaV1 } from '..';
export type RuleSnoozeSchedule = TypeOf<typeof ruleSnoozeScheduleSchemaV1>;
export type BulkEditRulesRequestBody = TypeOf<typeof bulkEditRulesRequestBodySchemaV1>;
interface BulkEditActionSkippedResult {

View file

@ -6,7 +6,6 @@
*/
export {
notifyWhenSchema,
actionFrequencySchema,
actionAlertsFilterSchema,
actionSchema,
@ -23,7 +22,6 @@ export type {
} from './types/latest';
export {
notifyWhenSchema as notifyWhenSchemaV1,
actionFrequencySchema as actionFrequencySchemaV1,
actionAlertsFilterSchema as actionAlertsFilterSchemaV1,
actionSchema as actionSchemaV1,

View file

@ -6,26 +6,12 @@
*/
import { schema } from '@kbn/config-schema';
import { ruleNotifyWhenV1 } from '../../../response';
import {
validateNotifyWhenV1,
validateDurationV1,
validateHoursV1,
validateTimezoneV1,
} from '../../../validation';
export const notifyWhenSchema = schema.oneOf(
[
schema.literal(ruleNotifyWhenV1.CHANGE),
schema.literal(ruleNotifyWhenV1.ACTIVE),
schema.literal(ruleNotifyWhenV1.THROTTLE),
],
{ validate: validateNotifyWhenV1 }
);
import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation';
import { notifyWhenSchemaV1 } from '../../../response';
export const actionFrequencySchema = schema.object({
summary: schema.boolean(),
notify_when: notifyWhenSchema,
notify_when: notifyWhenSchemaV1,
throttle: schema.nullable(schema.string({ validate: validateDurationV1 })),
});
@ -91,7 +77,7 @@ export const createBodySchema = schema.object({
interval: schema.string({ validate: validateDurationV1 }),
}),
actions: schema.arrayOf(actionSchema, { defaultValue: [] }),
notify_when: schema.maybe(schema.nullable(notifyWhenSchema)),
notify_when: schema.maybe(schema.nullable(notifyWhenSchemaV1)),
});
export const createParamsSchema = schema.object({

View file

@ -0,0 +1,14 @@
/*
* 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 { resolveParamsSchema } from './schemas/latest';
export { resolveParamsSchema as resolveParamsSchemaV1 } from './schemas/v1';
export type { ResolveRuleResponse } from './types/latest';
export type { ResolveRuleResponse as ResolveRuleResponseV1 } from './types/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 resolveParamsSchema = schema.object({
id: schema.string(),
});

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 { RuleParamsV1, RuleResponseV1 } from '../../../response';
export interface ResolveRuleResponse<Params extends RuleParamsV1 = never> {
body: RuleResponseV1<Params>;
}

View file

@ -0,0 +1,13 @@
/*
* 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 { snoozeParamsSchema, snoozeBodySchema } from './schemas/latest';
export {
snoozeParamsSchema as snoozeParamsSchemaV1,
snoozeBodySchema as snoozeBodySchemaV1,
} from './schemas/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,17 @@
/*
* 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';
import { ruleSnoozeScheduleSchemaV1 } from '../../../request';
export const snoozeParamsSchema = schema.object({
id: schema.string(),
});
export const snoozeBodySchema = schema.object({
snooze_schedule: ruleSnoozeScheduleSchemaV1,
});

View file

@ -0,0 +1,13 @@
/*
* 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 { unsnoozeParamsSchema, unsnoozeBodySchema } from './schemas/latest';
export {
unsnoozeParamsSchema as unsnoozeParamsSchemaV1,
unsnoozeBodySchema as unsnoozeBodySchemaV1,
} from './schemas/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,18 @@
/*
* 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 unsnoozeParamsSchema = schema.object({
id: schema.string(),
});
const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string()));
export const unsnoozeBodySchema = schema.object({
schedule_ids: scheduleIdsSchema,
});

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,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.
*/
export {
ruleNotifyWhen,
ruleLastRunOutcomeValues,
ruleExecutionStatusValues,
ruleExecutionStatusErrorReason,
ruleExecutionStatusWarningReason,
} from './constants/latest';
export type {
RuleNotifyWhen,
RuleLastRunOutcomeValues,
RuleExecutionStatusValues,
RuleExecutionStatusErrorReason,
RuleExecutionStatusWarningReason,
} from './constants/latest';
export {
ruleNotifyWhen as ruleNotifyWhenV1,
ruleLastRunOutcomeValues as ruleLastRunOutcomeValuesV1,
ruleExecutionStatusValues as ruleExecutionStatusValuesV1,
ruleExecutionStatusErrorReason as ruleExecutionStatusErrorReasonV1,
ruleExecutionStatusWarningReason as ruleExecutionStatusWarningReasonV1,
} from './constants/v1';
export type {
RuleNotifyWhen as RuleNotifyWhenV1,
RuleLastRunOutcomeValues as RuleLastRunOutcomeValuesV1,
RuleExecutionStatusValues as RuleExecutionStatusValuesV1,
RuleExecutionStatusErrorReason as RuleExecutionStatusErrorReasonV1,
RuleExecutionStatusWarningReason as RuleExecutionStatusWarningReasonV1,
} from './constants/v1';

View file

@ -5,6 +5,6 @@
* 2.0.
*/
export { validateSnoozeSchedule } from './validate_snooze_schedule/latest';
export { ruleSnoozeScheduleSchema } from './schemas/latest';
export { validateSnoozeSchedule as validateSnoozeScheduleV1 } from './validate_snooze_schedule/v1';
export { ruleSnoozeScheduleSchema as ruleSnoozeScheduleSchemaV1 } from './schemas/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,19 @@
/*
* 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';
import { rRuleRequestSchemaV1 } from '../../../r_rule';
import { validateSnoozeScheduleV1 } from '../../validation';
export const ruleSnoozeScheduleSchema = schema.object(
{
id: schema.maybe(schema.string()),
duration: schema.number(),
rRule: rRuleRequestSchemaV1,
},
{ validate: validateSnoozeScheduleV1 }
);

View file

@ -14,6 +14,8 @@ export {
monitoringSchema,
ruleResponseSchema,
ruleSnoozeScheduleSchema,
notifyWhenSchema,
scheduleIdsSchema,
} from './schemas/latest';
export type {
@ -24,22 +26,6 @@ export type {
Monitoring,
} from './types/latest';
export {
ruleNotifyWhen,
ruleLastRunOutcomeValues,
ruleExecutionStatusValues,
ruleExecutionStatusErrorReason,
ruleExecutionStatusWarningReason,
} from './constants/latest';
export type {
RuleNotifyWhen,
RuleLastRunOutcomeValues,
RuleExecutionStatusValues,
RuleExecutionStatusErrorReason,
RuleExecutionStatusWarningReason,
} from './constants/latest';
export {
ruleParamsSchema as ruleParamsSchemaV1,
actionParamsSchema as actionParamsSchemaV1,
@ -49,24 +35,10 @@ export {
monitoringSchema as monitoringSchemaV1,
ruleResponseSchema as ruleResponseSchemaV1,
ruleSnoozeScheduleSchema as ruleSnoozeScheduleSchemaV1,
notifyWhenSchema as notifyWhenSchemaV1,
scheduleIdsSchema as scheduleIdsSchemaV1,
} from './schemas/v1';
export {
ruleNotifyWhen as ruleNotifyWhenV1,
ruleLastRunOutcomeValues as ruleLastRunOutcomeValuesV1,
ruleExecutionStatusValues as ruleExecutionStatusValuesV1,
ruleExecutionStatusErrorReason as ruleExecutionStatusErrorReasonV1,
ruleExecutionStatusWarningReason as ruleExecutionStatusWarningReasonV1,
} from './constants/v1';
export type {
RuleNotifyWhen as RuleNotifyWhenV1,
RuleLastRunOutcomeValues as RuleLastRunOutcomeValuesV1,
RuleExecutionStatusValues as RuleExecutionStatusValuesV1,
RuleExecutionStatusErrorReason as RuleExecutionStatusErrorReasonV1,
RuleExecutionStatusWarningReason as RuleExecutionStatusWarningReasonV1,
} from './constants/v1';
export type {
RuleParams as RuleParamsV1,
RuleResponse as RuleResponseV1,

View file

@ -13,17 +13,21 @@ import {
ruleExecutionStatusErrorReason as ruleExecutionStatusErrorReasonV1,
ruleExecutionStatusWarningReason as ruleExecutionStatusWarningReasonV1,
ruleLastRunOutcomeValues as ruleLastRunOutcomeValuesV1,
} from '../constants/v1';
} from '../../common/constants/v1';
import { validateNotifyWhenV1 } from '../../validation';
export const ruleParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any()));
export const actionParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any()));
export const mappedParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any()));
const notifyWhenSchema = schema.oneOf([
schema.literal(ruleNotifyWhenV1.CHANGE),
schema.literal(ruleNotifyWhenV1.ACTIVE),
schema.literal(ruleNotifyWhenV1.THROTTLE),
]);
export const notifyWhenSchema = schema.oneOf(
[
schema.literal(ruleNotifyWhenV1.CHANGE),
schema.literal(ruleNotifyWhenV1.ACTIVE),
schema.literal(ruleNotifyWhenV1.THROTTLE),
],
{ validate: validateNotifyWhenV1 }
);
const intervalScheduleSchema = schema.object({
interval: schema.string(),
@ -182,9 +186,9 @@ export const monitoringSchema = schema.object({
});
export const ruleSnoozeScheduleSchema = schema.object({
id: schema.maybe(schema.string()),
duration: schema.number(),
rRule: rRuleResponseSchemaV1,
id: schema.maybe(schema.string()),
skipRecurrences: schema.maybe(schema.arrayOf(schema.string())),
});
@ -221,3 +225,5 @@ export const ruleResponseSchema = schema.object({
running: schema.maybe(schema.nullable(schema.boolean())),
view_in_app_relative_url: schema.maybe(schema.nullable(schema.string())),
});
export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string()));

View file

@ -7,10 +7,11 @@
export { validateDuration } from './validate_duration/latest';
export { validateHours } from './validate_hours/latest';
export { validateNotifyWhen } from './validate_notify_when/latest';
export { validateTimezone } from './validate_timezone/latest';
export { validateSnoozeSchedule } from './validate_snooze_schedule/latest';
export { validateDuration as validateDurationV1 } from './validate_duration/v1';
export { validateHours as validateHoursV1 } from './validate_hours/v1';
export { validateNotifyWhen as validateNotifyWhenV1 } from './validate_notify_when/v1';
export { validateTimezone as validateTimezoneV1 } from './validate_timezone/v1';
export { validateSnoozeSchedule as validateSnoozeScheduleV1 } from './validate_snooze_schedule/v1';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { ruleNotifyWhenV1, RuleNotifyWhenV1 } from '../../response';
import { ruleNotifyWhenV1, RuleNotifyWhenV1 } from '../../common';
export function validateNotifyWhen(notifyWhen: string) {
if (Object.values(ruleNotifyWhenV1).includes(notifyWhen as RuleNotifyWhenV1)) {

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

@ -5,11 +5,15 @@
* 2.0.
*/
import { TypeOf } from '@kbn/config-schema';
import { Frequency } from '@kbn/rrule';
import moment from 'moment';
import { RuleSnoozeScheduleV1 } from '../..';
import { rRuleRequestSchema } from '../../../r_rule';
export const validateSnoozeSchedule = (schedule: RuleSnoozeScheduleV1) => {
export const validateSnoozeSchedule = (schedule: {
rRule: TypeOf<typeof rRuleRequestSchema>;
duration: number;
}) => {
const intervalIsDaily = schedule.rRule.freq === Frequency.DAILY;
const durationInDays = moment.duration(schedule.duration, 'milliseconds').asDays();
if (intervalIsDaily && schedule.rRule.interval && durationInDays >= schedule.rRule.interval) {

View file

@ -191,7 +191,10 @@ export type SanitizedRule<Params extends RuleTypeParams = never> = Omit<
> & { actions: SanitizedRuleAction[] };
export type ResolvedSanitizedRule<Params extends RuleTypeParams = never> = SanitizedRule<Params> &
Omit<SavedObjectsResolveResponse, 'saved_object'>;
Omit<SavedObjectsResolveResponse, 'saved_object'> & {
outcome: string;
alias_target_id?: string;
};
export type SanitizedRuleConfig = Pick<
SanitizedRule,

View file

@ -37,10 +37,10 @@ export const rRuleSchema = schema.object({
byweekday: schema.maybe(schema.arrayOf(schema.oneOf([schema.string(), schema.number()]))),
bymonth: schema.maybe(schema.arrayOf(schema.number())),
bysetpos: schema.maybe(schema.arrayOf(schema.number())),
bymonthday: schema.arrayOf(schema.number()),
byyearday: schema.arrayOf(schema.number()),
byweekno: schema.arrayOf(schema.number()),
byhour: schema.arrayOf(schema.number()),
byminute: schema.arrayOf(schema.number()),
bysecond: schema.arrayOf(schema.number()),
bymonthday: schema.maybe(schema.arrayOf(schema.number())),
byyearday: schema.maybe(schema.arrayOf(schema.number())),
byweekno: schema.maybe(schema.arrayOf(schema.number())),
byhour: schema.maybe(schema.arrayOf(schema.number())),
byminute: schema.maybe(schema.arrayOf(schema.number())),
bysecond: schema.maybe(schema.arrayOf(schema.number())),
});

View file

@ -45,7 +45,7 @@ export type BulkEditOptionsFilter<Params extends RuleParams> = BulkEditOptionsCo
};
export type BulkEditOptionsIds<Params extends RuleParams> = BulkEditOptionsCommon<Params> & {
ids: string[];
ids?: string[];
};
export type BulkEditSkipReason = 'RULE_NOT_MODIFIED';

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 { ResolveParams } from './resolve_rule';
export { resolveRule } from './resolve_rule';

View file

@ -0,0 +1,106 @@
/*
* 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 { withSpan } from '@kbn/apm-utils';
import { AlertConsumers } from '@kbn/rule-data-utils';
import { resolveRuleSavedObject } from '../../../../rules_client/lib';
import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events';
import { RuleTypeParams } from '../../../../types';
import { ReadOperations, AlertingAuthorizationEntity } from '../../../../authorization';
import { RulesClientContext } from '../../../../rules_client/types';
import { formatLegacyActions } from '../../../../rules_client/lib';
import { transformRuleAttributesToRuleDomain, transformRuleDomainToRule } from '../../transforms';
import { Rule } from '../../types';
import { ruleSchema } from '../../schemas';
import { resolveRuleParamsSchema } from './schemas';
import type { ResolvedSanitizedRule } from '../../../../types';
export interface ResolveParams {
id: string;
includeSnoozeData?: boolean;
}
export async function resolveRule<Params extends RuleTypeParams = never>(
context: RulesClientContext,
{ id, includeSnoozeData = false }: ResolveParams
): // TODO (http-versioning): This should be of type Rule, change this when all rule types are fixed
Promise<ResolvedSanitizedRule<Params>> {
try {
resolveRuleParamsSchema.validate({ id });
} catch (error) {
throw Boom.badRequest(`Error validating resolve params - ${error.message}`);
}
const { saved_object: result, ...resolveResponse } = await withSpan(
{ name: 'resolveRuleSavedObject', type: 'rules' },
() =>
resolveRuleSavedObject(context, {
ruleId: 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.RESOLVE,
savedObject: { type: 'alert', id },
error,
})
);
throw error;
}
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.RESOLVE,
savedObject: { type: 'alert', id },
})
);
const ruleDomain = transformRuleAttributesToRuleDomain(result.attributes, {
id: result.id,
logger: context.logger,
ruleType: context.ruleTypeRegistry.get(result.attributes.alertTypeId),
references: result.references,
includeSnoozeData,
});
const rule = transformRuleDomainToRule(ruleDomain);
try {
ruleSchema.validate(rule);
} catch (error) {
context.logger.warn(`Error validating resolve data - ${error.message}`);
}
// format legacy actions for SIEM rules
if (result.attributes.consumer === AlertConsumers.SIEM) {
// @ts-expect-error formatLegacyActions uses common Rule type instead of server; wontfix as this function is deprecated
const [migratedRule] = await formatLegacyActions([rule], {
savedObjectsClient: context.unsecuredSavedObjectsClient,
logger: context.logger,
});
return {
...(migratedRule as Rule<never>),
...resolveResponse,
// TODO (http-versioning): Remove this cast, this enables us to move forward
// without fixing all of other solution types
} as ResolvedSanitizedRule;
}
return {
...rule,
...resolveResponse,
// TODO (http-versioning): Remove this cast, this enables us to move forward
// without fixing all of other solution types
} as ResolvedSanitizedRule;
}

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 { resolveRuleParamsSchema } from './resolve_rule_params_schema';

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 resolveRuleParamsSchema = 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 './resolved_rule';

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.
*/
import type { Rule } from '../../../types';
export type ResolvedRule<Params> = Rule<Params> & { outcome: string; alias_target_id?: string };

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 { SnoozeRuleOptions } from './types';
export { snoozeRule } from './snooze_rule';

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 { snoozeRuleParamsSchema } from './snooze_rule_params_schema';
export { snoozeRuleBodySchema } from './snooze_rule_body_schema';

View file

@ -0,0 +1,13 @@
/*
* 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';
import { ruleSnoozeScheduleSchema as ruleSnoozeScheduleRequestSchema } from '../../../../../../common/routes/rule/request';
export const snoozeRuleBodySchema = schema.object({
snoozeSchedule: ruleSnoozeScheduleRequestSchema,
});

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 snoozeRuleParamsSchema = schema.object({
id: schema.string(),
});

View file

@ -6,26 +6,32 @@
*/
import Boom from '@hapi/boom';
import { RawRule, RuleSnoozeSchedule } from '../../types';
import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization';
import { retryIfConflicts } from '../../lib/retry_if_conflicts';
import { partiallyUpdateAlert } from '../../saved_objects';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { validateSnoozeStartDate } from '../../lib/validate_snooze_date';
import { RuleMutedError } from '../../lib/errors/rule_muted';
import { RulesClientContext } from '../types';
import { getSnoozeAttributes, verifySnoozeAttributeScheduleLimit } from '../common';
import { updateMeta } from '../lib';
import { withSpan } from '@kbn/apm-utils';
import { getRuleSavedObject } from '../../../../rules_client/lib';
import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events';
import { WriteOperations, AlertingAuthorizationEntity } from '../../../../authorization';
import { retryIfConflicts } from '../../../../lib/retry_if_conflicts';
import { validateSnoozeStartDate } from '../../../../lib/validate_snooze_date';
import { RuleMutedError } from '../../../../lib/errors/rule_muted';
import { RulesClientContext } from '../../../../rules_client/types';
import {
getSnoozeAttributes,
verifySnoozeAttributeScheduleLimit,
} from '../../../../rules_client/common';
import { updateRuleSo } from '../../../../data/rule';
import { updateMetaAttributes } from '../../../../rules_client/lib/update_meta_attributes';
import { snoozeRuleParamsSchema } from './schemas';
import type { SnoozeRuleOptions } from './types';
export interface SnoozeParams {
id: string;
snoozeSchedule: RuleSnoozeSchedule;
}
export async function snooze(
export async function snoozeRule(
context: RulesClientContext,
{ id, snoozeSchedule }: SnoozeParams
{ id, snoozeSchedule }: SnoozeRuleOptions
): Promise<void> {
try {
snoozeRuleParamsSchema.validate({ id });
} catch (error) {
throw Boom.badRequest(`Error validating snooze params - ${error.message}`);
}
const snoozeDateValidationMsg = validateSnoozeStartDate(snoozeSchedule.rRule.dtstart);
if (snoozeDateValidationMsg) {
throw new RuleMutedError(snoozeDateValidationMsg);
@ -40,17 +46,14 @@ export async function snooze(
async function snoozeWithOCC(
context: RulesClientContext,
{
id,
snoozeSchedule,
}: {
id: string;
snoozeSchedule: RuleSnoozeSchedule;
}
{ id, snoozeSchedule }: SnoozeRuleOptions
) {
const { attributes, version } = await context.unsecuredSavedObjectsClient.get<RawRule>(
'alert',
id
const { attributes, version } = await withSpan(
{ name: 'getRuleSavedObject', type: 'rules' },
() =>
getRuleSavedObject(context, {
ruleId: id,
})
);
try {
@ -93,17 +96,14 @@ async function snoozeWithOCC(
throw Boom.badRequest(error.message);
}
const updateAttributes = updateMeta(context, {
...newAttrs,
updatedBy: await context.getUserName(),
updatedAt: new Date().toISOString(),
});
const updateOptions = { version };
await partiallyUpdateAlert(
context.unsecuredSavedObjectsClient,
await updateRuleSo({
savedObjectsClient: context.unsecuredSavedObjectsClient,
savedObjectsUpdateOptions: { version },
id,
updateAttributes,
updateOptions
);
updateRuleAttributes: updateMetaAttributes(context, {
...newAttrs,
updatedBy: await context.getUserName(),
updatedAt: new Date().toISOString(),
}),
});
}

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 './snooze_rule_options';

View file

@ -0,0 +1,13 @@
/*
* 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 { ruleSnoozeScheduleSchema as ruleSnoozeScheduleRequestSchema } from '../../../../../../common/routes/rule/request';
export interface SnoozeRuleOptions {
id: string;
snoozeSchedule: TypeOf<typeof ruleSnoozeScheduleRequestSchema>;
}

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 { UnsnoozeParams } from './unsnooze_rule';
export { unsnoozeRule } from './unsnooze_rule';

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 { unsnoozeRuleParamsSchema } from './unsnooze_rule_params_schema';

View file

@ -0,0 +1,13 @@
/*
* 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 unsnoozeRuleParamsSchema = schema.object({
id: schema.string(),
scheduleIds: schema.maybe(schema.arrayOf(schema.string())),
});

View file

@ -5,21 +5,24 @@
* 2.0.
*/
import { RawRule } from '../../types';
import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization';
import { retryIfConflicts } from '../../lib/retry_if_conflicts';
import { partiallyUpdateAlert } from '../../saved_objects';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { RulesClientContext } from '../types';
import { updateMeta } from '../lib';
import { getUnsnoozeAttributes } from '../common';
import Boom from '@hapi/boom';
import { withSpan } from '@kbn/apm-utils';
import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events';
import { getRuleSavedObject } from '../../../../rules_client/lib';
import { WriteOperations, AlertingAuthorizationEntity } from '../../../../authorization';
import { retryIfConflicts } from '../../../../lib/retry_if_conflicts';
import { RulesClientContext } from '../../../../rules_client/types';
import { getUnsnoozeAttributes } from '../../../../rules_client/common';
import { updateRuleSo } from '../../../../data/rule';
import { updateMetaAttributes } from '../../../../rules_client/lib/update_meta_attributes';
import { unsnoozeRuleParamsSchema } from './schemas';
export interface UnsnoozeParams {
id: string;
scheduleIds?: string[];
}
export async function unsnooze(
export async function unsnoozeRule(
context: RulesClientContext,
{ id, scheduleIds }: UnsnoozeParams
): Promise<void> {
@ -31,9 +34,17 @@ export async function unsnooze(
}
async function unsnoozeWithOCC(context: RulesClientContext, { id, scheduleIds }: UnsnoozeParams) {
const { attributes, version } = await context.unsecuredSavedObjectsClient.get<RawRule>(
'alert',
id
try {
unsnoozeRuleParamsSchema.validate({ id, scheduleIds });
} catch (error) {
throw Boom.badRequest(`Error validating unsnooze params - ${error.message}`);
}
const { attributes, version } = await withSpan(
{ name: 'getRuleSavedObject', type: 'rules' },
() =>
getRuleSavedObject(context, {
ruleId: id,
})
);
try {
@ -69,17 +80,14 @@ async function unsnoozeWithOCC(context: RulesClientContext, { id, scheduleIds }:
context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId);
const newAttrs = getUnsnoozeAttributes(attributes, scheduleIds);
const updateAttributes = updateMeta(context, {
...newAttrs,
updatedBy: await context.getUserName(),
updatedAt: new Date().toISOString(),
});
const updateOptions = { version };
await partiallyUpdateAlert(
context.unsecuredSavedObjectsClient,
await updateRuleSo({
savedObjectsClient: context.unsecuredSavedObjectsClient,
savedObjectsUpdateOptions: { version },
id,
updateAttributes,
updateOptions
);
updateRuleAttributes: updateMetaAttributes(context, {
...newAttrs,
updatedBy: await context.getUserName(),
updatedAt: new Date().toISOString(),
}),
});
}

View file

@ -20,10 +20,10 @@ export interface RRuleAttributes {
byweekday?: Array<string | number>;
bymonth?: number[];
bysetpos?: number[];
bymonthday: number[];
byyearday: number[];
byweekno: number[];
byhour: number[];
byminute: number[];
bysecond: number[];
bymonthday?: number[];
byyearday?: number[];
byweekno?: number[];
byhour?: number[];
byminute?: number[];
bysecond?: number[];
}

View file

@ -17,5 +17,9 @@ export { bulkCreateRulesSo } from './methods/bulk_create_rule_so';
export type { BulkCreateRulesSoParams } from './methods/bulk_create_rule_so';
export { bulkDeleteRulesSo } from './methods/bulk_delete_rules_so';
export type { BulkDeleteRulesSoParams } from './methods/bulk_delete_rules_so';
export { resolveRuleSo } from './methods/resolve_rule_so';
export type { ResolveRuleSoParams } from './methods/resolve_rule_so';
export { getRuleSo } from './methods/get_rule_so';
export type { GetRuleSoParams } from './methods/get_rule_so';
export { bulkDisableRulesSo } from './methods/bulk_disable_rules_so';
export type { BulkDisableRulesSoParams } from './methods/bulk_disable_rules_so';

View file

@ -0,0 +1,22 @@
/*
* 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 { SavedObjectsClientContract, SavedObject } from '@kbn/core/server';
import { SavedObjectsGetOptions } from '@kbn/core-saved-objects-api-server';
import { RuleAttributes } from '../types';
export interface GetRuleSoParams {
savedObjectsClient: SavedObjectsClientContract;
id: string;
savedObjectsGetOptions?: SavedObjectsGetOptions;
}
export const getRuleSo = (params: GetRuleSoParams): Promise<SavedObject<RuleAttributes>> => {
const { savedObjectsClient, id, savedObjectsGetOptions } = params;
return savedObjectsClient.get<RuleAttributes>('alert', id, savedObjectsGetOptions);
};

View file

@ -0,0 +1,24 @@
/*
* 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 { SavedObjectsClientContract, SavedObjectsResolveResponse } from '@kbn/core/server';
import { SavedObjectsResolveOptions } from '@kbn/core-saved-objects-api-server';
import { RuleAttributes } from '../types';
export interface ResolveRuleSoParams {
savedObjectsClient: SavedObjectsClientContract;
id: string;
savedObjectsResolveOptions?: SavedObjectsResolveOptions;
}
export const resolveRuleSo = (
params: ResolveRuleSoParams
): Promise<SavedObjectsResolveResponse<RuleAttributes>> => {
const { savedObjectsClient, id, savedObjectsResolveOptions } = params;
return savedObjectsClient.resolve('alert', id, savedObjectsResolveOptions);
};

View file

@ -29,7 +29,7 @@ import { getActionErrorLogRoute } from './get_action_error_log';
import { getRuleExecutionKPIRoute } from './get_rule_execution_kpi';
import { getRuleStateRoute } from './get_rule_state';
import { healthRoute } from './health';
import { resolveRuleRoute } from './resolve_rule';
import { resolveRuleRoute } from './rule/apis/resolve';
import { ruleTypesRoute } from './rule_types';
import { muteAllRuleRoute } from './mute_all_rule';
import { muteAlertRoute } from './rule/apis/mute_alert/mute_alert';
@ -37,8 +37,8 @@ import { unmuteAllRuleRoute } from './unmute_all_rule';
import { unmuteAlertRoute } from './unmute_alert';
import { updateRuleApiKeyRoute } from './update_rule_api_key';
import { bulkEditInternalRulesRoute } from './rule/apis/bulk_edit/bulk_edit_rules_route';
import { snoozeRuleRoute } from './snooze_rule';
import { unsnoozeRuleRoute } from './unsnooze_rule';
import { snoozeRuleRoute } from './rule/apis/snooze';
import { unsnoozeRuleRoute } from './rule/apis/unsnooze';
import { runSoonRoute } from './run_soon';
import { bulkDeleteRulesRoute } from './rule/apis/bulk_delete/bulk_delete_rules_route';
import { bulkEnableRulesRoute } from './bulk_enable_rules';

View file

@ -1,91 +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,
RewriteResponseCase,
rewriteRuleLastRun,
rewriteActionsRes,
} from './lib';
import {
RuleTypeParams,
AlertingRequestHandlerContext,
INTERNAL_BASE_ALERTING_API_PATH,
ResolvedSanitizedRule,
} from '../types';
const paramSchema = schema.object({
id: schema.string(),
});
const rewriteBodyRes: RewriteResponseCase<ResolvedSanitizedRule<RuleTypeParams>> = ({
alertTypeId,
createdBy,
updatedBy,
createdAt,
updatedAt,
apiKeyOwner,
apiKeyCreatedByUser,
notifyWhen,
muteAll,
mutedInstanceIds,
executionStatus,
actions,
scheduledTaskId,
lastRun,
nextRun,
...rest
}) => ({
...rest,
rule_type_id: alertTypeId,
created_by: createdBy,
updated_by: updatedBy,
created_at: createdAt,
updated_at: updatedAt,
api_key_owner: apiKeyOwner,
notify_when: notifyWhen,
mute_all: muteAll,
muted_alert_ids: mutedInstanceIds,
scheduled_task_id: scheduledTaskId,
execution_status: executionStatus && {
...omit(executionStatus, 'lastExecutionDate', 'lastDuration'),
last_execution_date: executionStatus.lastExecutionDate,
last_duration: executionStatus.lastDuration,
},
actions: rewriteActionsRes(actions),
...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}),
...(nextRun ? { next_run: nextRun } : {}),
...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}),
});
export const resolveRuleRoute = (
router: IRouter<AlertingRequestHandlerContext>,
licenseState: ILicenseState
) => {
router.get(
{
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_resolve`,
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.resolve({ id, includeSnoozeData: true });
return res.ok({
body: rewriteBodyRes(rule),
});
})
)
);
};

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 { resolveRuleRoute } from './resolve_rule_route';

View file

@ -6,17 +6,17 @@
*/
import { pick } from 'lodash';
import { resolveRuleRoute } from './resolve_rule';
import { resolveRuleRoute } from './resolve_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 { ResolvedSanitizedRule } from '../types';
import { AsApiContract } from './lib';
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 { ResolvedRule } from '../../../../application/rule/methods/resolve/types';
import { ResolvedSanitizedRule } from '../../../../../common';
const rulesClient = rulesClientMock.create();
jest.mock('../lib/license_api_access', () => ({
jest.mock('../../../../lib/license_api_access', () => ({
verifyApiAccess: jest.fn(),
}));
@ -25,7 +25,7 @@ beforeEach(() => {
});
describe('resolveRuleRoute', () => {
const mockedRule: ResolvedSanitizedRule<{
const mockedRule: ResolvedRule<{
bar: boolean;
}> = {
id: '1',
@ -67,7 +67,7 @@ describe('resolveRuleRoute', () => {
revision: 0,
};
const resolveResult: AsApiContract<ResolvedSanitizedRule<{ bar: boolean }>> = {
const resolveResult = {
...pick(
mockedRule,
'consumer',
@ -86,13 +86,13 @@ describe('resolveRuleRoute', () => {
updated_by: mockedRule.updatedBy,
api_key_owner: mockedRule.apiKeyOwner,
muted_alert_ids: mockedRule.mutedInstanceIds,
created_at: mockedRule.createdAt,
updated_at: mockedRule.updatedAt,
created_at: mockedRule.createdAt.toISOString(),
updated_at: mockedRule.updatedAt.toISOString(),
id: mockedRule.id,
revision: mockedRule.revision,
execution_status: {
status: mockedRule.executionStatus.status,
last_execution_date: mockedRule.executionStatus.lastExecutionDate,
last_execution_date: mockedRule.executionStatus.lastExecutionDate.toISOString(),
},
actions: [
{
@ -115,7 +115,9 @@ describe('resolveRuleRoute', () => {
expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/_resolve"`);
rulesClient.resolve.mockResolvedValueOnce(mockedRule);
// TODO (http-versioning): Remove this cast, this enables us to move forward
// without fixing all of other solution types
rulesClient.resolve.mockResolvedValueOnce(mockedRule as ResolvedSanitizedRule);
const [context, req, res] = mockHandlerArguments(
{ rulesClient },
@ -142,7 +144,9 @@ describe('resolveRuleRoute', () => {
const [, handler] = router.get.mock.calls[0];
rulesClient.resolve.mockResolvedValueOnce(mockedRule);
// TODO (http-versioning): Remove this cast, this enables us to move forward
// without fixing all of other solution types
rulesClient.resolve.mockResolvedValueOnce(mockedRule as ResolvedSanitizedRule);
const [context, req, res] = mockHandlerArguments(
{ rulesClient },
@ -169,7 +173,9 @@ describe('resolveRuleRoute', () => {
const [, handler] = router.get.mock.calls[0];
rulesClient.resolve.mockResolvedValueOnce(mockedRule);
// TODO (http-versioning): Remove this cast, this enables us to move forward
// without fixing all of other solution types
rulesClient.resolve.mockResolvedValueOnce(mockedRule as ResolvedSanitizedRule);
const [context, req, res] = mockHandlerArguments(
{ rulesClient },

View file

@ -0,0 +1,52 @@
/*
* 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 { IRouter } from '@kbn/core/server';
import { RuleParamsV1 } from '../../../../../common/routes/rule/response';
import { ResolvedRule } from '../../../../application/rule/methods/resolve/types';
import {
resolveParamsSchemaV1,
ResolveRuleResponseV1,
} from '../../../../../common/routes/rule/apis/resolve';
import { ILicenseState } from '../../../../lib';
import { verifyAccessAndContext } from '../../../lib';
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types';
import { transformResolveResponseV1 } from './transforms';
export type ResolveRuleRequestParamsV1 = TypeOf<typeof resolveParamsSchemaV1>;
export const resolveRuleRoute = (
router: IRouter<AlertingRequestHandlerContext>,
licenseState: ILicenseState
) => {
router.get(
{
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_resolve`,
validate: {
params: resolveParamsSchemaV1,
},
},
router.handleLegacyErrors(
verifyAccessAndContext(licenseState, async function (context, req, res) {
const rulesClient = (await context.alerting).getRulesClient();
const params: ResolveRuleRequestParamsV1 = req.params;
const { id } = params;
// TODO (http-versioning): Remove this cast, this enables us to move forward
// without fixing all of other solution types
const rule = (await rulesClient.resolve({
id,
includeSnoozeData: true,
})) as ResolvedRule<RuleParamsV1>;
const response: ResolveRuleResponseV1<RuleParamsV1> = {
body: transformResolveResponseV1<RuleParamsV1>(rule),
};
return res.ok(response);
})
)
);
};

View file

@ -0,0 +1,10 @@
/*
* 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 { transformResolveResponse } from './transform_resolve_response/latest';
export { transformResolveResponse as transformResolveResponseV1 } from './transform_resolve_response/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,18 @@
/*
* 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 { ResolvedRule } from '../../../../../../application/rule/methods/resolve/types';
import { RuleParams } from '../../../../../../application/rule/types';
import { transformRuleToRuleResponseV1 } from '../../../../transforms';
export const transformResolveResponse = <Params extends RuleParams = never>(
rule: ResolvedRule<Params>
) => ({
...transformRuleToRuleResponseV1<Params>(rule),
outcome: rule.outcome,
alias_target_id: rule.alias_target_id,
});

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 { snoozeRuleRoute } from './snooze_rule_route';

View file

@ -5,15 +5,15 @@
* 2.0.
*/
import { snoozeRuleRoute } from './snooze_rule';
import { snoozeRuleRoute } from './snooze_rule_route';
import { httpServiceMock } from '@kbn/core/server/mocks';
import { licenseStateMock } from '../lib/license_state.mock';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { rulesClientMock } from '../rules_client.mock';
import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled';
import { licenseStateMock } from '../../../../lib/license_state.mock';
import { mockHandlerArguments } from '../../../_mock_handler_arguments';
import { rulesClientMock } from '../../../../rules_client.mock';
import { RuleTypeDisabledError } from '../../../../lib/errors/rule_type_disabled';
const rulesClient = rulesClientMock.create();
jest.mock('../lib/license_api_access', () => ({
jest.mock('../../../../lib/license_api_access', () => ({
verifyApiAccess: jest.fn(),
}));

View file

@ -5,36 +5,18 @@
* 2.0.
*/
import { TypeOf } from '@kbn/config-schema';
import { IRouter } from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { ILicenseState, RuleMutedError } from '../lib';
import { verifyAccessAndContext, rRuleSchema } from './lib';
import { SnoozeOptions } from '../rules_client';
import { AlertingRequestHandlerContext, INTERNAL_ALERTING_SNOOZE_RULE } from '../types';
import { validateSnoozeSchedule } from '../lib/validate_snooze_schedule';
import {
snoozeBodySchema,
snoozeParamsSchema,
} from '../../../../../common/routes/rule/apis/snooze';
import { ILicenseState, RuleMutedError } from '../../../../lib';
import { verifyAccessAndContext } from '../../../lib';
import { AlertingRequestHandlerContext, INTERNAL_ALERTING_SNOOZE_RULE } from '../../../../types';
import { transformSnoozeBodyV1 } from './transforms';
const paramSchema = schema.object({
id: schema.string(),
});
export const snoozeScheduleSchema = schema.object(
{
id: schema.maybe(schema.string()),
duration: schema.number(),
rRule: rRuleSchema,
},
{ validate: validateSnoozeSchedule }
);
const bodySchema = schema.object({
snooze_schedule: snoozeScheduleSchema,
});
const rewriteBodyReq: (opts: {
snooze_schedule: SnoozeOptions['snoozeSchedule'];
}) => SnoozeOptions = ({ snooze_schedule: snoozeSchedule }) => ({
snoozeSchedule,
});
export type SnoozeRuleRequestParamsV1 = TypeOf<typeof snoozeParamsSchema>;
export const snoozeRuleRoute = (
router: IRouter<AlertingRequestHandlerContext>,
@ -44,15 +26,15 @@ export const snoozeRuleRoute = (
{
path: INTERNAL_ALERTING_SNOOZE_RULE,
validate: {
params: paramSchema,
body: bodySchema,
params: snoozeParamsSchema,
body: snoozeBodySchema,
},
},
router.handleLegacyErrors(
verifyAccessAndContext(licenseState, async function (context, req, res) {
const rulesClient = (await context.alerting).getRulesClient();
const params = req.params;
const body = rewriteBodyReq(req.body);
const params: SnoozeRuleRequestParamsV1 = req.params;
const body = transformSnoozeBodyV1(req.body);
try {
await rulesClient.snooze({ ...params, ...body });
return res.noContent();

View file

@ -0,0 +1,10 @@
/*
* 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 { transformSnoozeBody } from './transform_snooze_body/latest';
export { transformSnoozeBody as transformSnoozeBodyV1 } from './transform_snooze_body/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,17 @@
/*
* 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 { snoozeBodySchemaV1 } from '../../../../../../../common/routes/rule/apis/snooze';
type SnoozeBodySchema = TypeOf<typeof snoozeBodySchemaV1>;
export const transformSnoozeBody: (opts: SnoozeBodySchema) => {
snoozeSchedule: SnoozeBodySchema['snooze_schedule'];
} = ({ snooze_schedule: snoozeSchedule }) => ({
snoozeSchedule,
});

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 { unsnoozeRuleRoute } from './unsnooze_rule_route';

View file

@ -0,0 +1,10 @@
/*
* 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 { transformUnsnoozeBody } from './transform_unsnooze_body/latest';
export { transformUnsnoozeBody as transformUnsnoozeBodyV1 } from './transform_unsnooze_body/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,10 @@
/*
* 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 const transformUnsnoozeBody: (opts: { schedule_ids?: string[] }) => {
scheduleIds?: string[];
} = ({ schedule_ids: scheduleIds }) => (scheduleIds ? { scheduleIds } : {});

View file

@ -5,15 +5,15 @@
* 2.0.
*/
import { unsnoozeRuleRoute } from './unsnooze_rule';
import { unsnoozeRuleRoute } from './unsnooze_rule_route';
import { httpServiceMock } from '@kbn/core/server/mocks';
import { licenseStateMock } from '../lib/license_state.mock';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { rulesClientMock } from '../rules_client.mock';
import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled';
import { licenseStateMock } from '../../../../lib/license_state.mock';
import { mockHandlerArguments } from '../../../_mock_handler_arguments';
import { rulesClientMock } from '../../../../rules_client.mock';
import { RuleTypeDisabledError } from '../../../../lib/errors/rule_type_disabled';
const rulesClient = rulesClientMock.create();
jest.mock('../lib/license_api_access', () => ({
jest.mock('../../../../lib/license_api_access', () => ({
verifyApiAccess: jest.fn(),
}));

View file

@ -5,25 +5,18 @@
* 2.0.
*/
import { TypeOf } from '@kbn/config-schema';
import { IRouter } from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { ILicenseState, RuleMutedError } from '../lib';
import { verifyAccessAndContext, RewriteRequestCase } from './lib';
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types';
import {
unsnoozeBodySchema,
unsnoozeParamsSchema,
} from '../../../../../common/routes/rule/apis/unsnooze';
import { ILicenseState, RuleMutedError } from '../../../../lib';
import { verifyAccessAndContext } from '../../../lib';
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types';
import { transformUnsnoozeBodyV1 } from './transforms';
const paramSchema = schema.object({
id: schema.string(),
});
export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string()));
const bodySchema = schema.object({
schedule_ids: scheduleIdsSchema,
});
const rewriteBodyReq: RewriteRequestCase<{ scheduleIds?: string[] }> = ({
schedule_ids: scheduleIds,
}) => (scheduleIds ? { scheduleIds } : {});
export type UnsnoozeRuleRequestParamsV1 = TypeOf<typeof unsnoozeParamsSchema>;
export const unsnoozeRuleRoute = (
router: IRouter<AlertingRequestHandlerContext>,
@ -33,15 +26,15 @@ export const unsnoozeRuleRoute = (
{
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_unsnooze`,
validate: {
params: paramSchema,
body: bodySchema,
params: unsnoozeParamsSchema,
body: unsnoozeBodySchema,
},
},
router.handleLegacyErrors(
verifyAccessAndContext(licenseState, async function (context, req, res) {
const rulesClient = (await context.alerting).getRulesClient();
const params = req.params;
const body = rewriteBodyReq(req.body);
const params: UnsnoozeRuleRequestParamsV1 = req.params;
const body = transformUnsnoozeBodyV1(req.body);
try {
await rulesClient.unsnooze({ ...params, ...body });
return res.noContent();

View file

@ -82,6 +82,7 @@ export const transformRuleToRuleResponse = <Params extends RuleParams = never>(
mute_all: rule.muteAll,
...(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 } : {}),

View file

@ -6,18 +6,19 @@
*/
import { i18n } from '@kbn/i18n';
import { RawRule, RuleSnoozeSchedule } from '../../types';
import {
Rule,
RuleDomain,
RuleParams,
RuleSnoozeSchedule as RuleDomainSnoozeSchedule,
} from '../../application/rule/types';
import { RuleAttributes } from '../../data/rule/types';
import { getActiveScheduledSnoozes } from '../../lib/is_rule_snoozed';
/**
* @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types
*/
export function getSnoozeAttributes(attributes: RawRule, snoozeSchedule: RuleSnoozeSchedule) {
export function getSnoozeAttributes(
attributes: RuleAttributes,
snoozeSchedule: RuleDomainSnoozeSchedule
) {
// If duration is -1, instead mute all
const { id: snoozeId, duration } = snoozeSchedule;
@ -69,10 +70,7 @@ export function getBulkSnooze<Params extends RuleParams>(
};
}
/**
* @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types
*/
export function getUnsnoozeAttributes(attributes: RawRule, scheduleIds?: string[]) {
export function getUnsnoozeAttributes(attributes: RuleAttributes, scheduleIds?: string[]) {
const snoozeSchedule = scheduleIds
? clearScheduledSnoozesAttributesById(attributes, scheduleIds)
: clearCurrentActiveSnoozeAttributes(attributes);
@ -106,10 +104,7 @@ export function getBulkUnsnooze<Params extends RuleParams>(
};
}
/**
* @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types
*/
export function clearUnscheduledSnoozeAttributes(attributes: RawRule) {
export function clearUnscheduledSnoozeAttributes(attributes: RuleAttributes) {
// Clear any snoozes that have no ID property. These are "simple" snoozes created with the quick UI, e.g. snooze for 3 days starting now
return attributes.snoozeSchedule
? attributes.snoozeSchedule.filter((s) => typeof s.id !== 'undefined')
@ -120,10 +115,7 @@ export function clearUnscheduledSnooze<Params extends RuleParams>(rule: RuleDoma
return rule.snoozeSchedule ? rule.snoozeSchedule.filter((s) => typeof s.id !== 'undefined') : [];
}
/**
* @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types
*/
export function clearScheduledSnoozesAttributesById(attributes: RawRule, ids: string[]) {
export function clearScheduledSnoozesAttributesById(attributes: RuleAttributes, ids: string[]) {
return attributes.snoozeSchedule
? attributes.snoozeSchedule.filter((s) => s.id && !ids.includes(s.id))
: [];
@ -136,10 +128,7 @@ export function clearScheduledSnoozesById<Params extends RuleParams>(
return rule.snoozeSchedule ? rule.snoozeSchedule.filter((s) => s.id && !ids.includes(s.id)) : [];
}
/**
* @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types
*/
export function clearCurrentActiveSnoozeAttributes(attributes: RawRule) {
export function clearCurrentActiveSnoozeAttributes(attributes: RuleAttributes) {
// First attempt to cancel a simple (unscheduled) snooze
const clearedUnscheduledSnoozes = clearUnscheduledSnoozeAttributes(attributes);
// Now clear any scheduled snoozes that are currently active and never recur
@ -193,10 +182,7 @@ export function clearCurrentActiveSnooze<Params extends RuleParams>(rule: RuleDo
return clearedSnoozesAndSkippedRecurringSnoozes;
}
/**
* @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types
*/
export function verifySnoozeAttributeScheduleLimit(attributes: Partial<RawRule>) {
export function verifySnoozeAttributeScheduleLimit(attributes: Partial<Rule>) {
const schedules = attributes.snoozeSchedule?.filter((snooze) => snooze.id);
if (schedules && schedules.length > 5) {
throw Error(

View file

@ -37,6 +37,9 @@ export interface GetAlertFromRawParams {
omitGeneratedValues?: boolean;
}
/**
* @deprecated in favor of transformRuleAttributesToRuleDomain
*/
export function getAlertFromRaw<Params extends RuleTypeParams>(
context: RulesClientContext,
id: string,

View file

@ -0,0 +1,39 @@
/*
* 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 { withSpan } from '@kbn/apm-utils';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { RulesClientContext } from '../types';
import { getRuleSo } from '../../data/rule';
import { RuleAttributes } from '../../data/rule/types';
interface GetRuleSavedObjectParams {
ruleId: string;
}
export async function getRuleSavedObject(
context: RulesClientContext,
params: GetRuleSavedObjectParams
): Promise<SavedObject<RuleAttributes>> {
const { ruleId } = params;
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.GET,
outcome: 'unknown',
savedObject: { type: 'alert', id: ruleId },
})
);
return await withSpan({ name: 'unsecuredSavedObjectsClient.get', type: 'rules' }, () =>
getRuleSo({
id: ruleId,
savedObjectsClient: context.unsecuredSavedObjectsClient,
})
);
}

View file

@ -6,9 +6,13 @@
*/
export { createRuleSavedObject } from './create_rule_saved_object';
export { resolveRuleSavedObject } from './resolve_rule_saved_object';
export { getRuleSavedObject } from './get_rule_saved_object';
export { extractReferences } from './extract_references';
export { validateActions } from './validate_actions';
export { updateMeta } from './update_meta';
export { updateMetaAttributes } from './update_meta_attributes';
export * from './get_alert_from_raw';
export { getAuthorizationFilter } from './get_authorization_filter';
export { checkAuthorizationAndGetTotal } from './check_authorization_and_get_total';

View file

@ -0,0 +1,39 @@
/*
* 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 { SavedObjectsResolveResponse } from '@kbn/core/server';
import { withSpan } from '@kbn/apm-utils';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { RulesClientContext } from '../types';
import { resolveRuleSo } from '../../data/rule';
import { RuleAttributes } from '../../data/rule/types';
interface ResolveRuleSavedObjectParams {
ruleId: string;
}
export async function resolveRuleSavedObject(
context: RulesClientContext,
params: ResolveRuleSavedObjectParams
): Promise<SavedObjectsResolveResponse<RuleAttributes>> {
const { ruleId } = params;
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.RESOLVE,
outcome: 'unknown',
savedObject: { type: 'alert', id: ruleId },
})
);
return await withSpan({ name: 'unsecuredSavedObjectsClient.resolve', type: 'rules' }, () =>
resolveRuleSo({
id: ruleId,
savedObjectsClient: context.unsecuredSavedObjectsClient,
})
);
}

View file

@ -8,6 +8,9 @@
import { RawRule } from '../../types';
import { RulesClientContext } from '../types';
/**
* @deprecated Use updateMetaAttributes instead
*/
export function updateMeta<T extends Partial<RawRule>>(
context: RulesClientContext,
alertAttributes: T

View file

@ -0,0 +1,25 @@
/*
* 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 { RuleAttributes } from '../../data/rule/types';
import { RulesClientContext } from '../types';
export function updateMetaAttributes<T extends Partial<RuleAttributes>>(
context: RulesClientContext,
alertAttributes: T
): T {
if (alertAttributes.hasOwnProperty('apiKey') || alertAttributes.hasOwnProperty('apiKeyOwner')) {
return {
...alertAttributes,
meta: {
...(alertAttributes.meta ?? {}),
versionApiKeyLastmodified: context.kibanaVersion,
},
};
}
return alertAttributes;
}

View file

@ -11,8 +11,9 @@ import { retryIfConflicts } from '../../lib/retry_if_conflicts';
import { partiallyUpdateAlert } from '../../saved_objects';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { RulesClientContext } from '../types';
import { updateMeta } from '../lib';
import { updateMetaAttributes } from '../lib';
import { clearUnscheduledSnoozeAttributes } from '../common';
import { RuleAttributes } from '../../data/rule/types';
export async function muteAll(context: RulesClientContext, { id }: { id: string }): Promise<void> {
return await retryIfConflicts(
@ -60,10 +61,10 @@ async function muteAllWithOCC(context: RulesClientContext, { id }: { id: string
context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId);
const updateAttributes = updateMeta(context, {
const updateAttributes = updateMetaAttributes(context, {
muteAll: true,
mutedInstanceIds: [],
snoozeSchedule: clearUnscheduledSnoozeAttributes(attributes),
snoozeSchedule: clearUnscheduledSnoozeAttributes(attributes as RuleAttributes),
updatedBy: await context.getUserName(),
updatedAt: new Date().toISOString(),
});

View file

@ -1,81 +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, RuleTypeParams, ResolvedSanitizedRule } 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';
export interface ResolveParams {
id: string;
includeLegacyId?: boolean;
includeSnoozeData?: boolean;
}
export async function resolve<Params extends RuleTypeParams = never>(
context: RulesClientContext,
{ id, includeLegacyId, includeSnoozeData = false }: ResolveParams
): Promise<ResolvedSanitizedRule<Params>> {
const { saved_object: result, ...resolveResponse } =
await context.unsecuredSavedObjectsClient.resolve<RawRule>('alert', 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.RESOLVE,
savedObject: { type: 'alert', id },
error,
})
);
throw error;
}
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.RESOLVE,
savedObject: { type: 'alert', id },
})
);
const rule = getAlertFromRaw<Params>(
context,
result.id,
result.attributes.alertTypeId,
result.attributes,
result.references,
includeLegacyId,
false,
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,
...resolveResponse,
};
}
return {
...rule,
...resolveResponse,
};
}

View file

@ -11,8 +11,9 @@ import { retryIfConflicts } from '../../lib/retry_if_conflicts';
import { partiallyUpdateAlert } from '../../saved_objects';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { RulesClientContext } from '../types';
import { updateMeta } from '../lib';
import { updateMetaAttributes } from '../lib';
import { clearUnscheduledSnoozeAttributes } from '../common';
import { RuleAttributes } from '../../data/rule/types';
export async function unmuteAll(
context: RulesClientContext,
@ -63,10 +64,10 @@ async function unmuteAllWithOCC(context: RulesClientContext, { id }: { id: strin
context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId);
const updateAttributes = updateMeta(context, {
const updateAttributes = updateMetaAttributes(context, {
muteAll: false,
mutedInstanceIds: [],
snoozeSchedule: clearUnscheduledSnoozeAttributes(attributes),
snoozeSchedule: clearUnscheduledSnoozeAttributes(attributes as RuleAttributes),
updatedBy: await context.getUserName(),
updatedAt: new Date().toISOString(),
});

View file

@ -12,8 +12,10 @@ import { parseDuration } from '../../common/parse_duration';
import { RulesClientContext, BulkOptions } from './types';
import { clone, CloneArguments } from './methods/clone';
import { createRule, CreateRuleParams } from '../application/rule/methods/create';
import { snoozeRule, SnoozeRuleOptions } from '../application/rule/methods/snooze';
import { unsnoozeRule, UnsnoozeParams } from '../application/rule/methods/unsnooze';
import { get, GetParams } from './methods/get';
import { resolve, ResolveParams } from './methods/resolve';
import { resolveRule, ResolveParams } from '../application/rule/methods/resolve';
import { getAlertState, GetAlertStateParams } from './methods/get_alert_state';
import { getAlertSummary, GetAlertSummaryParams } from './methods/get_alert_summary';
import {
@ -54,8 +56,6 @@ import { bulkEnableRules } from './methods/bulk_enable';
import { updateApiKey } from './methods/update_api_key';
import { enable } from './methods/enable';
import { disable } from './methods/disable';
import { snooze, SnoozeParams } from './methods/snooze';
import { unsnooze, UnsnoozeParams } from './methods/unsnooze';
import { clearExpiredSnoozes } from './methods/clear_expired_snoozes';
import { muteInstance } from '../application/rule/methods/mute_alert/mute_instance';
import { muteAll } from './methods/mute_all';
@ -130,7 +130,7 @@ export class RulesClient {
public get = <Params extends RuleTypeParams = never>(params: GetParams) =>
get<Params>(this.context, params);
public resolve = <Params extends RuleTypeParams = never>(params: ResolveParams) =>
resolve<Params>(this.context, params);
resolveRule<Params>(this.context, params);
public update = <Params extends RuleTypeParams = never>(params: UpdateOptions<Params>) =>
update<Params>(this.context, params);
@ -162,8 +162,8 @@ export class RulesClient {
public enable = (options: { id: string }) => enable(this.context, options);
public disable = (options: { id: string }) => disable(this.context, options);
public snooze = (options: SnoozeParams) => snooze(this.context, options);
public unsnooze = (options: UnsnoozeParams) => unsnooze(this.context, options);
public snooze = (options: SnoozeRuleOptions) => snoozeRule(this.context, options);
public unsnooze = (options: UnsnoozeParams) => unsnoozeRule(this.context, options);
public clearExpiredSnoozes = (options: {
rule: Pick<SanitizedRule<RuleTypeParams>, 'id' | 'snoozeSchedule'>;

View file

@ -96,6 +96,11 @@ describe('resolve()', () => {
},
],
notifyWhen: 'onActiveAlert',
executionStatus: {
status: 'ok',
last_execution_date: new Date().toISOString(),
last_duration: 10,
},
},
references: [
{
@ -123,6 +128,10 @@ describe('resolve()', () => {
"alertTypeId": "123",
"alias_target_id": "2",
"createdAt": 2019-02-12T21:01:22.479Z,
"executionStatus": Object {
"lastExecutionDate": 2019-02-12T21:01:22.479Z,
"status": "ok",
},
"id": "1",
"notifyWhen": "onActiveAlert",
"outcome": "aliasMatch",
@ -138,86 +147,12 @@ describe('resolve()', () => {
`);
expect(unsecuredSavedObjectsClient.resolve).toHaveBeenCalledTimes(1);
expect(unsecuredSavedObjectsClient.resolve.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alert",
"1",
]
`);
});
test('calls saved objects client with id and includeLegacyId params', async () => {
const rulesClient = new RulesClient(rulesClientParams);
unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({
saved_object: {
id: '1',
type: 'alert',
attributes: {
legacyId: 'some-legacy-id',
alertTypeId: '123',
schedule: { interval: '10s' },
params: {
bar: true,
},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
actions: [
{
group: 'default',
actionRef: 'action_0',
params: {
foo: true,
},
},
],
notifyWhen: 'onActiveAlert',
},
references: [
{
name: 'action_0',
type: 'action',
id: '1',
},
],
},
outcome: 'aliasMatch',
alias_target_id: '2',
});
const result = await rulesClient.resolve({ id: '1', includeLegacyId: true });
expect(result).toMatchInlineSnapshot(`
Object {
"actions": Array [
Object {
"group": "default",
"id": "1",
"params": Object {
"foo": true,
},
},
],
"alertTypeId": "123",
"alias_target_id": "2",
"createdAt": 2019-02-12T21:01:22.479Z,
"id": "1",
"legacyId": "some-legacy-id",
"notifyWhen": "onActiveAlert",
"outcome": "aliasMatch",
"params": Object {
"bar": true,
},
"schedule": Object {
"interval": "10s",
},
"snoozeSchedule": Array [],
"updatedAt": 2019-02-12T21:01:22.479Z,
}
Array [
"alert",
"1",
undefined,
]
`);
expect(unsecuredSavedObjectsClient.resolve).toHaveBeenCalledTimes(1);
expect(unsecuredSavedObjectsClient.resolve.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alert",
"1",
]
`);
});
test('calls saved objects client with id and includeSnoozeData params', async () => {
@ -256,6 +191,11 @@ describe('resolve()', () => {
},
],
notifyWhen: 'onActiveAlert',
executionStatus: {
status: 'ok',
last_execution_date: new Date().toISOString(),
last_duration: 10,
},
},
references: [
{
@ -323,6 +263,11 @@ describe('resolve()', () => {
},
],
notifyWhen: 'onActiveAlert',
executionStatus: {
status: 'ok',
last_execution_date: new Date().toISOString(),
last_duration: 10,
},
},
references: [
{
@ -363,6 +308,10 @@ describe('resolve()', () => {
"alertTypeId": "123",
"alias_target_id": "2",
"createdAt": 2019-02-12T21:01:22.479Z,
"executionStatus": Object {
"lastExecutionDate": 2019-02-12T21:01:22.479Z,
"status": "ok",
},
"id": "1",
"notifyWhen": "onActiveAlert",
"outcome": "aliasMatch",
@ -400,6 +349,11 @@ describe('resolve()', () => {
},
},
],
executionStatus: {
status: 'ok',
last_execution_date: new Date().toISOString(),
last_duration: 10,
},
},
references: [],
},
@ -505,6 +459,11 @@ describe('resolve()', () => {
},
},
],
executionStatus: {
status: 'ok',
last_execution_date: new Date().toISOString(),
last_duration: 10,
},
},
references: [
{
@ -563,6 +522,11 @@ describe('resolve()', () => {
bar: true,
},
actions: [],
executionStatus: {
status: 'ok',
last_execution_date: new Date().toISOString(),
last_duration: 10,
},
},
references: [],
},
@ -633,6 +597,11 @@ describe('resolve()', () => {
},
],
notifyWhen: 'onActiveAlert',
executionStatus: {
status: 'ok',
last_execution_date: new Date().toISOString(),
last_duration: 10,
},
},
references: [
{

View file

@ -442,6 +442,9 @@ export interface RawRuleExecutionStatus extends SavedObjectAttributes {
};
}
/**
* @deprecated in favor of Rule
*/
export interface RawRule extends SavedObjectAttributes {
enabled: boolean;
name: string;

View file

@ -59,7 +59,8 @@
"@kbn/serverless",
"@kbn/core-http-router-server-mocks",
"@kbn/core-elasticsearch-server",
"@kbn/core-application-common"
"@kbn/core-application-common",
"@kbn/core-saved-objects-api-server"
],
"exclude": ["target/**/*"]
}

View file

@ -199,7 +199,6 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
}, [formattedSnoozeText, isLoading, isDisabled, openPopover]);
const scheduledSnoozeButton = useMemo(() => {
// TODO: Implement scheduled snooze button
return (
<EuiButton
size="s"