Skip to content

Commit

Permalink
[RAM][HTTP Versioning] Version Public Rule Update Route (#179587)
Browse files Browse the repository at this point in the history
## Summary

Issue: #179476
Parent issue: #157883

This PR versions the update (`PUT '/api/alerting/rule/{id}'`) route.
Still using `config-schema` for now, even though we will eventually
switch to `zod` when core is ready with openapi doc generation support
in the versioned router.

We are now validating update data using the update data schema when
calling `rulesClient->update`. We are also validating (but not throwing)
the updated rule as well.

### 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: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
JiaweiWu and kibanamachine authored Apr 15, 2024
1 parent 091fd14 commit ed4fade
Show file tree
Hide file tree
Showing 54 changed files with 1,555 additions and 1,008 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,11 @@ export const actionAlertsFilterSchema = schema.object({
});

export const actionSchema = schema.object({
uuid: schema.maybe(schema.string()),
group: schema.maybe(schema.string()),
id: schema.string(),
actionTypeId: schema.maybe(schema.string()),
params: schema.recordOf(schema.string(), schema.maybe(schema.any()), { defaultValue: {} }),
frequency: schema.maybe(actionFrequencySchema),
uuid: schema.maybe(schema.string()),
alerts_filter: schema.maybe(actionAlertsFilterSchema),
use_alert_data_for_template: schema.maybe(schema.boolean()),
});
Expand Down
38 changes: 38 additions & 0 deletions x-pack/plugins/alerting/common/routes/rule/apis/update/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
actionFrequencySchema,
actionAlertsFilterSchema,
actionSchema,
updateBodySchema,
updateParamsSchema,
} from './schemas/latest';

export type {
UpdateRuleAction,
UpdateRuleActionFrequency,
UpdateRuleRequestParams,
UpdateRuleRequestBody,
UpdateRuleResponse,
} from './types/latest';

export {
actionFrequencySchema as actionFrequencySchemaV1,
actionAlertsFilterSchema as actionAlertsFilterSchemaV1,
actionSchema as actionSchemaV1,
updateBodySchema as updateBodySchemaV1,
updateParamsSchema as updateParamsSchemaV1,
} from './schemas/v1';

export type {
UpdateRuleAction as UpdateRuleActionV1,
UpdateRuleActionFrequency as UpdateRuleActionFrequencyV1,
UpdateRuleRequestParams as UpdateRuleRequestParamsV1,
UpdateRuleRequestBody as UpdateRuleRequestBodyV1,
UpdateRuleResponse as UpdateRuleResponseV1,
} from './types/v1';
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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 { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation';
import { notifyWhenSchemaV1, alertDelaySchemaV1 } from '../../../response';
import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query';

export const actionFrequencySchema = schema.object({
summary: schema.boolean(),
notify_when: notifyWhenSchemaV1,
throttle: schema.nullable(schema.string({ validate: validateDurationV1 })),
});

export const actionAlertsFilterSchema = schema.object({
query: schema.maybe(alertsFilterQuerySchemaV1),
timeframe: schema.maybe(
schema.object({
days: schema.arrayOf(
schema.oneOf([
schema.literal(1),
schema.literal(2),
schema.literal(3),
schema.literal(4),
schema.literal(5),
schema.literal(6),
schema.literal(7),
])
),
hours: schema.object({
start: schema.string({
validate: validateHoursV1,
}),
end: schema.string({
validate: validateHoursV1,
}),
}),
timezone: schema.string({ validate: validateTimezoneV1 }),
})
),
});

export const actionSchema = schema.object({
group: schema.maybe(schema.string()),
id: schema.string(),
params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
frequency: schema.maybe(actionFrequencySchema),
uuid: schema.maybe(schema.string()),
alerts_filter: schema.maybe(actionAlertsFilterSchema),
use_alert_data_for_template: schema.maybe(schema.boolean()),
});

export const updateBodySchema = schema.object({
name: schema.string(),
tags: schema.arrayOf(schema.string(), { defaultValue: [] }),
schedule: schema.object({
interval: schema.string({ validate: validateDurationV1 }),
}),
throttle: schema.maybe(schema.nullable(schema.string({ validate: validateDurationV1 }))),
params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
actions: schema.arrayOf(actionSchema, { defaultValue: [] }),
notify_when: schema.maybe(schema.nullable(notifyWhenSchemaV1)),
alert_delay: schema.maybe(alertDelaySchemaV1),
});

export const updateParamsSchema = schema.object({
id: schema.string(),
});
Original file line number Diff line number Diff line change
@@ -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';
37 changes: 37 additions & 0 deletions x-pack/plugins/alerting/common/routes/rule/apis/update/types/v1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 {
actionSchemaV1,
actionFrequencySchemaV1,
updateParamsSchemaV1,
updateBodySchemaV1,
} from '..';

export type UpdateRuleAction = TypeOf<typeof actionSchemaV1>;
export type UpdateRuleActionFrequency = TypeOf<typeof actionFrequencySchemaV1>;

export type UpdateRuleRequestParams = TypeOf<typeof updateParamsSchemaV1>;
type UpdateBodySchema = TypeOf<typeof updateBodySchemaV1>;

export interface UpdateRuleRequestBody<Params extends RuleParamsV1 = never> {
name: UpdateBodySchema['name'];
tags: UpdateBodySchema['tags'];
schedule: UpdateBodySchema['schedule'];
throttle?: UpdateBodySchema['throttle'];
params: Params;
actions: UpdateBodySchema['actions'];
notify_when?: UpdateBodySchema['notify_when'];
alert_delay?: UpdateBodySchema['alert_delay'];
}

export interface UpdateRuleResponse<Params extends RuleParamsV1 = never> {
body: RuleResponseV1<Params>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -1327,7 +1327,7 @@ describe('bulkEdit()', () => {
Object {
"errors": Array [
Object {
"message": "Error validating bulk edit rules operations - [0.group]: expected value of type [string] but got [undefined]",
"message": "Error validating bulk edit rules operations - [group]: expected value of type [string] but got [undefined]",
"rule": Object {
"id": "1",
"name": "my rule name",
Expand Down Expand Up @@ -1378,7 +1378,7 @@ describe('bulkEdit()', () => {
errors: [
{
message:
'Error validating bulk edit rules operations - [0.group]: definition for this key is missing',
'Error validating bulk edit rules operations - [group]: definition for this key is missing',
rule: {
id: '1',
name: 'my rule name',
Expand Down Expand Up @@ -1432,7 +1432,7 @@ describe('bulkEdit()', () => {
errors: [
{
message:
'Error validating bulk edit rules operations - [0.frequency]: definition for this key is missing',
'Error validating bulk edit rules operations - [frequency]: definition for this key is missing',
rule: {
id: '1',
name: 'my rule name',
Expand Down Expand Up @@ -1484,7 +1484,7 @@ describe('bulkEdit()', () => {
errors: [
{
message:
'Error validating bulk edit rules operations - [0.alertsFilter]: definition for this key is missing',
'Error validating bulk edit rules operations - [alertsFilter]: definition for this key is missing',
rule: {
id: '1',
name: 'my rule name',
Expand Down Expand Up @@ -1568,7 +1568,7 @@ describe('bulkEdit()', () => {
Object {
"errors": Array [
Object {
"message": "Error validating bulk edit rules operations - [0.group]: expected value of type [string] but got [undefined]",
"message": "Error validating bulk edit rules operations - [group]: expected value of type [string] but got [undefined]",
"rule": Object {
"id": "1",
"name": "my rule name",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import {
} from './types';
import { RawRuleAction, RawRule, SanitizedRule } from '../../../../types';
import { ruleNotifyWhen } from '../../constants';
import { ruleDomainSchema } from '../../schemas';
import { actionRequestSchema, ruleDomainSchema, systemActionRequestSchema } from '../../schemas';
import { RuleParams, RuleDomain, RuleSnoozeSchedule } from '../../types';
import { findRulesSo, bulkCreateRulesSo } from '../../../../data/rule';
import { RuleAttributes, RuleActionAttributes } from '../../../../data/rule/types';
Expand All @@ -81,10 +81,6 @@ import {
transformRuleDomainToRule,
} from '../../transforms';
import { validateScheduleLimit, ValidateScheduleLimitResult } from '../get_schedule_frequency';
import {
bulkEditDefaultActionsSchema,
bulkEditSystemActionsSchema,
} from './schemas/bulk_edit_rules_option_schemas';

const isValidInterval = (interval: string | undefined): interval is string => {
return interval !== undefined;
Expand Down Expand Up @@ -656,27 +652,28 @@ async function getUpdatedAttributesFromOperations<Params extends RuleParams>({
const systemActions = operation.value.filter((action): action is RuleSystemAction =>
actionsClient.isSystemAction(action.id)
);
if (systemActions.length > 0) {
const actions = operation.value.filter(
(action): action is RuleAction => !actionsClient.isSystemAction(action.id)
);

systemActions.forEach((systemAction) => {
try {
bulkEditSystemActionsSchema.validate(systemActions);
systemActionRequestSchema.validate(systemAction);
} catch (error) {
throw Boom.badRequest(`Error validating bulk edit rules operations - ${error.message}`);
}
}
});

const defaultActions = operation.value.filter(
(action): action is RuleAction => !actionsClient.isSystemAction(action.id)
);
if (defaultActions.length > 0) {
actions.forEach((action) => {
try {
bulkEditDefaultActionsSchema.validate(defaultActions);
actionRequestSchema.validate(action);
} catch (error) {
throw Boom.badRequest(`Error validating bulk edit rules operations - ${error.message}`);
}
}
});

const { actions: genActions, systemActions: genSystemActions } =
await addGeneratedActionValues(defaultActions, systemActions, context);
await addGeneratedActionValues(actions, systemActions, context);
const updatedOperation = {
...operation,
value: [...genActions, ...genSystemActions],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
import { schema } from '@kbn/config-schema';
import { rRuleRequestSchema } from '../../../../r_rule/schemas';
import { notifyWhenSchema, actionAlertsFilterSchema } from '../../../schemas';
import { notifyWhenSchema, actionRequestSchema, systemActionRequestSchema } from '../../../schemas';
import { validateDuration } from '../../../validation';
import { validateSnoozeSchedule } from '../validation';

Expand All @@ -26,31 +26,6 @@ const bulkEditRuleSnoozeScheduleSchemaWithValidation = schema.object(
{ validate: validateSnoozeSchedule }
);

const bulkEditDefaultActionSchema = schema.object({
group: schema.string(),
id: schema.string(),
params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
uuid: schema.maybe(schema.string()),
frequency: schema.maybe(
schema.object({
summary: schema.boolean(),
throttle: schema.nullable(schema.string()),
notifyWhen: notifyWhenSchema,
})
),
alertsFilter: schema.maybe(actionAlertsFilterSchema),
});

export const bulkEditDefaultActionsSchema = schema.arrayOf(bulkEditDefaultActionSchema);

export const bulkEditSystemActionSchema = schema.object({
id: schema.string(),
params: schema.recordOf(schema.string(), schema.maybe(schema.any()), { defaultValue: {} }),
uuid: schema.maybe(schema.string()),
});

export const bulkEditSystemActionsSchema = schema.arrayOf(bulkEditSystemActionSchema);

const bulkEditTagSchema = schema.object({
operation: schema.oneOf([schema.literal('add'), schema.literal('delete'), schema.literal('set')]),
field: schema.literal('tags'),
Expand All @@ -60,7 +35,7 @@ const bulkEditTagSchema = schema.object({
const bulkEditActionsSchema = schema.object({
operation: schema.oneOf([schema.literal('add'), schema.literal('set')]),
field: schema.literal('actions'),
value: schema.arrayOf(schema.oneOf([bulkEditDefaultActionSchema, bulkEditSystemActionSchema])),
value: schema.arrayOf(schema.oneOf([actionRequestSchema, systemActionRequestSchema])),
});

const bulkEditScheduleSchema = schema.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export async function createRule<Params extends RuleParams = never>(
id: createdRuleSavedObject.id,
logger: context.logger,
ruleType: context.ruleTypeRegistry.get(createdRuleSavedObject.attributes.alertTypeId),
references,
references: createdRuleSavedObject.references,
},
(connectorId: string) => actionsClient.isSystemAction(connectorId)
);
Expand Down
Loading

0 comments on commit ed4fade

Please sign in to comment.