diff --git a/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars index 6bb6fccf7d3b3..5395edbcf5f25 100644 --- a/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars @@ -6,7 +6,7 @@ */ import { z } from "zod"; -import { requiredOptional, isValidDateMath } from "@kbn/zod-helpers" +import { requiredOptional, isValidDateMath, ArrayFromString, BooleanFromString } from "@kbn/zod-helpers" {{> disclaimer}} diff --git a/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars index 7fa146cd783e4..ad51f934b7fde 100644 --- a/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars @@ -19,10 +19,7 @@ {{~/if~}} {{~#if (eq type "array")}} - z.preprocess( - (value: unknown) => (typeof value === "string") ? value === '' ? [] : value.split(",") : value, - z.array({{~> zod_schema_item items ~}}) - ) + ArrayFromString({{~> zod_schema_item items ~}}) {{~#if minItems}}.min({{minItems}}){{/if~}} {{~#if maxItems}}.max({{maxItems}}){{/if~}} {{~#if (eq requiredBool false)}}.optional(){{/if~}} @@ -30,12 +27,9 @@ {{~/if~}} {{~#if (eq type "boolean")}} - z.preprocess( - (value: unknown) => (typeof value === "boolean") ? String(value) : value, - z.enum(["true", "false"]) - {{~#if (defined default)}}.default("{{{toJSON default}}}"){{/if~}} - .transform((value) => value === "true") - ) + BooleanFromString + {{~#if (eq requiredBool false)}}.optional(){{/if~}} + {{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}} {{~/if~}} {{~#if (eq type "string")}} diff --git a/packages/kbn-zod-helpers/index.ts b/packages/kbn-zod-helpers/index.ts index f1062064dc5cf..d8a62f58686b2 100644 --- a/packages/kbn-zod-helpers/index.ts +++ b/packages/kbn-zod-helpers/index.ts @@ -6,8 +6,11 @@ * Side Public License, v 1. */ +export * from './src/array_from_string'; +export * from './src/boolean_from_string'; export * from './src/expect_parse_error'; export * from './src/expect_parse_success'; export * from './src/is_valid_date_math'; export * from './src/required_optional'; +export * from './src/safe_parse_result'; export * from './src/stringify_zod_error'; diff --git a/packages/kbn-zod-helpers/src/array_from_string.test.ts b/packages/kbn-zod-helpers/src/array_from_string.test.ts new file mode 100644 index 0000000000000..ba27fddb0c9b5 --- /dev/null +++ b/packages/kbn-zod-helpers/src/array_from_string.test.ts @@ -0,0 +1,34 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ArrayFromString } from './array_from_string'; +import * as z from 'zod'; + +describe('ArrayFromString', () => { + const itemsSchema = z.string(); + + it('should return an array when input is a string', () => { + const result = ArrayFromString(itemsSchema).parse('a,b,c'); + expect(result).toEqual(['a', 'b', 'c']); + }); + + it('should return an empty array when input is an empty string', () => { + const result = ArrayFromString(itemsSchema).parse(''); + expect(result).toEqual([]); + }); + + it('should return the input as is when it is not a string', () => { + const input = ['a', 'b', 'c']; + const result = ArrayFromString(itemsSchema).parse(input); + expect(result).toEqual(input); + }); + + it('should throw an error when input is not a string or an array', () => { + expect(() => ArrayFromString(itemsSchema).parse(123)).toThrow(); + }); +}); diff --git a/packages/kbn-zod-helpers/src/array_from_string.ts b/packages/kbn-zod-helpers/src/array_from_string.ts new file mode 100644 index 0000000000000..24247e2d14c40 --- /dev/null +++ b/packages/kbn-zod-helpers/src/array_from_string.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as z from 'zod'; + +/** + * This is a helper schema to convert comma separated strings to arrays. Useful + * for processing query params. + * + * @param schema Array items schema + * @returns Array schema that accepts a comma-separated string as input + */ +export function ArrayFromString(schema: T) { + return z.preprocess( + (value: unknown) => + typeof value === 'string' ? (value === '' ? [] : value.split(',')) : value, + z.array(schema) + ); +} diff --git a/packages/kbn-zod-helpers/src/boolean_from_string.test.ts b/packages/kbn-zod-helpers/src/boolean_from_string.test.ts new file mode 100644 index 0000000000000..842eda2d6e9a2 --- /dev/null +++ b/packages/kbn-zod-helpers/src/boolean_from_string.test.ts @@ -0,0 +1,32 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BooleanFromString } from './boolean_from_string'; + +describe('BooleanFromString', () => { + it('should return true when input is "true"', () => { + expect(BooleanFromString.parse('true')).toBe(true); + }); + + it('should return false when input is "false"', () => { + expect(BooleanFromString.parse('false')).toBe(false); + }); + + it('should return true when input is true', () => { + expect(BooleanFromString.parse(true)).toBe(true); + }); + + it('should return false when input is false', () => { + expect(BooleanFromString.parse(false)).toBe(false); + }); + + it('should throw an error when input is not a boolean or "true" or "false"', () => { + expect(() => BooleanFromString.parse('not a boolean')).toThrow(); + expect(() => BooleanFromString.parse(42)).toThrow(); + }); +}); diff --git a/packages/kbn-zod-helpers/src/boolean_from_string.ts b/packages/kbn-zod-helpers/src/boolean_from_string.ts new file mode 100644 index 0000000000000..d73e77ea1bddc --- /dev/null +++ b/packages/kbn-zod-helpers/src/boolean_from_string.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import * as z from 'zod'; + +/** + * This is a helper schema to convert a boolean string ("true" or "false") to a + * boolean. Useful for processing query params. + * + * Accepts "true" or "false" as strings, or a boolean. + */ +export const BooleanFromString = z + .enum(['true', 'false']) + .or(z.boolean()) + .transform((value) => { + if (typeof value === 'boolean') { + return value; + } + return value === 'true'; + }); diff --git a/packages/kbn-zod-helpers/src/expect_parse_success.ts b/packages/kbn-zod-helpers/src/expect_parse_success.ts index 4fc4a74047933..8c9e518c27b87 100644 --- a/packages/kbn-zod-helpers/src/expect_parse_success.ts +++ b/packages/kbn-zod-helpers/src/expect_parse_success.ts @@ -7,9 +7,14 @@ */ import type { SafeParseReturnType, SafeParseSuccess } from 'zod'; +import { stringifyZodError } from './stringify_zod_error'; export function expectParseSuccess( result: SafeParseReturnType ): asserts result is SafeParseSuccess { - expect(result.success).toEqual(true); + if (!result.success) { + // We are throwing here instead of using assertions because we want to show + // the stringified error to assist with debugging. + throw new Error(`Expected parse success, got error: ${stringifyZodError(result.error)}`); + } } diff --git a/packages/kbn-zod-helpers/src/safe_parse_result.ts b/packages/kbn-zod-helpers/src/safe_parse_result.ts new file mode 100644 index 0000000000000..4e9b701a18faf --- /dev/null +++ b/packages/kbn-zod-helpers/src/safe_parse_result.ts @@ -0,0 +1,28 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as z from 'zod'; + +/** + * Safely parse a payload against a schema, returning the output or undefined. + * This method does not throw validation errors and is useful for validating + * optional objects when we don't care about errors. + * + * @param payload Schema payload + * @param schema Validation schema + * @returns Schema output or undefined + */ +export function safeParseResult( + payload: unknown, + schema: T +): T['_output'] | undefined { + const result = schema.safeParse(payload); + if (result.success) { + return result.data; + } +} diff --git a/packages/kbn-zod-helpers/src/stringify_zod_error.ts b/packages/kbn-zod-helpers/src/stringify_zod_error.ts index b873870f99381..1fbaec8bbac85 100644 --- a/packages/kbn-zod-helpers/src/stringify_zod_error.ts +++ b/packages/kbn-zod-helpers/src/stringify_zod_error.ts @@ -6,16 +6,41 @@ * Side Public License, v 1. */ -import { ZodError } from 'zod'; +import { ZodError, ZodIssue } from 'zod'; + +const MAX_ERRORS = 5; export function stringifyZodError(err: ZodError) { - return err.issues - .map((issue) => { - // If the path is empty, the error is for the root object - if (issue.path.length === 0) { - return issue.message; - } - return `${issue.path.join('.')}: ${issue.message}`; - }) - .join(', '); + const errorMessages: string[] = []; + + const issues = err.issues; + + // Recursively traverse all issues + while (issues.length > 0) { + const issue = issues.shift()!; + + // If the issue is an invalid union, we need to traverse all issues in the + // "unionErrors" array + if (issue.code === 'invalid_union') { + issues.push(...issue.unionErrors.flatMap((e) => e.issues)); + continue; + } + + errorMessages.push(stringifyIssue(issue)); + } + + const extraErrorCount = errorMessages.length - MAX_ERRORS; + if (extraErrorCount > 0) { + errorMessages.splice(MAX_ERRORS); + errorMessages.push(`and ${extraErrorCount} more`); + } + + return errorMessages.join(', '); +} + +function stringifyIssue(issue: ZodIssue) { + if (issue.path.length === 0) { + return issue.message; + } + return `${issue.path.join('.')}: ${issue.message}`; } diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.mock.ts index 22c507804e1d8..b52f61febbafb 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.mock.ts @@ -4,9 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import type { ErrorSchema } from './error_schema_legacy'; +import type { ErrorSchema } from './error_schema.gen'; export const getErrorSchemaMock = ( id: string = '819eded6-e9c8-445b-a647-519aea39e063' diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/index.ts index 0d243fc201fb9..f5c8440a07148 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/index.ts @@ -8,12 +8,8 @@ export * from './alerts'; export * from './rule_response_actions'; export * from './rule_schema'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -export * from './error_schema_legacy'; -export * from './pagination'; +export * from './error_schema.gen'; +export * from './pagination.gen'; export * from './schemas'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -export * from './sorting_legacy'; +export * from './sorting.gen'; export * from './warning_schema.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.gen.ts new file mode 100644 index 0000000000000..0a7336b8f78c3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.gen.ts @@ -0,0 +1,35 @@ +/* + * 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 { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +/** + * Page number + */ +export type Page = z.infer; +export const Page = z.number().int().min(1); + +/** + * Number of items per page + */ +export type PerPage = z.infer; +export const PerPage = z.number().int().min(0); + +export type PaginationResult = z.infer; +export const PaginationResult = z.object({ + page: Page, + per_page: PerPage, + /** + * Total number of items + */ + total: z.number().int().min(0), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.schema.yaml new file mode 100644 index 0000000000000..3afccce86e329 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.schema.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + title: Pagination Schema + version: 'not applicable' +paths: {} +components: + x-codegen-enabled: true + schemas: + Page: + type: integer + minimum: 1 + description: Page number + PerPage: + type: integer + minimum: 0 + description: Number of items per page + PaginationResult: + type: object + properties: + page: + $ref: '#/components/schemas/Page' + per_page: + $ref: '#/components/schemas/PerPage' + total: + type: integer + minimum: 0 + description: Total number of items + required: + - page + - per_page + - total diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.ts deleted file mode 100644 index bed2cade86df4..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.ts +++ /dev/null @@ -1,28 +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 * as t from 'io-ts'; -import { PositiveInteger, PositiveIntegerGreaterThanZero } from '@kbn/securitysolution-io-ts-types'; - -export type Page = t.TypeOf; -export const Page = PositiveIntegerGreaterThanZero; - -export type PageOrUndefined = t.TypeOf; -export const PageOrUndefined = t.union([Page, t.undefined]); - -export type PerPage = t.TypeOf; -export const PerPage = PositiveInteger; - -export type PerPageOrUndefined = t.TypeOf; -export const PerPageOrUndefined = t.union([PerPage, t.undefined]); - -export type PaginationResult = t.TypeOf; -export const PaginationResult = t.type({ - page: Page, - per_page: PerPage, - total: PositiveInteger, -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/index.ts index ccaf290dc5d33..e9956d88eb45a 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/index.ts @@ -5,7 +5,4 @@ * 2.0. */ -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -export { RESPONSE_ACTION_TYPES, SUPPORTED_RESPONSE_ACTION_TYPES } from './response_actions_legacy'; export * from './response_actions.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts index 0d62dfd9c21f3..79ad21ddfb009 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts @@ -367,26 +367,38 @@ export const RuleActionFrequency = z.object({ throttle: RuleActionThrottle.nullable(), }); +export type RuleActionAlertsFilter = z.infer; +export const RuleActionAlertsFilter = z.object({}).catchall(z.unknown()); + +/** + * Object containing the allowed connector fields, which varies according to the connector type. + */ +export type RuleActionParams = z.infer; +export const RuleActionParams = z.object({}).catchall(z.unknown()); + +/** + * Optionally groups actions by use cases. Use `default` for alert notifications. + */ +export type RuleActionGroup = z.infer; +export const RuleActionGroup = z.string(); + +/** + * The connector ID. + */ +export type RuleActionId = z.infer; +export const RuleActionId = z.string(); + export type RuleAction = z.infer; export const RuleAction = z.object({ /** * The action type used for sending notifications. */ action_type_id: z.string(), - /** - * Optionally groups actions by use cases. Use `default` for alert notifications. - */ - group: z.string(), - /** - * The connector ID. - */ - id: z.string(), - /** - * Object containing the allowed connector fields, which varies according to the connector type. - */ - params: z.object({}).catchall(z.unknown()), + group: RuleActionGroup, + id: RuleActionId, + params: RuleActionParams, uuid: NonEmptyString.optional(), - alerts_filter: z.object({}).catchall(z.unknown()).optional(), + alerts_filter: RuleActionAlertsFilter.optional(), frequency: RuleActionFrequency.optional(), }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml index 921f9350550b6..ad2bfaf76c4c0 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml @@ -397,6 +397,23 @@ components: - notifyWhen - throttle + RuleActionAlertsFilter: + type: object + additionalProperties: true + + RuleActionParams: + type: object + description: Object containing the allowed connector fields, which varies according to the connector type. + additionalProperties: true + + RuleActionGroup: + type: string + description: Optionally groups actions by use cases. Use `default` for alert notifications. + + RuleActionId: + type: string + description: The connector ID. + RuleAction: type: object properties: @@ -404,20 +421,15 @@ components: type: string description: The action type used for sending notifications. group: - type: string - description: Optionally groups actions by use cases. Use `default` for alert notifications. + $ref: '#/components/schemas/RuleActionGroup' id: - type: string - description: The connector ID. + $ref: '#/components/schemas/RuleActionId' params: - type: object - description: Object containing the allowed connector fields, which varies according to the connector type. - additionalProperties: true + $ref: '#/components/schemas/RuleActionParams' uuid: $ref: '#/components/schemas/NonEmptyString' alerts_filter: - type: object - additionalProperties: true + $ref: '#/components/schemas/RuleActionAlertsFilter' frequency: $ref: '#/components/schemas/RuleActionFrequency' required: diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts index abbfa4903ea31..062c913354404 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts @@ -25,7 +25,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", and 52 more"` + ); }); test('strips any unknown values', () => { @@ -46,7 +48,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", and 52 more"` + ); }); test('[rule_id, description] does not validate', () => { @@ -57,7 +61,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 44 more"` + ); }); test('[rule_id, description, from] does not validate', () => { @@ -69,7 +75,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 44 more"` + ); }); test('[rule_id, description, from, to] does not validate', () => { @@ -82,7 +90,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 44 more"` + ); }); test('[rule_id, description, from, to, name] does not validate', () => { @@ -96,7 +106,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", and 36 more"` + ); }); test('[rule_id, description, from, to, name, severity] does not validate', () => { @@ -111,7 +123,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 28 more"` + ); }); test('[rule_id, description, from, to, name, severity, type] does not validate', () => { @@ -127,7 +141,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 27 more"` + ); }); test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { @@ -144,7 +160,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 27 more"` + ); }); test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { @@ -162,7 +180,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 27 more"` + ); }); test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { @@ -202,7 +222,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", risk_score: Required, risk_score: Required, and 22 more"` + ); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { @@ -368,7 +390,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"references.0: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", references.0: Expected string, received number, references.0: Expected string, received number, and 22 more"` + ); }); test('indexes cannot be numbers', () => { @@ -379,7 +403,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", index.0: Expected string, received number, index.0: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", and 20 more"` + ); }); test('saved_query type can have filters with it', () => { @@ -401,7 +427,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", filters: Expected array, received string, filters: Expected array, received string, type: Invalid literal value, expected \\"saved_query\\", and 20 more"` + ); }); test('language validates with kuery', () => { @@ -434,7 +462,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 19 more"` + ); }); test('max_signals cannot be negative', () => { @@ -493,7 +523,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"tags.0: Expected string, received number, tags.1: Expected string, received number, tags.2: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", and 38 more"` + ); }); test('You cannot send in an array of threat that are missing "framework"', () => { @@ -519,7 +551,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"threat.0.framework: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", threat.0.framework: Required, threat.0.framework: Required, and 22 more"` + ); }); test('You cannot send in an array of threat that are missing "tactic"', () => { @@ -541,7 +575,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"threat.0.tactic: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", threat.0.tactic: Required, threat.0.tactic: Required, and 22 more"` + ); }); test('You can send in an array of threat that are missing "technique"', () => { @@ -583,7 +619,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"false_positives.0: Expected string, received number, false_positives.1: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", false_positives.0: Expected string, received number, and 30 more"` + ); }); test('You cannot set the risk_score to 101', () => { @@ -655,7 +693,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"meta: Expected object, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", meta: Expected object, received string, meta: Expected object, received string, and 22 more"` + ); }); test('You can omit the query string when filters are present', () => { @@ -690,7 +730,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', and 22 more"` + ); }); test('You cannot send in an array of actions that are missing "group"', () => { @@ -701,7 +743,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.group: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.group: Required, actions.0.group: Required, and 22 more"` + ); }); test('You cannot send in an array of actions that are missing "id"', () => { @@ -712,7 +756,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.id: Required, actions.0.id: Required, and 22 more"` + ); }); test('You cannot send in an array of actions that are missing "action_type_id"', () => { @@ -723,7 +769,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.action_type_id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.action_type_id: Required, actions.0.action_type_id: Required, and 22 more"` + ); }); test('You cannot send in an array of actions that are missing "params"', () => { @@ -734,7 +782,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.params: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.params: Required, actions.0.params: Required, and 22 more"` + ); }); test('You cannot send in an array of actions that are including "actionTypeId"', () => { @@ -752,7 +802,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.action_type_id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.action_type_id: Required, actions.0.action_type_id: Required, and 22 more"` + ); }); describe('note', () => { @@ -788,7 +840,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"note: Expected string, received object, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", note: Expected string, received object, note: Expected string, received object, and 22 more"` + ); }); test('empty name is not valid', () => { @@ -872,7 +926,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", saved_id: Required, type: Invalid literal value, expected \\"threshold\\", and 14 more"` + ); }); test('threshold is required when type is threshold and will not validate without it', () => { @@ -880,7 +936,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 14 more"` + ); }); test('threshold rules fail validation if threshold is not greater than 0', () => { @@ -958,7 +1016,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"exceptions_list.0.list_id: Required, exceptions_list.0.type: Required, exceptions_list.0.namespace_type: Invalid enum value. Expected 'agnostic' | 'single', received 'not a namespace type', type: Invalid literal value, expected \\"eql\\", query: Required, and 43 more"` + ); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { @@ -999,7 +1059,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 14 more"` + ); }); test('fails validation when threat_mapping is an empty array', () => { @@ -1068,7 +1130,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", data_view_id: Expected string, received number, data_view_id: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", and 20 more"` + ); }); test('it should validate a type of "query" with "data_view_id" defined', () => { @@ -1131,7 +1195,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"investigation_fields.field_names: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", investigation_fields.field_names: Required, investigation_fields.field_names: Required, and 22 more"` + ); }); test('You can send in investigation_fields', () => { @@ -1166,7 +1232,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"investigation_fields.field_names.0: Expected string, received number, investigation_fields.field_names.1: Expected string, received number, investigation_fields.field_names.2: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", and 38 more"` + ); }); test('You cannot send in investigation_fields without specifying fields', () => { @@ -1177,7 +1245,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"investigation_fields.field_names: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", investigation_fields.field_names: Required, investigation_fields.field_names: Required, and 22 more"` + ); }); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts index e8573502cb662..d1432e5a67352 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts @@ -40,7 +40,9 @@ describe('Rule response schema', () => { const result = RuleResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 15 more"` + ); }); test('it should validate a type of "query" with a saved_id together', () => { @@ -68,7 +70,9 @@ describe('Rule response schema', () => { const result = RuleResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", saved_id: Required, type: Invalid literal value, expected \\"threshold\\", and 14 more"` + ); }); test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { @@ -98,7 +102,9 @@ describe('Rule response schema', () => { const result = RuleResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"exceptions_list: Expected array, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", exceptions_list: Expected array, received string, exceptions_list: Expected array, received string, and 22 more"` + ); }); }); @@ -232,6 +238,8 @@ describe('investigation_fields', () => { const result = RuleResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"investigation_fields: Expected object, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", investigation_fields: Expected object, received string, investigation_fields: Expected object, received string, and 22 more"` + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/response_actions_legacy.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/response_actions.ts similarity index 82% rename from x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/response_actions_legacy.ts rename to x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/response_actions.ts index 6947953b4d65d..8f176a9908041 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/response_actions_legacy.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/response_actions.ts @@ -4,18 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { arrayQueries, ecsMapping } from '@kbn/osquery-io-ts-types'; import * as t from 'io-ts'; import { ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS } from '../../../../endpoint/service/response_actions/constants'; -import { ResponseActionTypesEnum } from './response_actions.gen'; - -export const RESPONSE_ACTION_TYPES = { - OSQUERY: ResponseActionTypesEnum['.osquery'], - ENDPOINT: ResponseActionTypesEnum['.endpoint'], -} as const; - -export const SUPPORTED_RESPONSE_ACTION_TYPES = Object.values(RESPONSE_ACTION_TYPES); // to enable using RESPONSE_ACTION_API_COMMANDS_NAMES as a type function keyObject(arr: T): { [K in T[number]]: null } { @@ -47,13 +38,13 @@ export const OsqueryParamsCamelCase = t.type({ // When we create new response action types, create a union of types export type RuleResponseOsqueryAction = t.TypeOf; export const RuleResponseOsqueryAction = t.strict({ - actionTypeId: t.literal(RESPONSE_ACTION_TYPES.OSQUERY), + actionTypeId: t.literal('.osquery'), params: OsqueryParamsCamelCase, }); export type RuleResponseEndpointAction = t.TypeOf; export const RuleResponseEndpointAction = t.strict({ - actionTypeId: t.literal(RESPONSE_ACTION_TYPES.ENDPOINT), + actionTypeId: t.literal('.endpoint'), params: EndpointParams, }); @@ -67,12 +58,12 @@ export const ResponseActionRuleParamsOrUndefined = t.union([ // When we create new response action types, create a union of types const OsqueryResponseAction = t.strict({ - action_type_id: t.literal(RESPONSE_ACTION_TYPES.OSQUERY), + action_type_id: t.literal('.osquery'), params: OsqueryParams, }); const EndpointResponseAction = t.strict({ - action_type_id: t.literal(RESPONSE_ACTION_TYPES.ENDPOINT), + action_type_id: t.literal('.endpoint'), params: EndpointParams, }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/rule_schemas.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/rule_schemas.ts index e95fa38e0d2e6..4ec9ca19ee399 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/rule_schemas.ts @@ -26,20 +26,10 @@ import { threat_mapping, threat_query, } from '@kbn/securitysolution-io-ts-alerting-types'; +import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; +import { ResponseActionArray } from './response_actions'; -import { RuleExecutionSummary } from '../../rule_monitoring/model'; -// eslint-disable-next-line no-restricted-imports -import { ResponseActionArray } from '../rule_response_actions/response_actions_legacy'; - -import { - anomaly_threshold, - created_at, - created_by, - revision, - saved_id, - updated_at, - updated_by, -} from '../schemas'; +import { anomaly_threshold, saved_id } from '../schemas'; import { AlertsIndex, @@ -51,10 +41,7 @@ import { InvestigationFields, InvestigationGuide, IsRuleEnabled, - IsRuleImmutable, MaxSignals, - RelatedIntegrationArray, - RequiredFieldArray, RuleAuthorArray, RuleDescription, RuleFalsePositiveArray, @@ -63,7 +50,6 @@ import { RuleMetadata, RuleName, RuleNameOverride, - RuleObjectId, RuleQuery, RuleReferenceArray, RuleSignatureId, @@ -72,7 +58,6 @@ import { SavedObjectResolveAliasPurpose, SavedObjectResolveAliasTargetId, SavedObjectResolveOutcome, - SetupGuide, ThreatArray, TimelineTemplateId, TimelineTemplateTitle, @@ -186,28 +171,24 @@ export const baseSchema = buildRuleSchemas({ }, }); -const responseRequiredFields = { - id: RuleObjectId, - rule_id: RuleSignatureId, - immutable: IsRuleImmutable, - updated_at, - updated_by, - created_at, - created_by, - revision, - - // NOTE: For now, Related Integrations, Required Fields and Setup Guide are supported for prebuilt - // rules only. We don't want to allow users to edit these 3 fields via the API. If we added them - // to baseParams.defaultable, they would become a part of the request schema as optional fields. - // This is why we add them here, in order to add them only to the response schema. - related_integrations: RelatedIntegrationArray, - required_fields: RequiredFieldArray, - setup: SetupGuide, -}; - -const responseOptionalFields = { - execution_summary: RuleExecutionSummary, -}; +export type DurationMetric = t.TypeOf; +export const DurationMetric = PositiveInteger; + +export type RuleExecutionMetrics = t.TypeOf; + +/** + @property total_search_duration_ms - "total time spent performing ES searches as measured by Kibana; + includes network latency and time spent serializing/deserializing request/response", + @property total_indexing_duration_ms - "total time spent indexing documents during current rule execution cycle", + @property total_enrichment_duration_ms - total time spent enriching documents during current rule execution cycle + @property execution_gap_duration_s - "duration in seconds of execution gap" +*/ +export const RuleExecutionMetrics = t.partial({ + total_search_duration_ms: DurationMetric, + total_indexing_duration_ms: DurationMetric, + total_enrichment_duration_ms: DurationMetric, + execution_gap_duration_s: DurationMetric, +}); export type BaseCreateProps = t.TypeOf; export const BaseCreateProps = baseSchema.create; @@ -225,36 +206,9 @@ export const SharedCreateProps = t.intersection([ t.exact(t.partial({ rule_id: RuleSignatureId })), ]); -type SharedUpdateProps = t.TypeOf; -const SharedUpdateProps = t.intersection([ - baseSchema.create, - t.exact(t.partial({ rule_id: RuleSignatureId })), - t.exact(t.partial({ id: RuleObjectId })), -]); - -type SharedPatchProps = t.TypeOf; -const SharedPatchProps = t.intersection([ - baseSchema.patch, - t.exact(t.partial({ rule_id: RuleSignatureId, id: RuleObjectId })), -]); - -export type SharedResponseProps = t.TypeOf; -export const SharedResponseProps = t.intersection([ - baseSchema.response, - t.exact(t.type(responseRequiredFields)), - t.exact(t.partial(responseOptionalFields)), -]); - // ------------------------------------------------------------------------------------------------- // EQL rule schema -export enum QueryLanguage { - 'kuery' = 'kuery', - 'lucene' = 'lucene', - 'eql' = 'eql', - 'esql' = 'esql', -} - export type KqlQueryLanguage = t.TypeOf; export const KqlQueryLanguage = t.keyof({ kuery: null, lucene: null }); @@ -278,21 +232,6 @@ const eqlSchema = buildRuleSchemas({ defaultable: {}, }); -export type EqlRule = t.TypeOf; -export const EqlRule = t.intersection([SharedResponseProps, eqlSchema.response]); - -export type EqlRuleCreateProps = t.TypeOf; -export const EqlRuleCreateProps = t.intersection([SharedCreateProps, eqlSchema.create]); - -export type EqlRuleUpdateProps = t.TypeOf; -export const EqlRuleUpdateProps = t.intersection([SharedUpdateProps, eqlSchema.create]); - -export type EqlRulePatchProps = t.TypeOf; -export const EqlRulePatchProps = t.intersection([SharedPatchProps, eqlSchema.patch]); - -export type EqlPatchParams = t.TypeOf; -export const EqlPatchParams = eqlSchema.patch; - // ------------------------------------------------------------------------------------------------- // ES|QL rule schema @@ -309,21 +248,6 @@ const esqlSchema = buildRuleSchemas({ defaultable: {}, }); -export type EsqlRule = t.TypeOf; -export const EsqlRule = t.intersection([SharedResponseProps, esqlSchema.response]); - -export type EsqlRuleCreateProps = t.TypeOf; -export const EsqlRuleCreateProps = t.intersection([SharedCreateProps, esqlSchema.create]); - -export type EsqlRuleUpdateProps = t.TypeOf; -export const EsqlRuleUpdateProps = t.intersection([SharedUpdateProps, esqlSchema.create]); - -export type EsqlRulePatchProps = t.TypeOf; -export const EsqlRulePatchProps = t.intersection([SharedPatchProps, esqlSchema.patch]); - -export type EsqlPatchParams = t.TypeOf; -export const EsqlPatchParams = esqlSchema.patch; - // ------------------------------------------------------------------------------------------------- // Indicator Match rule schema @@ -351,30 +275,6 @@ const threatMatchSchema = buildRuleSchemas({ }, }); -export type ThreatMatchRule = t.TypeOf; -export const ThreatMatchRule = t.intersection([SharedResponseProps, threatMatchSchema.response]); - -export type ThreatMatchRuleCreateProps = t.TypeOf; -export const ThreatMatchRuleCreateProps = t.intersection([ - SharedCreateProps, - threatMatchSchema.create, -]); - -export type ThreatMatchRuleUpdateProps = t.TypeOf; -export const ThreatMatchRuleUpdateProps = t.intersection([ - SharedUpdateProps, - threatMatchSchema.create, -]); - -export type ThreatMatchRulePatchProps = t.TypeOf; -export const ThreatMatchRulePatchProps = t.intersection([ - SharedPatchProps, - threatMatchSchema.patch, -]); - -export type ThreatMatchPatchParams = t.TypeOf; -export const ThreatMatchPatchParams = threatMatchSchema.patch; - // ------------------------------------------------------------------------------------------------- // Custom Query rule schema @@ -396,21 +296,6 @@ const querySchema = buildRuleSchemas({ }, }); -export type QueryRule = t.TypeOf; -export const QueryRule = t.intersection([SharedResponseProps, querySchema.response]); - -export type QueryRuleCreateProps = t.TypeOf; -export const QueryRuleCreateProps = t.intersection([SharedCreateProps, querySchema.create]); - -export type QueryRuleUpdateProps = t.TypeOf; -export const QueryRuleUpdateProps = t.intersection([SharedUpdateProps, querySchema.create]); - -export type QueryRulePatchProps = t.TypeOf; -export const QueryRulePatchProps = t.intersection([SharedPatchProps, querySchema.patch]); - -export type QueryPatchParams = t.TypeOf; -export const QueryPatchParams = querySchema.patch; - // ------------------------------------------------------------------------------------------------- // Saved Query rule schema @@ -434,27 +319,6 @@ const savedQuerySchema = buildRuleSchemas({ }, }); -export type SavedQueryRule = t.TypeOf; -export const SavedQueryRule = t.intersection([SharedResponseProps, savedQuerySchema.response]); - -export type SavedQueryRuleCreateProps = t.TypeOf; -export const SavedQueryRuleCreateProps = t.intersection([ - SharedCreateProps, - savedQuerySchema.create, -]); - -export type SavedQueryRuleUpdateProps = t.TypeOf; -export const SavedQueryRuleUpdateProps = t.intersection([ - SharedUpdateProps, - savedQuerySchema.create, -]); - -export type SavedQueryRulePatchProps = t.TypeOf; -export const SavedQueryRulePatchProps = t.intersection([SharedPatchProps, savedQuerySchema.patch]); - -export type SavedQueryPatchParams = t.TypeOf; -export const SavedQueryPatchParams = savedQuerySchema.patch; - // ------------------------------------------------------------------------------------------------- // Threshold rule schema @@ -475,21 +339,6 @@ const thresholdSchema = buildRuleSchemas({ }, }); -export type ThresholdRule = t.TypeOf; -export const ThresholdRule = t.intersection([SharedResponseProps, thresholdSchema.response]); - -export type ThresholdRuleCreateProps = t.TypeOf; -export const ThresholdRuleCreateProps = t.intersection([SharedCreateProps, thresholdSchema.create]); - -export type ThresholdRuleUpdateProps = t.TypeOf; -export const ThresholdRuleUpdateProps = t.intersection([SharedUpdateProps, thresholdSchema.create]); - -export type ThresholdRulePatchProps = t.TypeOf; -export const ThresholdRulePatchProps = t.intersection([SharedPatchProps, thresholdSchema.patch]); - -export type ThresholdPatchParams = t.TypeOf; -export const ThresholdPatchParams = thresholdSchema.patch; - // ------------------------------------------------------------------------------------------------- // Machine Learning rule schema @@ -503,33 +352,6 @@ const machineLearningSchema = buildRuleSchemas({ defaultable: {}, }); -export type MachineLearningRule = t.TypeOf; -export const MachineLearningRule = t.intersection([ - SharedResponseProps, - machineLearningSchema.response, -]); - -export type MachineLearningRuleCreateProps = t.TypeOf; -export const MachineLearningRuleCreateProps = t.intersection([ - SharedCreateProps, - machineLearningSchema.create, -]); - -export type MachineLearningRuleUpdateProps = t.TypeOf; -export const MachineLearningRuleUpdateProps = t.intersection([ - SharedUpdateProps, - machineLearningSchema.create, -]); - -export type MachineLearningRulePatchProps = t.TypeOf; -export const MachineLearningRulePatchProps = t.intersection([ - SharedPatchProps, - machineLearningSchema.patch, -]); - -export type MachineLearningPatchParams = t.TypeOf; -export const MachineLearningPatchParams = machineLearningSchema.patch; - // ------------------------------------------------------------------------------------------------- // New Terms rule schema @@ -550,21 +372,6 @@ const newTermsSchema = buildRuleSchemas({ }, }); -export type NewTermsRule = t.TypeOf; -export const NewTermsRule = t.intersection([SharedResponseProps, newTermsSchema.response]); - -export type NewTermsRuleCreateProps = t.TypeOf; -export const NewTermsRuleCreateProps = t.intersection([SharedCreateProps, newTermsSchema.create]); - -export type NewTermsRuleUpdateProps = t.TypeOf; -export const NewTermsRuleUpdateProps = t.intersection([SharedUpdateProps, newTermsSchema.create]); - -export type NewTermsRulePatchProps = t.TypeOf; -export const NewTermsRulePatchProps = t.intersection([SharedPatchProps, newTermsSchema.patch]); - -export type NewTermsPatchParams = t.TypeOf; -export const NewTermsPatchParams = newTermsSchema.patch; - // ------------------------------------------------------------------------------------------------- // Combined type specific schemas @@ -579,42 +386,3 @@ export const TypeSpecificCreateProps = t.union([ machineLearningSchema.create, newTermsSchema.create, ]); - -export type TypeSpecificPatchProps = t.TypeOf; -export const TypeSpecificPatchProps = t.union([ - eqlSchema.patch, - esqlSchema.patch, - threatMatchSchema.patch, - querySchema.patch, - savedQuerySchema.patch, - thresholdSchema.patch, - machineLearningSchema.patch, - newTermsSchema.patch, -]); - -export type TypeSpecificResponse = t.TypeOf; -export const TypeSpecificResponse = t.union([ - eqlSchema.response, - esqlSchema.response, - threatMatchSchema.response, - querySchema.response, - savedQuerySchema.response, - thresholdSchema.response, - machineLearningSchema.response, - newTermsSchema.response, -]); - -// ------------------------------------------------------------------------------------------------- -// Final combined schemas - -export type RuleCreateProps = t.TypeOf; -export const RuleCreateProps = t.intersection([TypeSpecificCreateProps, SharedCreateProps]); - -export type RuleUpdateProps = t.TypeOf; -export const RuleUpdateProps = t.intersection([TypeSpecificCreateProps, SharedUpdateProps]); - -export type RulePatchProps = t.TypeOf; -export const RulePatchProps = t.intersection([TypeSpecificPatchProps, SharedPatchProps]); - -export type RuleResponse = t.TypeOf; -export const RuleResponse = t.intersection([TypeSpecificResponse, SharedResponseProps]); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts index 9c325d1e70fc0..35a394edcb6ad 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts @@ -33,12 +33,6 @@ export type Status = t.TypeOf; export const conflicts = t.keyof({ abort: null, proceed: null }); -export const queryFilter = t.string; -export type QueryFilter = t.TypeOf; - -export const queryFilterOrUndefined = t.union([queryFilter, t.undefined]); -export type QueryFilterOrUndefined = t.TypeOf; - export const signal_ids = t.array(t.string); export type SignalIds = t.TypeOf; @@ -48,23 +42,12 @@ export const signal_status_query = t.object; export const alert_tag_ids = t.array(t.string); export type AlertTagIds = t.TypeOf; -export const fields = t.array(t.string); -export type Fields = t.TypeOf; -export const fieldsOrUndefined = t.union([fields, t.undefined]); -export type FieldsOrUndefined = t.TypeOf; - export const created_at = IsoDateString; export const updated_at = IsoDateString; export const created_by = t.string; export const updated_by = t.string; -export const status_code = PositiveInteger; -export const message = t.string; -export const perPage = PositiveInteger; -export const total = PositiveInteger; export const revision = PositiveInteger; -export const success = t.boolean; -export const success_count = PositiveInteger; export const indexRecord = t.record( t.string, diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting.test.ts index 17ad724039d7e..04a5fadefe051 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting.test.ts @@ -5,85 +5,30 @@ * 2.0. */ -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { DefaultSortOrderAsc, DefaultSortOrderDesc } from './sorting_legacy'; - -describe('Common sorting schemas', () => { - describe('DefaultSortOrderAsc', () => { - describe('Validation succeeds', () => { - it('when valid sort order is passed', () => { - const payload = 'desc'; - const decoded = DefaultSortOrderAsc.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - }); - - describe('Validation fails', () => { - it('when invalid sort order is passed', () => { - const payload = 'behind_you'; - const decoded = DefaultSortOrderAsc.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "behind_you" supplied to "DefaultSortOrderAsc"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('Validation sets the default sort order "asc"', () => { - it('when sort order is not passed', () => { - const payload = undefined; - const decoded = DefaultSortOrderAsc.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual('asc'); - }); - }); +import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers'; +import { SortOrder } from './sorting.gen'; + +describe('SortOrder schema', () => { + it('accepts asc value', () => { + const payload = 'asc'; + const result = SortOrder.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); - describe('DefaultSortOrderDesc', () => { - describe('Validation succeeds', () => { - it('when valid sort order is passed', () => { - const payload = 'asc'; - const decoded = DefaultSortOrderDesc.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - }); - - describe('Validation fails', () => { - it('when invalid sort order is passed', () => { - const payload = 'behind_you'; - const decoded = DefaultSortOrderDesc.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "behind_you" supplied to "DefaultSortOrderDesc"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('Validation sets the default sort order "desc"', () => { - it('when sort order is not passed', () => { - const payload = null; - const decoded = DefaultSortOrderDesc.decode(payload); - const message = pipe(decoded, foldLeftRight); + it('accepts desc value', () => { + const payload = 'desc'; + const result = SortOrder.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); + }); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual('desc'); - }); - }); + it('fails on unknown value', () => { + const payload = 'invalid'; + const result = SortOrder.safeParse(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual( + "Invalid enum value. Expected 'asc' | 'desc', received 'invalid'" + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting_legacy.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting_legacy.ts deleted file mode 100644 index 8aa8cf2831ea1..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting_legacy.ts +++ /dev/null @@ -1,40 +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 * as t from 'io-ts'; -import type { Either } from 'fp-ts/lib/Either'; -import { capitalize } from 'lodash'; - -export type SortOrder = t.TypeOf; -export const SortOrder = t.keyof({ asc: null, desc: null }); - -export type SortOrderOrUndefined = t.TypeOf; -export const SortOrderOrUndefined = t.union([SortOrder, t.undefined]); - -const defaultSortOrder = (order: SortOrder): t.Type => { - return new t.Type( - `DefaultSortOrder${capitalize(order)}`, - SortOrder.is, - (input, context): Either => - input == null ? t.success(order) : SortOrder.validate(input, context), - t.identity - ); -}; - -/** - * Types the DefaultSortOrderAsc as: - * - If undefined, then a default sort order of 'asc' will be set - * - If a string is sent in, then the string will be validated to ensure it's a valid SortOrder - */ -export const DefaultSortOrderAsc = defaultSortOrder('asc'); - -/** - * Types the DefaultSortOrderDesc as: - * - If undefined, then a default sort order of 'desc' will be set - * - If a string is sent in, then the string will be validated to ensure it's a valid SortOrder - */ -export const DefaultSortOrderDesc = defaultSortOrder('desc'); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/create_rule_exceptions/create_rule_exceptions_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/create_rule_exceptions/create_rule_exceptions_route.ts index feecf23faf293..75eb2b543d337 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/create_rule_exceptions/create_rule_exceptions_route.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/create_rule_exceptions/create_rule_exceptions_route.ts @@ -12,9 +12,7 @@ import type { ExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { createRuleExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { RuleObjectId } from '../../model/rule_schema_legacy'; +import { UUID } from '@kbn/securitysolution-io-ts-types'; /** * URL path parameters of the API route. @@ -22,7 +20,7 @@ import { RuleObjectId } from '../../model/rule_schema_legacy'; export type CreateRuleExceptionsRequestParams = t.TypeOf; export const CreateRuleExceptionsRequestParams = t.exact( t.type({ - id: RuleObjectId, + id: UUID, }) ); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen.ts new file mode 100644 index 0000000000000..d11eea7b16711 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen.ts @@ -0,0 +1,291 @@ +/* + * 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 { z } from 'zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +import { RuleResponse } from '../../model/rule_schema/rule_schemas.gen'; +import { + RuleActionGroup, + RuleActionId, + RuleActionParams, + RuleActionFrequency, + RuleActionAlertsFilter, + IndexPatternArray, + RuleTagArray, + TimelineTemplateId, + TimelineTemplateTitle, +} from '../../model/rule_schema/common_attributes.gen'; + +export type BulkEditSkipReason = z.infer; +export const BulkEditSkipReason = z.literal('RULE_NOT_MODIFIED'); + +export type BulkActionSkipResult = z.infer; +export const BulkActionSkipResult = z.object({ + id: z.string(), + name: z.string().optional(), + skip_reason: BulkEditSkipReason, +}); + +export type RuleDetailsInError = z.infer; +export const RuleDetailsInError = z.object({ + id: z.string(), + name: z.string().optional(), +}); + +export type BulkActionsDryRunErrCode = z.infer; +export const BulkActionsDryRunErrCode = z.enum([ + 'IMMUTABLE', + 'MACHINE_LEARNING_AUTH', + 'MACHINE_LEARNING_INDEX_PATTERN', + 'ESQL_INDEX_PATTERN', +]); +export type BulkActionsDryRunErrCodeEnum = typeof BulkActionsDryRunErrCode.enum; +export const BulkActionsDryRunErrCodeEnum = BulkActionsDryRunErrCode.enum; + +export type NormalizedRuleError = z.infer; +export const NormalizedRuleError = z.object({ + message: z.string(), + status_code: z.number().int(), + err_code: BulkActionsDryRunErrCode.optional(), + rules: z.array(RuleDetailsInError), +}); + +export type BulkEditActionResults = z.infer; +export const BulkEditActionResults = z.object({ + updated: z.array(RuleResponse), + created: z.array(RuleResponse), + deleted: z.array(RuleResponse), + skipped: z.array(BulkActionSkipResult), +}); + +export type BulkEditActionSummary = z.infer; +export const BulkEditActionSummary = z.object({ + failed: z.number().int(), + skipped: z.number().int(), + succeeded: z.number().int(), + total: z.number().int(), +}); + +export type BulkEditActionResponse = z.infer; +export const BulkEditActionResponse = z.object({ + success: z.boolean().optional(), + status_code: z.number().int().optional(), + message: z.string().optional(), + rules_count: z.number().int().optional(), + attributes: z.object({ + results: BulkEditActionResults, + summary: BulkEditActionSummary, + errors: z.array(NormalizedRuleError).optional(), + }), +}); + +export type BulkExportActionResponse = z.infer; +export const BulkExportActionResponse = z.string(); + +export type BulkActionBase = z.infer; +export const BulkActionBase = z.object({ + /** + * Query to filter rules + */ + query: z.string().optional(), + /** + * Array of rule IDs + */ + ids: z.array(z.string()).min(1).optional(), +}); + +export type BulkDeleteRules = z.infer; +export const BulkDeleteRules = BulkActionBase.and( + z.object({ + action: z.literal('delete'), + }) +); + +export type BulkDisableRules = z.infer; +export const BulkDisableRules = BulkActionBase.and( + z.object({ + action: z.literal('disable'), + }) +); + +export type BulkEnableRules = z.infer; +export const BulkEnableRules = BulkActionBase.and( + z.object({ + action: z.literal('enable'), + }) +); + +export type BulkExportRules = z.infer; +export const BulkExportRules = BulkActionBase.and( + z.object({ + action: z.literal('export'), + }) +); + +export type BulkDuplicateRules = z.infer; +export const BulkDuplicateRules = BulkActionBase.and( + z.object({ + action: z.literal('duplicate'), + duplicate: z + .object({ + /** + * Whether to copy exceptions from the original rule + */ + include_exceptions: z.boolean(), + /** + * Whether to copy expired exceptions from the original rule + */ + include_expired_exceptions: z.boolean(), + }) + .optional(), + }) +); + +/** + * The condition for throttling the notification: 'rule', 'no_actions', or time duration + */ +export type ThrottleForBulkActions = z.infer; +export const ThrottleForBulkActions = z.enum(['rule', '1h', '1d', '7d']); +export type ThrottleForBulkActionsEnum = typeof ThrottleForBulkActions.enum; +export const ThrottleForBulkActionsEnum = ThrottleForBulkActions.enum; + +export type BulkActionType = z.infer; +export const BulkActionType = z.enum([ + 'enable', + 'disable', + 'export', + 'delete', + 'duplicate', + 'edit', +]); +export type BulkActionTypeEnum = typeof BulkActionType.enum; +export const BulkActionTypeEnum = BulkActionType.enum; + +export type BulkActionEditType = z.infer; +export const BulkActionEditType = z.enum([ + 'add_tags', + 'delete_tags', + 'set_tags', + 'add_index_patterns', + 'delete_index_patterns', + 'set_index_patterns', + 'set_timeline', + 'add_rule_actions', + 'set_rule_actions', + 'set_schedule', +]); +export type BulkActionEditTypeEnum = typeof BulkActionEditType.enum; +export const BulkActionEditTypeEnum = BulkActionEditType.enum; + +export type NormalizedRuleAction = z.infer; +export const NormalizedRuleAction = z + .object({ + group: RuleActionGroup, + id: RuleActionId, + params: RuleActionParams, + frequency: RuleActionFrequency.optional(), + alerts_filter: RuleActionAlertsFilter.optional(), + }) + .strict(); + +export type BulkActionEditPayloadRuleActions = z.infer; +export const BulkActionEditPayloadRuleActions = z.object({ + type: z.enum(['add_rule_actions', 'set_rule_actions']), + value: z.object({ + throttle: ThrottleForBulkActions.optional(), + actions: z.array(NormalizedRuleAction), + }), +}); + +export type BulkActionEditPayloadSchedule = z.infer; +export const BulkActionEditPayloadSchedule = z.object({ + type: z.literal('set_schedule'), + value: z.object({ + /** + * Interval in which the rule is executed + */ + interval: z.string().regex(/^[1-9]\d*[smh]$/), + /** + * Lookback time for the rule + */ + lookback: z.string().regex(/^[1-9]\d*[smh]$/), + }), +}); + +export type BulkActionEditPayloadIndexPatterns = z.infer; +export const BulkActionEditPayloadIndexPatterns = z.object({ + type: z.enum(['add_index_patterns', 'delete_index_patterns', 'set_index_patterns']), + value: IndexPatternArray, + overwrite_data_views: z.boolean().optional(), +}); + +export type BulkActionEditPayloadTags = z.infer; +export const BulkActionEditPayloadTags = z.object({ + type: z.enum(['add_tags', 'delete_tags', 'set_tags']), + value: RuleTagArray, +}); + +export type BulkActionEditPayloadTimeline = z.infer; +export const BulkActionEditPayloadTimeline = z.object({ + type: z.literal('set_timeline'), + value: z.object({ + timeline_id: TimelineTemplateId, + timeline_title: TimelineTemplateTitle, + }), +}); + +export type BulkActionEditPayload = z.infer; +export const BulkActionEditPayload = z.union([ + BulkActionEditPayloadTags, + BulkActionEditPayloadIndexPatterns, + BulkActionEditPayloadTimeline, + BulkActionEditPayloadRuleActions, + BulkActionEditPayloadSchedule, +]); + +export type BulkEditRules = z.infer; +export const BulkEditRules = BulkActionBase.and( + z.object({ + action: z.literal('edit'), + /** + * Array of objects containing the edit operations + */ + edit: z.array(BulkActionEditPayload).min(1), + }) +); + +export type PerformBulkActionRequestQuery = z.infer; +export const PerformBulkActionRequestQuery = z.object({ + /** + * Enables dry run mode for the request call. + */ + dry_run: BooleanFromString.optional(), +}); +export type PerformBulkActionRequestQueryInput = z.input; + +export type PerformBulkActionRequestBody = z.infer; +export const PerformBulkActionRequestBody = z.union([ + BulkDeleteRules, + BulkDisableRules, + BulkEnableRules, + BulkExportRules, + BulkDuplicateRules, + BulkEditRules, +]); +export type PerformBulkActionRequestBodyInput = z.input; + +export type PerformBulkActionResponse = z.infer; +export const PerformBulkActionResponse = z.union([ + BulkEditActionResponse, + BulkExportActionResponse, +]); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.mock.ts index 66ce78ff9b615..fa4fcefbcad1a 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.mock.ts @@ -5,18 +5,18 @@ * 2.0. */ -import { BulkActionType, BulkActionEditType } from './bulk_actions_route'; -import type { PerformBulkActionRequestBody } from './bulk_actions_route'; +import type { PerformBulkActionRequestBody } from './bulk_actions_route.gen'; +import { BulkActionEditTypeEnum, BulkActionTypeEnum } from './bulk_actions_route.gen'; export const getPerformBulkActionSchemaMock = (): PerformBulkActionRequestBody => ({ query: '', ids: undefined, - action: BulkActionType.disable, + action: BulkActionTypeEnum.disable, }); export const getPerformBulkActionEditSchemaMock = (): PerformBulkActionRequestBody => ({ query: '', ids: undefined, - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.add_tags, value: ['tag1'] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.add_tags, value: ['tag1'] }], }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml index 8eba09881bbd9..583782f086ae7 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml @@ -6,7 +6,7 @@ paths: /api/detection_engine/rules/_bulk_action: post: operationId: PerformBulkAction - x-codegen-enabled: false + x-codegen-enabled: true summary: Applies a bulk action to multiple rules description: The bulk action is applied to all rules that match the filter or to the list of rules by their IDs. tags: @@ -22,19 +22,24 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PerformBulkActionRequest' + oneOf: + - $ref: '#/components/schemas/BulkDeleteRules' + - $ref: '#/components/schemas/BulkDisableRules' + - $ref: '#/components/schemas/BulkEnableRules' + - $ref: '#/components/schemas/BulkExportRules' + - $ref: '#/components/schemas/BulkDuplicateRules' + - $ref: '#/components/schemas/BulkEditRules' responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/BulkEditActionResponse' + oneOf: + - $ref: '#/components/schemas/BulkEditActionResponse' + - $ref: '#/components/schemas/BulkExportActionResponse' components: - x-codegen-enabled: false schemas: BulkEditSkipReason: type: string @@ -66,6 +71,11 @@ components: BulkActionsDryRunErrCode: type: string + enum: + - IMMUTABLE + - MACHINE_LEARNING_AUTH + - MACHINE_LEARNING_INDEX_PATTERN + - ESQL_INDEX_PATTERN NormalizedRuleError: type: object @@ -127,35 +137,17 @@ components: - succeeded - total - BulkEditActionSuccessResponse: + BulkEditActionResponse: type: object properties: success: type: boolean - rules_count: - type: integer - attributes: - type: object - properties: - results: - $ref: '#/components/schemas/BulkEditActionResults' - summary: - $ref: '#/components/schemas/BulkEditActionSummary' - required: - - results - - summary - required: - - success - - rules_count - - attributes - - BulkEditActionErrorResponse: - type: object - properties: status_code: type: integer message: type: string + rules_count: + type: integer attributes: type: object properties: @@ -171,35 +163,23 @@ components: - results - summary required: - - status_code - - message - attributes - BulkEditActionResponse: - oneOf: - - $ref: '#/components/schemas/BulkEditActionSuccessResponse' - - $ref: '#/components/schemas/BulkEditActionErrorResponse' + BulkExportActionResponse: + type: string BulkActionBase: - oneOf: - - type: object - properties: - query: - type: string - description: Query to filter rules - required: - - query - additionalProperties: false - - - type: object - properties: - ids: - type: array - description: Array of rule IDs - minItems: 1 - items: - type: string - additionalProperties: false + type: object + properties: + query: + type: string + description: Query to filter rules + ids: + type: array + description: Array of rule IDs + minItems: 1 + items: + type: string BulkDeleteRules: allOf: @@ -262,35 +242,20 @@ components: include_expired_exceptions: type: boolean description: Whether to copy expired exceptions from the original rule + required: + - include_exceptions + - include_expired_exceptions required: - action - RuleActionSummary: - type: boolean - description: Action summary indicates whether we will send a summary notification about all the generate alerts or notification per individual alert - - RuleActionNotifyWhen: - type: string - description: "The condition for throttling the notification: 'onActionGroupChange', 'onActiveAlert', or 'onThrottleInterval'" - enum: - - onActionGroupChange - - onActiveAlert - - onThrottleInterval - - RuleActionThrottle: + ThrottleForBulkActions: type: string description: "The condition for throttling the notification: 'rule', 'no_actions', or time duration" - - RuleActionFrequency: - type: object - properties: - summary: - $ref: '#/components/schemas/RuleActionSummary' - notifyWhen: - $ref: '#/components/schemas/RuleActionNotifyWhen' - throttle: - $ref: '#/components/schemas/RuleActionThrottle' - nullable: true + enum: + - rule + - 1h + - 1d + - 7d BulkActionType: type: string @@ -316,6 +281,26 @@ components: - set_rule_actions - set_schedule + # Per rulesClient.bulkEdit rules actions operation contract (x-pack/plugins/alerting/server/rules_client/rules_client.ts) normalized rule action object is expected (NormalizedAlertAction) as value for the edit operation + NormalizedRuleAction: + type: object + properties: + group: + $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionGroup' + id: + $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionId' + params: + $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionParams' + frequency: + $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionFrequency' + alerts_filter: + $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionAlertsFilter' + required: + - group + - id + - params + additionalProperties: false + BulkActionEditPayloadRuleActions: type: object properties: @@ -326,28 +311,11 @@ components: type: object properties: throttle: - $ref: '#/components/schemas/RuleActionThrottle' + $ref: '#/components/schemas/ThrottleForBulkActions' actions: type: array items: - type: object - properties: - group: - type: string - description: Action group - id: - type: string - description: Action ID - params: - type: object - description: Action parameters - frequency: - $ref: '#/components/schemas/RuleActionFrequency' - description: Action frequency - required: - - group - - id - - params + $ref: '#/components/schemas/NormalizedRuleAction' required: - actions required: @@ -366,12 +334,19 @@ components: interval: type: string description: Interval in which the rule is executed + pattern: '^[1-9]\d*[smh]$' # any number except zero followed by one of the suffixes 's', 'm', 'h' + example: '1h' lookback: type: string description: Lookback time for the rule + pattern: '^[1-9]\d*[smh]$' # any number except zero followed by one of the suffixes 's', 'm', 'h' + example: '1h' required: - interval - lookback + required: + - type + - value BulkActionEditPayloadIndexPatterns: type: object @@ -441,22 +416,13 @@ components: properties: action: type: string - x-type: literal enum: [edit] edit: type: array description: Array of objects containing the edit operations items: $ref: '#/components/schemas/BulkActionEditPayload' + minItems: 1 required: - action - - rule - - PerformBulkActionRequest: - oneOf: - - $ref: '#/components/schemas/BulkDeleteRules' - - $ref: '#/components/schemas/BulkDisableRules' - - $ref: '#/components/schemas/BulkEnableRules' - - $ref: '#/components/schemas/BulkExportRules' - - $ref: '#/components/schemas/BulkDuplicateRules' - - $ref: '#/components/schemas/BulkEditRules' + - edit diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.test.ts index 70ae548674332..ff5289f79d98d 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.test.ts @@ -5,19 +5,12 @@ * 2.0. */ -import { left } from 'fp-ts/lib/Either'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; +import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers'; import { + BulkActionEditTypeEnum, + BulkActionTypeEnum, PerformBulkActionRequestBody, - BulkActionType, - BulkActionEditType, -} from './bulk_actions_route'; - -const retrieveValidationMessage = (payload: unknown) => { - const decoded = PerformBulkActionRequestBody.decode(payload); - const checked = exactCheck(payload, decoded); - return foldLeftRight(checked); -}; +} from './bulk_actions_route.gen'; describe('Perform bulk action request schema', () => { describe('cases common to every bulk action', () => { @@ -25,62 +18,64 @@ describe('Perform bulk action request schema', () => { test('valid request: missing query', () => { const payload: PerformBulkActionRequestBody = { query: undefined, - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('invalid request: missing action', () => { const payload: Omit = { query: 'name: test', }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "action"', - 'Invalid value "undefined" supplied to "edit"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 2 more"` + ); }); test('invalid request: unknown action', () => { const payload: Omit & { action: 'unknown' } = { - query: 'name: test', action: 'unknown', }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "unknown" supplied to "action"', - 'Invalid value "undefined" supplied to "edit"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 2 more"` + ); }); - test('invalid request: unknown property', () => { + test('strips unknown properties', () => { const payload = { query: 'name: test', - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, mock: ['id'], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "mock,["id"]"']); - expect(message.schema).toEqual({}); + expect(result.data).toEqual({ + query: 'name: test', + action: BulkActionTypeEnum.enable, + }); }); test('invalid request: wrong type for ids', () => { const payload = { ids: 'mock', - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "mock" supplied to "ids"']); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"ids: Expected array, received string, action: Invalid literal value, expected \\"delete\\", ids: Expected array, received string, action: Invalid literal value, expected \\"disable\\", ids: Expected array, received string, and 7 more"` + ); }); }); @@ -88,11 +83,11 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -100,11 +95,11 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.disable, + action: BulkActionTypeEnum.disable, }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -112,11 +107,11 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.export, + action: BulkActionTypeEnum.export, }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -124,11 +119,11 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.delete, + action: BulkActionTypeEnum.delete, }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -136,15 +131,15 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.duplicate, - [BulkActionType.duplicate]: { + action: BulkActionTypeEnum.duplicate, + [BulkActionTypeEnum.duplicate]: { include_exceptions: false, include_expired_exceptions: false, }, }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -153,47 +148,30 @@ describe('Perform bulk action request schema', () => { test('invalid request: missing edit payload', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - }; - - const message = retrieveValidationMessage(payload); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "undefined" supplied to "edit"', - ]); - expect(message.schema).toEqual({}); - }); - - test('invalid request: specified edit payload for another action', () => { - const payload = { - query: 'name: test', - action: BulkActionType.enable, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: ['test-tag'] }], + action: BulkActionTypeEnum.edit, }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'invalid keys "edit,[{"type":"set_tags","value":["test-tag"]}]"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 1 more"` + ); }); test('invalid request: wrong type for edit payload', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: { type: BulkActionEditType.set_tags, value: ['test-tag'] }, + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: { type: BulkActionEditTypeEnum.set_tags, value: ['test-tag'] }, }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "{"type":"set_tags","value":["test-tag"]}" supplied to "edit"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 1 more"` + ); }); }); @@ -201,57 +179,61 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong tags type', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: 'test-tag' }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.set_tags, value: 'test-tag' }], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "test-tag" supplied to "edit,value"', - 'Invalid value "set_tags" supplied to "edit,type"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 9 more"` + ); }); test('valid request: add_tags edit action', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.add_tags, value: ['test-tag'] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.add_tags, value: ['test-tag'] }, + ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('valid request: set_tags edit action', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: ['test-tag'] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.set_tags, value: ['test-tag'] }, + ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('valid request: delete_tags edit action', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.delete_tags, value: ['test-tag'] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.delete_tags, value: ['test-tag'] }, + ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -259,63 +241,61 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong index_patterns type', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: 'logs-*' }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.set_tags, value: 'logs-*' }], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "logs-*" supplied to "edit,value"', - 'Invalid value "set_tags" supplied to "edit,type"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 9 more"` + ); }); test('valid request: set_index_patterns edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ - { type: BulkActionEditType.set_index_patterns, value: ['logs-*'] }, + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.set_index_patterns, value: ['logs-*'] }, ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('valid request: add_index_patterns edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ - { type: BulkActionEditType.add_index_patterns, value: ['logs-*'] }, + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.add_index_patterns, value: ['logs-*'] }, ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('valid request: delete_index_patterns edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ - { type: BulkActionEditType.delete_index_patterns, value: ['logs-*'] }, + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.delete_index_patterns, value: ['logs-*'] }, ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -323,27 +303,25 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong timeline payload type', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_timeline, value: [] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.set_timeline, value: [] }], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "set_timeline" supplied to "edit,type"', - 'Invalid value "[]" supplied to "edit,value"', - ]); - expect(message.schema).toEqual({}); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 7 more"` + ); }); test('invalid request: missing timeline_id', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_title: 'Test timeline title', }, @@ -351,24 +329,21 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining([ - 'Invalid value "{"timeline_title":"Test timeline title"}" supplied to "edit,value"', - 'Invalid value "undefined" supplied to "edit,value,timeline_id"', - ]) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 10 more"` ); - expect(message.schema).toEqual({}); }); test('valid request: set_timeline edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: 'timelineid', timeline_title: 'Test timeline title', @@ -377,10 +352,10 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -388,27 +363,25 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong schedules payload type', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_schedule, value: [] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.set_schedule, value: [] }], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "set_schedule" supplied to "edit,type"', - 'Invalid value "[]" supplied to "edit,value"', - ]); - expect(message.schema).toEqual({}); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 7 more"` + ); }); test('invalid request: wrong type of payload data', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: '-10m', lookback: '1m', @@ -417,25 +390,21 @@ describe('Perform bulk action request schema', () => { ], } as PerformBulkActionRequestBody; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "{"interval":"-10m","lookback":"1m"}" supplied to "edit,value"', - 'Invalid value "-10m" supplied to "edit,value,interval"', - ]) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"edit.0.value.interval: Invalid"` ); - expect(message.schema).toEqual({}); }); test('invalid request: missing interval', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { lookback: '1m', }, @@ -443,25 +412,21 @@ describe('Perform bulk action request schema', () => { ], } as PerformBulkActionRequestBody; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "{"lookback":"1m"}" supplied to "edit,value"', - 'Invalid value "undefined" supplied to "edit,value,interval"', - ]) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 10 more"` ); - expect(message.schema).toEqual({}); }); test('invalid request: missing lookback', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: '1m', }, @@ -469,25 +434,21 @@ describe('Perform bulk action request schema', () => { ], } as PerformBulkActionRequestBody; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "{"interval":"1m"}" supplied to "edit,value"', - 'Invalid value "undefined" supplied to "edit,value,lookback"', - ]) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 10 more"` ); - expect(message.schema).toEqual({}); }); test('valid request: set_schedule edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: '1m', lookback: '1m', @@ -496,10 +457,10 @@ describe('Perform bulk action request schema', () => { ], } as PerformBulkActionRequestBody; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -507,25 +468,25 @@ describe('Perform bulk action request schema', () => { test('invalid request: invalid rule actions payload', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.add_rule_actions, value: [] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.add_rule_actions, value: [] }], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining(['Invalid value "[]" supplied to "edit,value"']) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 7 more"` ); - expect(message.schema).toEqual({}); }); test('invalid request: missing actions in payload', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', }, @@ -533,21 +494,21 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining(['Invalid value "undefined" supplied to "edit,value,actions"']) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 11 more"` ); - expect(message.schema).toEqual({}); }); test('invalid request: invalid action_type_id property in actions array', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [ @@ -567,20 +528,20 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining(['invalid keys "action_type_id"']) + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"edit.0.value.actions.0: Unrecognized key(s) in object: 'action_type_id'"` ); - expect(message.schema).toEqual({}); }); test('valid request: add_rule_actions edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [ @@ -599,19 +560,19 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('valid request: set_rule_actions edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [ @@ -632,10 +593,10 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.ts deleted file mode 100644 index 768626d08769d..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.ts +++ /dev/null @@ -1,269 +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 * as t from 'io-ts'; - -import { NonEmptyArray, TimeDuration } from '@kbn/securitysolution-io-ts-types'; -import { - RuleActionAlertsFilter, - RuleActionFrequency, - RuleActionGroup, - RuleActionId, - RuleActionParams, -} from '@kbn/securitysolution-io-ts-alerting-types'; - -import type { BulkActionSkipResult } from '@kbn/alerting-plugin/common'; -import type { RuleResponse } from '../../model'; -import type { BulkActionsDryRunErrCode } from '../../../../constants'; - -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { - IndexPatternArray, - RuleQuery, - RuleTagArray, - TimelineTemplateId, - TimelineTemplateTitle, -} from '../../model/rule_schema_legacy'; - -export enum BulkActionType { - 'enable' = 'enable', - 'disable' = 'disable', - 'export' = 'export', - 'delete' = 'delete', - 'duplicate' = 'duplicate', - 'edit' = 'edit', -} - -export enum BulkActionEditType { - 'add_tags' = 'add_tags', - 'delete_tags' = 'delete_tags', - 'set_tags' = 'set_tags', - 'add_index_patterns' = 'add_index_patterns', - 'delete_index_patterns' = 'delete_index_patterns', - 'set_index_patterns' = 'set_index_patterns', - 'set_timeline' = 'set_timeline', - 'add_rule_actions' = 'add_rule_actions', - 'set_rule_actions' = 'set_rule_actions', - 'set_schedule' = 'set_schedule', -} - -export type ThrottleForBulkActions = t.TypeOf; -export const ThrottleForBulkActions = t.union([ - t.literal('rule'), - t.literal('1h'), - t.literal('1d'), - t.literal('7d'), -]); - -type BulkActionEditPayloadTags = t.TypeOf; -const BulkActionEditPayloadTags = t.type({ - type: t.union([ - t.literal(BulkActionEditType.add_tags), - t.literal(BulkActionEditType.delete_tags), - t.literal(BulkActionEditType.set_tags), - ]), - value: RuleTagArray, -}); - -export type BulkActionEditPayloadIndexPatterns = t.TypeOf< - typeof BulkActionEditPayloadIndexPatterns ->; -const BulkActionEditPayloadIndexPatterns = t.intersection([ - t.type({ - type: t.union([ - t.literal(BulkActionEditType.add_index_patterns), - t.literal(BulkActionEditType.delete_index_patterns), - t.literal(BulkActionEditType.set_index_patterns), - ]), - value: IndexPatternArray, - }), - t.exact(t.partial({ overwrite_data_views: t.boolean })), -]); - -type BulkActionEditPayloadTimeline = t.TypeOf; -const BulkActionEditPayloadTimeline = t.type({ - type: t.literal(BulkActionEditType.set_timeline), - value: t.type({ - timeline_id: TimelineTemplateId, - timeline_title: TimelineTemplateTitle, - }), -}); - -/** - * per rulesClient.bulkEdit rules actions operation contract (x-pack/plugins/alerting/server/rules_client/rules_client.ts) - * normalized rule action object is expected (NormalizedAlertAction) as value for the edit operation - */ -export type NormalizedRuleAction = t.TypeOf; -export const NormalizedRuleAction = t.exact( - t.intersection([ - t.type({ - group: RuleActionGroup, - id: RuleActionId, - params: RuleActionParams, - }), - t.partial({ frequency: RuleActionFrequency }), - t.partial({ alerts_filter: RuleActionAlertsFilter }), - ]) -); - -export type BulkActionEditPayloadRuleActions = t.TypeOf; -export const BulkActionEditPayloadRuleActions = t.type({ - type: t.union([ - t.literal(BulkActionEditType.add_rule_actions), - t.literal(BulkActionEditType.set_rule_actions), - ]), - value: t.intersection([ - t.partial({ throttle: ThrottleForBulkActions }), - t.type({ - actions: t.array(NormalizedRuleAction), - }), - ]), -}); - -type BulkActionEditPayloadSchedule = t.TypeOf; -const BulkActionEditPayloadSchedule = t.type({ - type: t.literal(BulkActionEditType.set_schedule), - value: t.type({ - interval: TimeDuration({ allowedUnits: ['s', 'm', 'h'] }), - lookback: TimeDuration({ allowedUnits: ['s', 'm', 'h'] }), - }), -}); - -export type BulkActionEditPayload = t.TypeOf; -export const BulkActionEditPayload = t.union([ - BulkActionEditPayloadTags, - BulkActionEditPayloadIndexPatterns, - BulkActionEditPayloadTimeline, - BulkActionEditPayloadRuleActions, - BulkActionEditPayloadSchedule, -]); - -const bulkActionDuplicatePayload = t.exact( - t.type({ - include_exceptions: t.boolean, - include_expired_exceptions: t.boolean, - }) -); - -export type BulkActionDuplicatePayload = t.TypeOf; - -/** - * actions that modify rules attributes - */ -export type BulkActionEditForRuleAttributes = - | BulkActionEditPayloadTags - | BulkActionEditPayloadRuleActions - | BulkActionEditPayloadSchedule; - -/** - * actions that modify rules params - */ -export type BulkActionEditForRuleParams = - | BulkActionEditPayloadIndexPatterns - | BulkActionEditPayloadTimeline - | BulkActionEditPayloadSchedule; - -/** - * Request body parameters of the API route. - */ -export type PerformBulkActionRequestBody = t.TypeOf; -export const PerformBulkActionRequestBody = t.intersection([ - t.exact( - t.type({ - query: t.union([RuleQuery, t.undefined]), - }) - ), - t.exact(t.partial({ ids: NonEmptyArray(t.string) })), - t.union([ - t.exact( - t.type({ - action: t.union([ - t.literal(BulkActionType.delete), - t.literal(BulkActionType.disable), - t.literal(BulkActionType.enable), - t.literal(BulkActionType.export), - ]), - }) - ), - t.intersection([ - t.exact( - t.type({ - action: t.literal(BulkActionType.duplicate), - }) - ), - t.exact( - t.partial({ - [BulkActionType.duplicate]: bulkActionDuplicatePayload, - }) - ), - ]), - t.exact( - t.type({ - action: t.literal(BulkActionType.edit), - [BulkActionType.edit]: NonEmptyArray(BulkActionEditPayload), - }) - ), - ]), -]); - -/** - * Query string parameters of the API route. - */ -export type PerformBulkActionRequestQuery = t.TypeOf; -export const PerformBulkActionRequestQuery = t.exact( - t.partial({ - dry_run: t.union([t.literal('true'), t.literal('false')]), - }) -); - -export interface RuleDetailsInError { - id: string; - name?: string; -} -export interface NormalizedRuleError { - message: string; - status_code: number; - err_code?: BulkActionsDryRunErrCode; - rules: RuleDetailsInError[]; -} -export interface BulkEditActionResults { - updated: RuleResponse[]; - created: RuleResponse[]; - deleted: RuleResponse[]; - skipped: BulkActionSkipResult[]; -} - -export interface BulkEditActionSummary { - failed: number; - skipped: number; - succeeded: number; - total: number; -} -export interface BulkEditActionSuccessResponse { - success: boolean; - rules_count: number; - attributes: { - results: BulkEditActionResults; - summary: BulkEditActionSummary; - }; -} -export interface BulkEditActionErrorResponse { - status_code: number; - message: string; - attributes: { - results: BulkEditActionResults; - summary: BulkEditActionSummary; - errors?: NormalizedRuleError[]; - }; -} - -export type BulkEditActionResponse = BulkEditActionSuccessResponse | BulkEditActionErrorResponse; - -export type BulkExportActionResponse = string; - -export type PerformBulkActionResponse = BulkEditActionResponse | BulkExportActionResponse; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_types.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_types.ts new file mode 100644 index 0000000000000..6e57e5abe2410 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_types.ts @@ -0,0 +1,30 @@ +/* + * 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 { + BulkActionEditPayloadIndexPatterns, + BulkActionEditPayloadRuleActions, + BulkActionEditPayloadSchedule, + BulkActionEditPayloadTags, + BulkActionEditPayloadTimeline, +} from './bulk_actions_route.gen'; + +/** + * actions that modify rules attributes + */ +export type BulkActionEditForRuleAttributes = + | BulkActionEditPayloadTags + | BulkActionEditPayloadRuleActions + | BulkActionEditPayloadSchedule; + +/** + * actions that modify rules params + */ +export type BulkActionEditForRuleParams = + | BulkActionEditPayloadIndexPatterns + | BulkActionEditPayloadTimeline + | BulkActionEditPayloadSchedule; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.test.ts index 40955f2eba40a..2e6cff31f8f7d 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.test.ts @@ -26,7 +26,9 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.name: Required, 0.description: Required, 0.risk_score: Required, 0.severity: Required, 0.type: Invalid literal value, expected \\"eql\\", and 52 more"` + ); }); test('single array element does validate', () => { @@ -56,7 +58,9 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"` + ); }); test('two array elements where the first is valid but the second is invalid (risk_score) will not validate', () => { @@ -68,7 +72,9 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"1.risk_score: Required, 1.type: Invalid literal value, expected \\"eql\\", 1.language: Invalid literal value, expected \\"eql\\", 1.risk_score: Required, 1.risk_score: Required, and 22 more"` + ); }); test('two array elements where the first is invalid (risk_score) but the second is valid will not validate', () => { @@ -80,7 +86,9 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"` + ); }); test('two array elements where both are invalid (risk_score) will not validate', () => { @@ -95,7 +103,7 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toMatchInlineSnapshot( - `"0: Invalid input, 1: Invalid input"` + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 49 more"` ); }); @@ -121,7 +129,9 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', and 22 more"` + ); }); test('You can set "note" to a string', () => { @@ -154,6 +164,8 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.note: Expected string, received object, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.note: Expected string, received object, 0.note: Expected string, received object, and 22 more"` + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.test.ts index 443a3e0862b45..d5325ad5ed13f 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.test.ts @@ -70,6 +70,8 @@ describe('Bulk patch rules request schema', () => { const result = BulkPatchRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"1.note: Expected string, received object, 1.note: Expected string, received object, 1.note: Expected string, received object, 1.note: Expected string, received object, 1.note: Expected string, received object, and 3 more"` + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.test.ts index 86a3a943b6626..3fa69c6ad24dc 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.test.ts @@ -27,7 +27,9 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.name: Required, 0.description: Required, 0.risk_score: Required, 0.severity: Required, 0.type: Invalid literal value, expected \\"eql\\", and 52 more"` + ); }); test('single array element does validate', () => { @@ -57,7 +59,9 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"` + ); }); test('two array elements where the first is valid but the second is invalid (risk_score) will not validate', () => { @@ -69,7 +73,9 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"1.risk_score: Required, 1.type: Invalid literal value, expected \\"eql\\", 1.language: Invalid literal value, expected \\"eql\\", 1.risk_score: Required, 1.risk_score: Required, and 22 more"` + ); }); test('two array elements where the first is invalid (risk_score) but the second is valid will not validate', () => { @@ -81,7 +87,9 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"` + ); }); test('two array elements where both are invalid (risk_score) will not validate', () => { @@ -96,7 +104,7 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toMatchInlineSnapshot( - `"0: Invalid input, 1: Invalid input"` + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 49 more"` ); }); @@ -122,7 +130,9 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', and 22 more"` + ); }); test('You can set "namespace" to a string', () => { @@ -165,6 +175,8 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.note: Expected string, received object, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.note: Expected string, received object, 0.note: Expected string, received object, and 22 more"` + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts index d8e3c997e2279..413d83f9fee01 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts @@ -45,7 +45,9 @@ describe('Bulk CRUD rules response schema', () => { const result = BulkCrudRulesResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.error: Required, 0: Unrecognized key(s) in object: 'author', 'created_at', 'updated_at', 'created_by', 'description', 'enabled', 'false_positives', 'from', 'immutable', 'references', 'revision', 'severity', 'severity_mapping', 'updated_by', 'tags', 'to', 'threat', 'version', 'output_index', 'max_signals', 'risk_score', 'risk_score_mapping', 'interval', 'exceptions_list', 'related_integrations', 'required_fields', 'setup', 'throttle', 'actions', 'building_block_type', 'note', 'license', 'outcome', 'alias_target_id', 'alias_purpose', 'timeline_id', 'timeline_title', 'meta', 'rule_name_override', 'timestamp_override', 'timestamp_override_fallback_disabled', 'namespace', 'investigation_fields', 'query', 'type', 'language', 'index', 'data_view_id', 'filters', 'saved_id', 'response_actions', 'alert_suppression', 0.name: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", and 24 more"` + ); }); test('it should NOT validate an invalid error message with a deleted value', () => { @@ -56,7 +58,9 @@ describe('Bulk CRUD rules response schema', () => { const result = BulkCrudRulesResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.error: Required, 0.name: Required, 0.description: Required, 0.risk_score: Required, 0.severity: Required, and 267 more"` + ); }); test('it should omit any extra rule props', () => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.test.ts index b70b5a6a7d908..1994b3de5d453 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.test.ts @@ -373,7 +373,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"references.0: Expected string, received number, references.0: Expected string, received number, references.0: Expected string, received number, references.0: Expected string, received number, references.0: Expected string, received number, and 3 more"` + ); }); test('indexes cannot be numbers', () => { @@ -385,7 +387,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", index.0: Expected string, received number, index.0: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", index.0: Expected string, received number, and 8 more"` + ); }); test('saved_id is not required when type is saved_query and will validate without it', () => { @@ -456,7 +460,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', type: Invalid literal value, expected \\"saved_query\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', and 9 more"` + ); }); test('max_signals cannot be negative', () => { @@ -518,7 +524,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"meta: Expected object, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", meta: Expected object, received string, meta: Expected object, received string, and 12 more"` + ); }); test('filters cannot be a string', () => { @@ -529,7 +537,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", filters: Expected array, received string, filters: Expected array, received string, type: Invalid literal value, expected \\"saved_query\\", and 10 more"` + ); }); test('name cannot be an empty string', () => { @@ -631,7 +641,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"threat.0.framework: Required, threat.0.framework: Required, threat.0.framework: Required, threat.0.framework: Required, threat.0.framework: Required, and 3 more"` + ); }); test('threat is invalid when updated with missing tactic sub-object', () => { @@ -655,7 +667,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"threat.0.tactic: Required, threat.0.tactic: Required, threat.0.tactic: Required, threat.0.tactic: Required, threat.0.tactic: Required, and 3 more"` + ); }); test('threat is valid when updated with missing technique', () => { @@ -700,7 +714,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', and 3 more"` + ); }); describe('note', () => { @@ -744,7 +760,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"note: Expected string, received object, note: Expected string, received object, note: Expected string, received object, note: Expected string, received object, note: Expected string, received object, and 3 more"` + ); }); }); @@ -756,7 +774,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.group: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.group: Required, actions.0.group: Required, and 12 more"` + ); }); test('You cannot send in an array of actions that are missing "id"', () => { @@ -767,7 +787,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.id: Required, actions.0.id: Required, and 12 more"` + ); }); test('You cannot send in an array of actions that are missing "params"', () => { @@ -778,7 +800,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.params: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.params: Required, actions.0.params: Required, and 12 more"` + ); }); test('You cannot send in an array of actions that are including "actionTypeId"', () => { @@ -796,7 +820,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.action_type_id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.action_type_id: Required, actions.0.action_type_id: Required, and 12 more"` + ); }); describe('exception_list', () => { @@ -862,7 +888,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"exceptions_list.0.list_id: Required, exceptions_list.0.type: Required, exceptions_list.0.namespace_type: Invalid enum value. Expected 'agnostic' | 'single', received 'not a namespace type', type: Invalid literal value, expected \\"eql\\", exceptions_list.0.list_id: Required, and 26 more"` + ); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.gen.ts index 01cd91216753f..2dfb54396c9ce 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.gen.ts @@ -6,6 +6,7 @@ */ import { z } from 'zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; /* * NOTICE: Do not edit this file manually. @@ -19,13 +20,7 @@ export const ExportRulesRequestQuery = z.object({ /** * Determines whether a summary of the exported rules is returned. */ - exclude_export_details: z.preprocess( - (value: unknown) => (typeof value === 'boolean' ? String(value) : value), - z - .enum(['true', 'false']) - .default('false') - .transform((value) => value === 'true') - ), + exclude_export_details: BooleanFromString.optional().default(false), /** * File name for saving the exported rules. */ diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.test.ts index 49b56c6673218..783556d2076af 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.test.ts @@ -120,7 +120,7 @@ describe('Export rules request schema', () => { const result = ExportRulesRequestQuery.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toEqual( - `exclude_export_details: Invalid enum value. Expected 'true' | 'false', received 'invalid string'` + `exclude_export_details: Invalid enum value. Expected 'true' | 'false', received 'invalid string', exclude_export_details: Expected boolean, received string` ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.gen.ts new file mode 100644 index 0000000000000..a515cf70f9513 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.gen.ts @@ -0,0 +1,71 @@ +/* + * 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 { z } from 'zod'; +import { ArrayFromString } from '@kbn/zod-helpers'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +import { SortOrder } from '../../model/sorting.gen'; +import { RuleResponse } from '../../model/rule_schema/rule_schemas.gen'; + +export type FindRulesSortField = z.infer; +export const FindRulesSortField = z.enum([ + 'created_at', + 'createdAt', + 'enabled', + 'execution_summary.last_execution.date', + 'execution_summary.last_execution.metrics.execution_gap_duration_s', + 'execution_summary.last_execution.metrics.total_indexing_duration_ms', + 'execution_summary.last_execution.metrics.total_search_duration_ms', + 'execution_summary.last_execution.status', + 'name', + 'risk_score', + 'riskScore', + 'severity', + 'updated_at', + 'updatedAt', +]); +export type FindRulesSortFieldEnum = typeof FindRulesSortField.enum; +export const FindRulesSortFieldEnum = FindRulesSortField.enum; + +export type FindRulesRequestQuery = z.infer; +export const FindRulesRequestQuery = z.object({ + fields: ArrayFromString(z.string()).optional(), + /** + * Search query + */ + filter: z.string().optional(), + /** + * Field to sort by + */ + sort_field: FindRulesSortField.optional(), + /** + * Sort order + */ + sort_order: SortOrder.optional(), + /** + * Page number + */ + page: z.coerce.number().int().min(1).optional().default(1), + /** + * Rules per page + */ + per_page: z.coerce.number().int().min(0).optional().default(20), +}); +export type FindRulesRequestQueryInput = z.input; + +export type FindRulesResponse = z.infer; +export const FindRulesResponse = z.object({ + page: z.number().int(), + perPage: z.number().int(), + total: z.number().int(), + data: z.array(RuleResponse), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.schema.yaml new file mode 100644 index 0000000000000..4fa1c14542ed0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.schema.yaml @@ -0,0 +1,98 @@ +openapi: 3.0.0 +info: + title: Find Rules API endpoint + version: 2023-10-31 +paths: + /api/detection_engine/rules/_find: + get: + operationId: FindRules + x-codegen-enabled: true + description: Finds rules that match the given query. + tags: + - Rules API + parameters: + - name: 'fields' + in: query + required: false + schema: + type: array + items: + type: string + - name: 'filter' + in: query + description: Search query + required: false + schema: + type: string + - name: 'sort_field' + in: query + description: Field to sort by + required: false + schema: + $ref: '#/components/schemas/FindRulesSortField' + - name: 'sort_order' + in: query + description: Sort order + required: false + schema: + $ref: '../../model/sorting.schema.yaml#/components/schemas/SortOrder' + - name: 'page' + in: query + description: Page number + required: false + schema: + type: integer + minimum: 1 + default: 1 + - name: 'per_page' + in: query + description: Rules per page + required: false + schema: + type: integer + minimum: 0 + default: 20 + + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + page: + type: integer + perPage: + type: integer + total: + type: integer + data: + type: array + items: + $ref: '../../model/rule_schema/rule_schemas.schema.yaml#/components/schemas/RuleResponse' + required: + - page + - perPage + - total + - data + +components: + schemas: + FindRulesSortField: + type: string + enum: + - 'created_at' + - 'createdAt' # Legacy notation, keeping for backwards compatibility + - 'enabled' + - 'execution_summary.last_execution.date' + - 'execution_summary.last_execution.metrics.execution_gap_duration_s' + - 'execution_summary.last_execution.metrics.total_indexing_duration_ms' + - 'execution_summary.last_execution.metrics.total_search_duration_ms' + - 'execution_summary.last_execution.status' + - 'name' + - 'risk_score' + - 'riskScore' # Legacy notation, keeping for backwards compatibility + - 'severity' + - 'updated_at' + - 'updatedAt' # Legacy notation, keeping for backwards compatibility diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.test.ts index 391826fed9923..aa4bb53ab7481 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.test.ts @@ -5,21 +5,17 @@ * 2.0. */ -import { left } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; - -import { FindRulesRequestQuery } from './find_rules_route'; +import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers'; +import type { FindRulesRequestQueryInput } from './find_rules_route.gen'; +import { FindRulesRequestQuery } from './find_rules_route.gen'; describe('Find rules request schema', () => { test('empty objects do validate', () => { - const payload: FindRulesRequestQuery = {}; + const payload: FindRulesRequestQueryInput = {}; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual({ page: 1, per_page: 20, }); @@ -35,167 +31,126 @@ describe('Find rules request schema', () => { sort_order: 'asc', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); - test('made up parameters do not validate', () => { - const payload: Partial & { madeUp: string } = { + test('made up parameters are ignored', () => { + const payload: Partial & { madeUp: string } = { madeUp: 'invalid value', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); - expect(message.schema).toEqual({}); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual({ + page: 1, + per_page: 20, + }); }); test('per_page validates', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { per_page: 5, }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).per_page).toEqual(payload.per_page); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.per_page).toEqual(payload.per_page); }); test('page validates', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { page: 5, }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).page).toEqual(payload.page); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.page).toEqual(payload.page); }); test('sort_field validates', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { sort_field: 'name', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).sort_field).toEqual('name'); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.sort_field).toEqual(payload.sort_field); }); test('fields validates with a string', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { fields: ['some value'], }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).fields).toEqual(payload.fields); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.fields).toEqual(payload.fields); }); test('fields validates with multiple strings', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { fields: ['some value 1', 'some value 2'], }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).fields).toEqual(payload.fields); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.fields).toEqual(payload.fields); }); test('fields does not validate with a number', () => { - const payload: Omit & { fields: number } = { + const payload: Omit & { fields: number } = { fields: 5, }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "fields"']); - expect(message.schema).toEqual({}); - }); - - test('per_page has a default of 20', () => { - const payload: FindRulesRequestQuery = {}; - - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).per_page).toEqual(20); - }); - - test('page has a default of 1', () => { - const payload: FindRulesRequestQuery = {}; - - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).page).toEqual(1); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual('fields: Expected array, received number'); }); test('filter works with a string', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { filter: 'some value 1', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).filter).toEqual(payload.filter); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.filter).toEqual(payload.filter); }); test('filter does not work with a number', () => { - const payload: Omit & { filter: number } = { + const payload: Omit & { filter: number } = { filter: 5, }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "filter"']); - expect(message.schema).toEqual({}); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual('filter: Expected string, received number'); }); test('sort_order validates with desc and sort_field', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { sort_order: 'desc', sort_field: 'name', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).sort_order).toEqual(payload.sort_order); - expect((message.schema as FindRulesRequestQuery).sort_field).toEqual(payload.sort_field); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.sort_order).toEqual(payload.sort_order); + expect(result.data.sort_field).toEqual(payload.sort_field); }); test('sort_order does not validate with a string other than asc and desc', () => { - const payload: Omit & { sort_order: string } = { + const payload: Omit & { sort_order: string } = { sort_order: 'some other string', sort_field: 'name', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some other string" supplied to "sort_order"', - ]); - expect(message.schema).toEqual({}); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual( + "sort_order: Invalid enum value. Expected 'asc' | 'desc', received 'some other string'" + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.ts deleted file mode 100644 index 7e16b696bdd70..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.ts +++ /dev/null @@ -1,54 +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 * as t from 'io-ts'; -import { DefaultPerPage, DefaultPage } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { RuleResponse } from '../../model'; -import { SortOrder, queryFilter, fields } from '../../model'; - -export type FindRulesSortField = t.TypeOf; -export const FindRulesSortField = t.union([ - t.literal('created_at'), - t.literal('createdAt'), // Legacy notation, keeping for backwards compatibility - t.literal('enabled'), - t.literal('execution_summary.last_execution.date'), - t.literal('execution_summary.last_execution.metrics.execution_gap_duration_s'), - t.literal('execution_summary.last_execution.metrics.total_indexing_duration_ms'), - t.literal('execution_summary.last_execution.metrics.total_search_duration_ms'), - t.literal('execution_summary.last_execution.status'), - t.literal('name'), - t.literal('risk_score'), - t.literal('riskScore'), // Legacy notation, keeping for backwards compatibility - t.literal('severity'), - t.literal('updated_at'), - t.literal('updatedAt'), // Legacy notation, keeping for backwards compatibility -]); - -export type FindRulesSortFieldOrUndefined = t.TypeOf; -export const FindRulesSortFieldOrUndefined = t.union([FindRulesSortField, t.undefined]); - -/** - * Query string parameters of the API route. - */ -export type FindRulesRequestQuery = t.TypeOf; -export const FindRulesRequestQuery = t.exact( - t.partial({ - fields, - filter: queryFilter, - sort_field: FindRulesSortField, - sort_order: SortOrder, - page: DefaultPage, // defaults to 1 - per_page: DefaultPerPage, // defaults to 20 - }) -); - -export interface FindRulesResponse { - page: number; - perPage: number; - total: number; - data: RuleResponse[]; -} diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.test.ts index 93cded33f6d94..c9fb9ce9a3524 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.test.ts @@ -5,19 +5,19 @@ * 2.0. */ -import type { FindRulesRequestQuery } from './find_rules_route'; +import type { FindRulesRequestQueryInput } from './find_rules_route.gen'; import { validateFindRulesRequestQuery } from './request_schema_validation'; describe('Find rules request schema, additional validation', () => { describe('validateFindRulesRequestQuery', () => { test('You can have an empty sort_field and empty sort_order', () => { - const schema: FindRulesRequestQuery = {}; + const schema: FindRulesRequestQueryInput = {}; const errors = validateFindRulesRequestQuery(schema); expect(errors).toEqual([]); }); test('You can have both a sort_field and and a sort_order', () => { - const schema: FindRulesRequestQuery = { + const schema: FindRulesRequestQueryInput = { sort_field: 'name', sort_order: 'asc', }; @@ -26,7 +26,7 @@ describe('Find rules request schema, additional validation', () => { }); test('You cannot have sort_field without sort_order', () => { - const schema: FindRulesRequestQuery = { + const schema: FindRulesRequestQueryInput = { sort_field: 'name', }; const errors = validateFindRulesRequestQuery(schema); @@ -36,7 +36,7 @@ describe('Find rules request schema, additional validation', () => { }); test('You cannot have sort_order without sort_field', () => { - const schema: FindRulesRequestQuery = { + const schema: FindRulesRequestQueryInput = { sort_order: 'asc', }; const errors = validateFindRulesRequestQuery(schema); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.ts index 769ef566d1efd..69d94be334e3f 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.ts @@ -5,23 +5,16 @@ * 2.0. */ -import type { FindRulesRequestQuery } from './find_rules_route'; +import type { FindRulesRequestQueryInput } from './find_rules_route.gen'; /** * Additional validation that is implemented outside of the schema itself. */ -export const validateFindRulesRequestQuery = (query: FindRulesRequestQuery): string[] => { - return [...validateSortOrder(query)]; -}; - -const validateSortOrder = (query: FindRulesRequestQuery): string[] => { +export const validateFindRulesRequestQuery = (query: FindRulesRequestQueryInput): string[] => { if (query.sort_order != null || query.sort_field != null) { if (query.sort_order == null || query.sort_field == null) { return ['when "sort_order" and "sort_field" must exist together or not at all']; - } else { - return []; } - } else { - return []; } + return []; }; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen.ts index d0a105e28c2c8..225179a6c0489 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen.ts @@ -6,6 +6,7 @@ */ import { z } from 'zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; /* * NOTICE: Do not edit this file manually. @@ -20,43 +21,19 @@ export const ImportRulesRequestQuery = z.object({ /** * Determines whether existing rules with the same `rule_id` are overwritten. */ - overwrite: z.preprocess( - (value: unknown) => (typeof value === 'boolean' ? String(value) : value), - z - .enum(['true', 'false']) - .default('false') - .transform((value) => value === 'true') - ), + overwrite: BooleanFromString.optional().default(false), /** * Determines whether existing exception lists with the same `list_id` are overwritten. */ - overwrite_exceptions: z.preprocess( - (value: unknown) => (typeof value === 'boolean' ? String(value) : value), - z - .enum(['true', 'false']) - .default('false') - .transform((value) => value === 'true') - ), + overwrite_exceptions: BooleanFromString.optional().default(false), /** * Determines whether existing actions with the same `kibana.alert.rule.actions.id` are overwritten. */ - overwrite_action_connectors: z.preprocess( - (value: unknown) => (typeof value === 'boolean' ? String(value) : value), - z - .enum(['true', 'false']) - .default('false') - .transform((value) => value === 'true') - ), + overwrite_action_connectors: BooleanFromString.optional().default(false), /** * Generates a new list ID for each imported exception list. */ - as_new_list: z.preprocess( - (value: unknown) => (typeof value === 'boolean' ? String(value) : value), - z - .enum(['true', 'false']) - .default('false') - .transform((value) => value === 'true') - ), + as_new_list: BooleanFromString.optional().default(false), }); export type ImportRulesRequestQueryInput = z.input; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/index.ts index 7fdab0816d650..709e63a6ec402 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/index.ts @@ -5,7 +5,8 @@ * 2.0. */ -export * from './bulk_actions/bulk_actions_route'; +export * from './bulk_actions/bulk_actions_types'; +export * from './bulk_actions/bulk_actions_route.gen'; export * from './bulk_crud/bulk_create_rules/bulk_create_rules_route.gen'; export * from './bulk_crud/bulk_delete_rules/bulk_delete_rules_route.gen'; export * from './bulk_crud/bulk_patch_rules/bulk_patch_rules_route.gen'; @@ -22,7 +23,7 @@ export * from './crud/update_rule/request_schema_validation'; export * from './crud/update_rule/update_rule_route.gen'; export * from './export_rules/export_rules_details_schema'; export * from './export_rules/export_rules_route.gen'; -export * from './find_rules/find_rules_route'; +export * from './find_rules/find_rules_route.gen'; export * from './find_rules/request_schema_validation'; export * from './get_rule_management_filters/get_rule_management_filters_route'; export * from './import_rules/import_rules_route.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/index.ts index 1494e09b9c51a..ddb132ebf64bb 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/index.ts @@ -10,15 +10,15 @@ export * from './detection_engine_health/get_rule_health/get_rule_health_route'; export * from './detection_engine_health/get_space_health/get_space_health_route'; export * from './detection_engine_health/setup_health/setup_health_route'; export * from './detection_engine_health/model'; -export * from './rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route'; +export * from './rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.gen'; export * from './rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen'; export * from './urls'; -export * from './model/execution_event'; -export * from './model/execution_metrics'; +export * from './model/execution_event.gen'; +export * from './model/execution_metrics.gen'; export * from './model/execution_result.gen'; export * from './model/execution_settings'; export * from './model/execution_status.gen'; export * from './model/execution_status'; -export * from './model/execution_summary'; +export * from './model/execution_summary.gen'; export * from './model/log_level'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.gen.ts new file mode 100644 index 0000000000000..e493c5233a03d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.gen.ts @@ -0,0 +1,44 @@ +/* + * 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 { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +export type LogLevel = z.infer; +export const LogLevel = z.enum(['trace', 'debug', 'info', 'warn', 'error']); +export type LogLevelEnum = typeof LogLevel.enum; +export const LogLevelEnum = LogLevel.enum; + +/** + * Type of a plain rule execution event: +- message: Simple log message of some log level, such as debug, info or error. +- status-change: We log an event of this type each time a rule changes its status during an execution. +- execution-metrics: We log an event of this type at the end of a rule execution. It contains various execution metrics such as search and indexing durations. + */ +export type RuleExecutionEventType = z.infer; +export const RuleExecutionEventType = z.enum(['message', 'status-change', 'execution-metrics']); +export type RuleExecutionEventTypeEnum = typeof RuleExecutionEventType.enum; +export const RuleExecutionEventTypeEnum = RuleExecutionEventType.enum; + +/** + * Plain rule execution event. A rule can write many of them during each execution. Events can be of different types and log levels. + +NOTE: This is a read model of rule execution events and it is pretty generic. It contains only a subset of their fields: only those fields that are common to all types of execution events. + */ +export type RuleExecutionEvent = z.infer; +export const RuleExecutionEvent = z.object({ + timestamp: z.string().datetime(), + sequence: z.number().int(), + level: LogLevel, + type: RuleExecutionEventType, + execution_id: z.string().min(1), + message: z.string(), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts index c8efaa8dd85b8..3d987356a37c9 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts @@ -5,9 +5,8 @@ * 2.0. */ -import type { RuleExecutionEvent } from './execution_event'; -import { RuleExecutionEventType } from './execution_event'; -import { LogLevel } from './log_level'; +import type { RuleExecutionEvent } from './execution_event.gen'; +import { LogLevelEnum, RuleExecutionEventTypeEnum } from './execution_event.gen'; const DEFAULT_TIMESTAMP = '2021-12-28T10:10:00.806Z'; const DEFAULT_SEQUENCE_NUMBER = 0; @@ -17,13 +16,13 @@ const getMessageEvent = (props: Partial = {}): RuleExecution // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, - level: LogLevel.debug, + level: LogLevelEnum.debug, execution_id: 'execution-id-1', message: 'Some message', // Overridden values ...props, // Mandatory values for this type of event - type: RuleExecutionEventType.message, + type: RuleExecutionEventTypeEnum.message, }; }; @@ -37,8 +36,8 @@ const getRunningStatusChange = (props: Partial = {}): RuleEx // Overridden values ...props, // Mandatory values for this type of event - level: LogLevel.info, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.info, + type: RuleExecutionEventTypeEnum['status-change'], }; }; @@ -54,8 +53,8 @@ const getPartialFailureStatusChange = ( // Overridden values ...props, // Mandatory values for this type of event - level: LogLevel.warn, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.warn, + type: RuleExecutionEventTypeEnum['status-change'], }; }; @@ -69,8 +68,8 @@ const getFailedStatusChange = (props: Partial = {}): RuleExe // Overridden values ...props, // Mandatory values for this type of event - level: LogLevel.error, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.error, + type: RuleExecutionEventTypeEnum['status-change'], }; }; @@ -84,8 +83,8 @@ const getSucceededStatusChange = (props: Partial = {}): Rule // Overridden values ...props, // Mandatory values for this type of event - level: LogLevel.info, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.info, + type: RuleExecutionEventTypeEnum['status-change'], }; }; @@ -99,8 +98,8 @@ const getExecutionMetricsEvent = (props: Partial = {}): Rule // Overridden values ...props, // Mandatory values for this type of event - level: LogLevel.debug, - type: RuleExecutionEventType['execution-metrics'], + level: LogLevelEnum.debug, + type: RuleExecutionEventTypeEnum['execution-metrics'], }; }; @@ -120,7 +119,7 @@ const getSomeEvents = (): RuleExecutionEvent[] => [ getMessageEvent({ timestamp: '2021-12-28T10:10:06.806Z', sequence: 6, - level: LogLevel.debug, + level: LogLevelEnum.debug, message: 'Rule execution started', }), getFailedStatusChange({ @@ -138,7 +137,7 @@ const getSomeEvents = (): RuleExecutionEvent[] => [ getMessageEvent({ timestamp: '2021-12-28T10:10:02.806Z', sequence: 2, - level: LogLevel.error, + level: LogLevelEnum.error, message: 'Some error', }), getRunningStatusChange({ @@ -148,7 +147,7 @@ const getSomeEvents = (): RuleExecutionEvent[] => [ getMessageEvent({ timestamp: '2021-12-28T10:10:00.806Z', sequence: 0, - level: LogLevel.debug, + level: LogLevelEnum.debug, message: 'Rule execution started', }), ]; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.schema.yaml new file mode 100644 index 0000000000000..d49a49d222401 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.schema.yaml @@ -0,0 +1,49 @@ +openapi: '3.0.0' +info: + title: Execution Event Schema + version: 'not applicable' +paths: {} +components: + x-codegen-enabled: true + schemas: + LogLevel: + type: string + enum: ['trace', 'debug', 'info', 'warn', 'error'] + + RuleExecutionEventType: + type: string + enum: ['message', 'status-change', 'execution-metrics'] + description: |- + Type of a plain rule execution event: + - message: Simple log message of some log level, such as debug, info or error. + - status-change: We log an event of this type each time a rule changes its status during an execution. + - execution-metrics: We log an event of this type at the end of a rule execution. It contains various execution metrics such as search and indexing durations. + + RuleExecutionEvent: + type: object + properties: + timestamp: + type: string + format: date-time + sequence: + type: integer + level: + $ref: '#/components/schemas/LogLevel' + type: + $ref: '#/components/schemas/RuleExecutionEventType' + execution_id: + type: string + minLength: 1 + message: + type: string + required: + - timestamp + - sequence + - level + - type + - execution_id + - message + description: |- + Plain rule execution event. A rule can write many of them during each execution. Events can be of different types and log levels. + + NOTE: This is a read model of rule execution events and it is pretty generic. It contains only a subset of their fields: only those fields that are common to all types of execution events. diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts deleted file mode 100644 index 64acfb01e2e2a..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts +++ /dev/null @@ -1,61 +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 * as t from 'io-ts'; -import { enumeration, IsoDateString, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; -import { enumFromString } from '../../../../utils/enum_from_string'; -import { TLogLevel } from './log_level'; - -/** - * Type of a plain rule execution event. - */ -export enum RuleExecutionEventType { - /** - * Simple log message of some log level, such as debug, info or error. - */ - 'message' = 'message', - - /** - * We log an event of this type each time a rule changes its status during an execution. - */ - 'status-change' = 'status-change', - - /** - * We log an event of this type at the end of a rule execution. It contains various execution - * metrics such as search and indexing durations. - */ - 'execution-metrics' = 'execution-metrics', -} - -export const TRuleExecutionEventType = enumeration( - 'RuleExecutionEventType', - RuleExecutionEventType -); - -/** - * An array of supported types of rule execution events. - */ -export const RULE_EXECUTION_EVENT_TYPES = Object.values(RuleExecutionEventType); - -export const ruleExecutionEventTypeFromString = enumFromString(RuleExecutionEventType); - -/** - * Plain rule execution event. A rule can write many of them during each execution. Events can be - * of different types and log levels. - * - * NOTE: This is a read model of rule execution events and it is pretty generic. It contains only a - * subset of their fields: only those fields that are common to all types of execution events. - */ -export type RuleExecutionEvent = t.TypeOf; -export const RuleExecutionEvent = t.type({ - timestamp: IsoDateString, - sequence: t.number, - level: TLogLevel, - type: TRuleExecutionEventType, - execution_id: NonEmptyString, - message: t.string, -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.gen.ts index 235437cc5ed68..67aac49310a7d 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.gen.ts @@ -15,16 +15,19 @@ import { z } from 'zod'; export type RuleExecutionMetrics = z.infer; export const RuleExecutionMetrics = z.object({ /** - * Total time spent searching for events + * Total time spent performing ES searches as measured by Kibana; includes network latency and time spent serializing/deserializing request/response */ total_search_duration_ms: z.number().int().min(0).optional(), /** - * Total time spent indexing alerts + * Total time spent indexing documents during current rule execution cycle */ total_indexing_duration_ms: z.number().int().min(0).optional(), + /** + * Total time spent enriching documents during current rule execution cycle + */ total_enrichment_duration_ms: z.number().int().min(0).optional(), /** - * Time gap between last execution and current execution + * Duration in seconds of execution gap */ execution_gap_duration_s: z.number().int().min(0).optional(), }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.schema.yaml index 7e04ef38a0a87..985da08e1df88 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.schema.yaml @@ -10,17 +10,18 @@ components: type: object properties: total_search_duration_ms: - description: Total time spent searching for events + description: Total time spent performing ES searches as measured by Kibana; includes network latency and time spent serializing/deserializing request/response type: integer minimum: 0 total_indexing_duration_ms: - description: Total time spent indexing alerts + description: Total time spent indexing documents during current rule execution cycle type: integer minimum: 0 total_enrichment_duration_ms: + description: Total time spent enriching documents during current rule execution cycle type: integer minimum: 0 execution_gap_duration_s: - description: Time gap between last execution and current execution + description: Duration in seconds of execution gap type: integer minimum: 0 diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.ts deleted file mode 100644 index b15c76119e441..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.ts +++ /dev/null @@ -1,28 +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 * as t from 'io-ts'; -import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; - -export type DurationMetric = t.TypeOf; -export const DurationMetric = PositiveInteger; - -export type RuleExecutionMetrics = t.TypeOf; - -/** - @property total_search_duration_ms - "total time spent performing ES searches as measured by Kibana; - includes network latency and time spent serializing/deserializing request/response", - @property total_indexing_duration_ms - "total time spent indexing documents during current rule execution cycle", - @property total_enrichment_duration_ms - total time spent enriching documents during current rule execution cycle - @property execution_gap_duration_s - "duration in seconds of execution gap" -*/ -export const RuleExecutionMetrics = t.partial({ - total_search_duration_ms: DurationMetric, - total_indexing_duration_ms: DurationMetric, - total_enrichment_duration_ms: DurationMetric, - execution_gap_duration_s: DurationMetric, -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_status.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_status.ts index ae031191fd74d..903912b39cbb2 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_status.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_status.ts @@ -6,14 +6,10 @@ */ import type { RuleLastRunOutcomes } from '@kbn/alerting-plugin/common'; -import { enumeration } from '@kbn/securitysolution-io-ts-types'; import { assertUnreachable } from '../../../../utility_types'; import type { RuleExecutionStatus, RuleExecutionStatusOrder } from './execution_status.gen'; import { RuleExecutionStatusEnum } from './execution_status.gen'; -// TODO remove after the migration to Zod is done -export const TRuleExecutionStatus = enumeration('RuleExecutionStatus', RuleExecutionStatusEnum); - export const ruleExecutionStatusToNumber = ( status: RuleExecutionStatus ): RuleExecutionStatusOrder => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.mock.ts index 59482e759f902..5ffc034edd172 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.mock.ts @@ -6,7 +6,7 @@ */ import { RuleExecutionStatusEnum } from './execution_status.gen'; -import type { RuleExecutionSummary } from './execution_summary'; +import type { RuleExecutionSummary } from './execution_summary.gen'; const getSummarySucceeded = (): RuleExecutionSummary => ({ last_execution: { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.ts deleted file mode 100644 index a747d2f021b7c..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.ts +++ /dev/null @@ -1,22 +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 { IsoDateString } from '@kbn/securitysolution-io-ts-types'; -import * as t from 'io-ts'; -import { RuleExecutionMetrics } from './execution_metrics'; -import { TRuleExecutionStatus } from './execution_status'; - -export type RuleExecutionSummary = t.TypeOf; -export const RuleExecutionSummary = t.type({ - last_execution: t.type({ - date: IsoDateString, - status: TRuleExecutionStatus, - status_order: t.number, - message: t.string, - metrics: RuleExecutionMetrics, - }), -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/index.ts index b4f003cf48228..7fbb16e206197 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/index.ts @@ -5,10 +5,10 @@ * 2.0. */ -export * from './execution_event'; -export * from './execution_metrics'; +export * from './execution_event.gen'; +export * from './execution_metrics.gen'; export * from './execution_result.gen'; export * from './execution_settings'; export * from './execution_status.gen'; -export * from './execution_summary'; +export * from './execution_summary.gen'; export * from './log_level'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/log_level.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/log_level.ts index 495589b3cd432..e7004455160bd 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/log_level.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/log_level.ts @@ -5,42 +5,31 @@ * 2.0. */ -import { enumeration } from '@kbn/securitysolution-io-ts-types'; -import { enumFromString } from '../../../../utils/enum_from_string'; import { assertUnreachable } from '../../../../utility_types'; import type { RuleExecutionStatus } from './execution_status.gen'; import { RuleExecutionStatusEnum } from './execution_status.gen'; - -export enum LogLevel { - 'trace' = 'trace', - 'debug' = 'debug', - 'info' = 'info', - 'warn' = 'warn', - 'error' = 'error', -} - -export const TLogLevel = enumeration('LogLevel', LogLevel); +import { LogLevel, LogLevelEnum } from './execution_event.gen'; /** * An array of supported log levels. */ -export const LOG_LEVELS = Object.values(LogLevel); +export const LOG_LEVELS = LogLevel.options; -export const logLevelToNumber = (level: keyof typeof LogLevel | null | undefined): number => { +export const logLevelToNumber = (level: LogLevel | null | undefined): number => { if (!level) { return 0; } switch (level) { - case 'trace': + case LogLevelEnum.trace: return 0; - case 'debug': + case LogLevelEnum.debug: return 10; - case 'info': + case LogLevelEnum.info: return 20; - case 'warn': + case LogLevelEnum.warn: return 30; - case 'error': + case LogLevelEnum.error: return 40; default: assertUnreachable(level); @@ -50,34 +39,32 @@ export const logLevelToNumber = (level: keyof typeof LogLevel | null | undefined export const logLevelFromNumber = (num: number | null | undefined): LogLevel => { if (num === null || num === undefined || num < 10) { - return LogLevel.trace; + return LogLevelEnum.trace; } if (num < 20) { - return LogLevel.debug; + return LogLevelEnum.debug; } if (num < 30) { - return LogLevel.info; + return LogLevelEnum.info; } if (num < 40) { - return LogLevel.warn; + return LogLevelEnum.warn; } - return LogLevel.error; + return LogLevelEnum.error; }; -export const logLevelFromString = enumFromString(LogLevel); - export const logLevelFromExecutionStatus = (status: RuleExecutionStatus): LogLevel => { switch (status) { case RuleExecutionStatusEnum['going to run']: case RuleExecutionStatusEnum.running: case RuleExecutionStatusEnum.succeeded: - return LogLevel.info; + return LogLevelEnum.info; case RuleExecutionStatusEnum['partial failure']: - return LogLevel.warn; + return LogLevelEnum.warn; case RuleExecutionStatusEnum.failed: - return LogLevel.error; + return LogLevelEnum.error; default: assertUnreachable(status); - return LogLevel.trace; + return LogLevelEnum.trace; } }; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.gen.ts index 751e571aae3fd..352a8cbdf89b0 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.gen.ts @@ -6,48 +6,43 @@ */ import { z } from 'zod'; +import { ArrayFromString } from '@kbn/zod-helpers'; /* * NOTICE: Do not edit this file manually. * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ -import { RuleExecutionStatus } from '../../model/execution_status.gen'; import { - SortFieldOfRuleExecutionResult, - RuleExecutionResult, -} from '../../model/execution_result.gen'; + RuleExecutionEventType, + LogLevel, + RuleExecutionEvent, +} from '../../model/execution_event.gen'; import { SortOrder } from '../../../model/sorting.gen'; +import { PaginationResult } from '../../../model/pagination.gen'; export type GetRuleExecutionEventsRequestQuery = z.infer; export const GetRuleExecutionEventsRequestQuery = z.object({ /** - * Start date of the time range to query + * Include events of matching the search term. If omitted, all events will be included. */ - start: z.string().datetime(), + search_term: z.string().optional(), /** - * End date of the time range to query + * Include events of the specified types. If omitted, all types of events will be included. */ - end: z.string().datetime(), + event_types: ArrayFromString(RuleExecutionEventType).optional().default([]), /** - * Query text to filter results by + * Include events having these log levels. If omitted, events of all levels will be included. */ - query_text: z.string().optional().default(''), + log_levels: ArrayFromString(LogLevel).optional().default([]), /** - * Comma-separated list of rule execution statuses to filter results by + * Start date of the time range to query */ - status_filters: z - .preprocess( - (value: unknown) => - typeof value === 'string' ? (value === '' ? [] : value.split(',')) : value, - z.array(RuleExecutionStatus) - ) - .optional() - .default([]), + date_start: z.string().datetime().optional(), /** - * Field to sort results by + * End date of the time range to query */ - sort_field: SortFieldOfRuleExecutionResult.optional().default('timestamp'), + date_end: z.string().datetime().optional(), /** * Sort order to sort results by */ @@ -69,9 +64,6 @@ export type GetRuleExecutionEventsRequestParams = z.infer< typeof GetRuleExecutionEventsRequestParams >; export const GetRuleExecutionEventsRequestParams = z.object({ - /** - * Saved object ID of the rule to get execution results for - */ ruleId: z.string().min(1), }); export type GetRuleExecutionEventsRequestParamsInput = z.input< @@ -80,6 +72,6 @@ export type GetRuleExecutionEventsRequestParamsInput = z.input< export type GetRuleExecutionEventsResponse = z.infer; export const GetRuleExecutionEventsResponse = z.object({ - events: z.array(RuleExecutionResult).optional(), - total: z.number().int().optional(), + events: z.array(RuleExecutionEvent), + pagination: PaginationResult, }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.mock.ts index b46f8d9b13870..e730350215f8e 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.mock.ts @@ -6,7 +6,7 @@ */ import { ruleExecutionEventMock } from '../../model/execution_event.mock'; -import type { GetRuleExecutionEventsResponse } from './get_rule_execution_events_route'; +import type { GetRuleExecutionEventsResponse } from './get_rule_execution_events_route.gen'; const getSomeResponse = (): GetRuleExecutionEventsResponse => { const events = ruleExecutionEventMock.getSomeEvents(); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.schema.yaml index 677213bae4f2e..990ea4ef64876 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.schema.yaml @@ -14,47 +14,47 @@ paths: - name: ruleId in: path required: true - description: Saved object ID of the rule to get execution results for schema: type: string minLength: 1 - - name: start + - name: search_term in: query - required: true - description: Start date of the time range to query - schema: - type: string - format: date-time - - name: end - in: query - required: true - description: End date of the time range to query + required: false + description: Include events of matching the search term. If omitted, all events will be included. schema: type: string - format: date-time - - name: query_text + - name: event_types in: query required: false - description: Query text to filter results by + description: Include events of the specified types. If omitted, all types of events will be included. schema: - type: string - default: '' - - name: status_filters + type: array + items: + $ref: '../../model/execution_event.schema.yaml#/components/schemas/RuleExecutionEventType' + default: [] + - name: log_levels in: query required: false - description: Comma-separated list of rule execution statuses to filter results by + description: Include events having these log levels. If omitted, events of all levels will be included. schema: type: array items: - $ref: '../../model/execution_status.schema.yaml#/components/schemas/RuleExecutionStatus' + $ref: '../../model/execution_event.schema.yaml#/components/schemas/LogLevel' default: [] - - name: sort_field + - name: date_start in: query required: false - description: Field to sort results by + description: Start date of the time range to query schema: - $ref: '../../model/execution_result.schema.yaml#/components/schemas/SortFieldOfRuleExecutionResult' - default: timestamp + type: string + format: date-time + - name: date_end + in: query + required: false + description: End date of the time range to query + schema: + type: string + format: date-time - name: sort_order in: query required: false @@ -87,6 +87,9 @@ paths: events: type: array items: - $ref: '../../model/execution_result.schema.yaml#/components/schemas/RuleExecutionResult' - total: - type: integer + $ref: '../../model/execution_event.schema.yaml#/components/schemas/RuleExecutionEvent' + pagination: + $ref: '../../../model/pagination.schema.yaml#/components/schemas/PaginationResult' + required: + - events + - pagination diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts index ecf94032039ac..5f73b6109e820 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts @@ -5,14 +5,11 @@ * 2.0. */ -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; - +import { expectParseError, expectParseSuccess } from '@kbn/zod-helpers'; import { GetRuleExecutionEventsRequestParams, GetRuleExecutionEventsRequestQuery, -} from './get_rule_execution_events_route'; +} from './get_rule_execution_events_route.gen'; describe('Request schema of Get rule execution events', () => { describe('GetRuleExecutionEventsRequestParams', () => { @@ -22,11 +19,10 @@ describe('Request schema of Get rule execution events', () => { ruleId: 'some id', }; - const decoded = GetRuleExecutionEventsRequestParams.decode(input); - const message = pipe(decoded, foldLeftRight); + const results = GetRuleExecutionEventsRequestParams.safeParse(input); + expectParseSuccess(results); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual( + expect(results.data).toEqual( expect.objectContaining({ ruleId: 'some id', }) @@ -39,23 +35,21 @@ describe('Request schema of Get rule execution events', () => { foo: 'bar', // this one is not in the schema and will be stripped }; - const decoded = GetRuleExecutionEventsRequestParams.decode(input); - const message = pipe(decoded, foldLeftRight); + const results = GetRuleExecutionEventsRequestParams.safeParse(input); + expectParseSuccess(results); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - ruleId: 'some id', - }); + expect(results.data).toEqual( + expect.objectContaining({ + ruleId: 'some id', + }) + ); }); }); describe('Validation fails', () => { const test = (input: unknown) => { - const decoded = GetRuleExecutionEventsRequestParams.decode(input); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors)).length).toBeGreaterThan(0); - expect(message.schema).toEqual({}); + const results = GetRuleExecutionEventsRequestParams.safeParse(input); + expectParseError(results); }; it('when not all the required parameters are passed', () => { @@ -84,11 +78,10 @@ describe('Request schema of Get rule execution events', () => { per_page: 6, }; - const decoded = GetRuleExecutionEventsRequestQuery.decode(input); - const message = pipe(decoded, foldLeftRight); + const result = GetRuleExecutionEventsRequestQuery.safeParse(input); + expectParseSuccess(result); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ + expect(result.data).toEqual({ event_types: ['message', 'status-change'], log_levels: ['debug', 'info', 'error'], sort_order: 'asc', @@ -107,11 +100,10 @@ describe('Request schema of Get rule execution events', () => { foo: 'bar', // this one is not in the schema and will be stripped }; - const decoded = GetRuleExecutionEventsRequestQuery.decode(input); - const message = pipe(decoded, foldLeftRight); + const result = GetRuleExecutionEventsRequestQuery.safeParse(input); + expectParseSuccess(result); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ + expect(result.data).toEqual({ event_types: ['message', 'status-change'], log_levels: ['debug', 'info', 'error'], sort_order: 'asc', @@ -119,25 +111,12 @@ describe('Request schema of Get rule execution events', () => { per_page: 6, }); }); - - it('when no parameters are passed (all are have default values)', () => { - const input = {}; - - const decoded = GetRuleExecutionEventsRequestQuery.decode(input); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expect.any(Object)); - }); }); describe('Validation fails', () => { const test = (input: unknown) => { - const decoded = GetRuleExecutionEventsRequestQuery.decode(input); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors)).length).toBeGreaterThan(0); - expect(message.schema).toEqual({}); + const result = GetRuleExecutionEventsRequestQuery.safeParse(input); + expectParseError(result); }; it('when invalid parameters are passed', () => { @@ -147,21 +126,18 @@ describe('Request schema of Get rule execution events', () => { }); }); - describe('Validation sets default values', () => { - it('when optional parameters are not passed', () => { - const input = {}; + it('Validation sets default values when optional parameters are not passed', () => { + const input = {}; - const decoded = GetRuleExecutionEventsRequestQuery.decode(input); - const message = pipe(decoded, foldLeftRight); + const result = GetRuleExecutionEventsRequestQuery.safeParse(input); + expectParseSuccess(result); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - event_types: [], - log_levels: [], - sort_order: 'desc', - page: 1, - per_page: 20, - }); + expect(result.data).toEqual({ + event_types: [], + log_levels: [], + sort_order: 'desc', + page: 1, + per_page: 20, }); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts deleted file mode 100644 index 628e71cf51790..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts +++ /dev/null @@ -1,60 +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 * as t from 'io-ts'; - -import { DefaultPerPage, DefaultPage } from '@kbn/securitysolution-io-ts-alerting-types'; -import { defaultCsvArray, IsoDateString, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; - -import { DefaultSortOrderDesc, PaginationResult } from '../../../model'; -import { RuleExecutionEvent, TRuleExecutionEventType, TLogLevel } from '../../model'; - -/** - * URL path parameters of the API route. - */ -export type GetRuleExecutionEventsRequestParams = t.TypeOf< - typeof GetRuleExecutionEventsRequestParams ->; -export const GetRuleExecutionEventsRequestParams = t.exact( - t.type({ - ruleId: NonEmptyString, - }) -); - -/** - * Query string parameters of the API route. - */ -export type GetRuleExecutionEventsRequestQuery = t.TypeOf< - typeof GetRuleExecutionEventsRequestQuery ->; -export const GetRuleExecutionEventsRequestQuery = t.exact( - t.intersection([ - t.partial({ - search_term: NonEmptyString, - event_types: defaultCsvArray(TRuleExecutionEventType), - log_levels: defaultCsvArray(TLogLevel), - date_start: IsoDateString, - date_end: IsoDateString, - }), - t.type({ - sort_order: DefaultSortOrderDesc, // defaults to 'desc' - page: DefaultPage, // defaults to 1 - per_page: DefaultPerPage, // defaults to 20 - }), - ]) -); - -/** - * Response body of the API route. - */ -export type GetRuleExecutionEventsResponse = t.TypeOf; -export const GetRuleExecutionEventsResponse = t.exact( - t.type({ - events: t.array(RuleExecutionEvent), - pagination: PaginationResult, - }) -); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen.ts index 442c45f3e8dc9..cb8e2f1d8ffa5 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen.ts @@ -6,6 +6,7 @@ */ import { z } from 'zod'; +import { ArrayFromString } from '@kbn/zod-helpers'; /* * NOTICE: Do not edit this file manually. @@ -38,14 +39,7 @@ export const GetRuleExecutionResultsRequestQuery = z.object({ /** * Comma-separated list of rule execution statuses to filter results by */ - status_filters: z - .preprocess( - (value: unknown) => - typeof value === 'string' ? (value === '' ? [] : value.split(',')) : value, - z.array(RuleExecutionStatus) - ) - .optional() - .default([]), + status_filters: ArrayFromString(RuleExecutionStatus).optional().default([]), /** * Field to sort results by */ diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.ts index dd1ecc4208ef7..9089efeb87d05 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.ts @@ -8,9 +8,6 @@ import * as t from 'io-ts'; import { PositiveInteger, PositiveIntegerGreaterThanZero } from '@kbn/securitysolution-io-ts-types'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { IndexPatternArray } from '../../model/rule_schema_legacy'; export const signalsReindexOptions = t.partial({ requests_per_second: t.number, @@ -23,7 +20,7 @@ export type SignalsReindexOptions = t.TypeOf; export const createSignalsMigrationSchema = t.intersection([ t.exact( t.type({ - index: IndexPatternArray, + index: t.array(t.string), }) ), t.exact(signalsReindexOptions), diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/api.ts b/x-pack/plugins/security_solution/common/api/timeline/model/api.ts index 864b4613857e2..c423b2a4418bb 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/api.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/api.ts @@ -21,7 +21,7 @@ import { SavedObjectResolveAliasTargetId, SavedObjectResolveOutcome, } from '../../detection_engine/model/rule_schema_legacy'; -import { ErrorSchema, success, success_count as successCount } from '../../detection_engine'; +import { ErrorSchema } from './error_schema'; export const BareNoteSchema = runtimeTypes.intersection([ runtimeTypes.type({ @@ -497,8 +497,8 @@ export interface ExportTimelineNotFoundError { export const importTimelineResultSchema = runtimeTypes.exact( runtimeTypes.type({ - success, - success_count: successCount, + success: runtimeTypes.boolean, + success_count: PositiveInteger, timelines_installed: PositiveInteger, timelines_updated: PositiveInteger, errors: runtimeTypes.array(ErrorSchema), diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.mock.ts b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.mock.ts new file mode 100644 index 0000000000000..7c24d605d5f98 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.mock.ts @@ -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 type { ErrorSchema } from './error_schema'; + +export const getErrorSchemaMock = ( + id: string = '819eded6-e9c8-445b-a647-519aea39e063' +): ErrorSchema => ({ + id, + error: { + status_code: 404, + message: 'id: "819eded6-e9c8-445b-a647-519aea39e063" not found', + }, +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.test.ts b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.test.ts similarity index 93% rename from x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.test.ts rename to x-pack/plugins/security_solution/common/api/timeline/model/error_schema.test.ts index 164f5ee854efc..8326479db9c14 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.test.ts @@ -8,9 +8,7 @@ import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { ErrorSchema } from './error_schema_legacy'; +import { ErrorSchema } from './error_schema'; import { getErrorSchemaMock } from './error_schema.mock'; describe('error_schema', () => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema_legacy.ts b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.ts similarity index 69% rename from x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema_legacy.ts rename to x-pack/plugins/security_solution/common/api/timeline/model/error_schema.ts index c2efee05269c1..a0ac17765c3a8 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema_legacy.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.ts @@ -5,22 +5,16 @@ * 2.0. */ -import { NonEmptyString } from '@kbn/securitysolution-io-ts-types'; +import { NonEmptyString, PositiveInteger } from '@kbn/securitysolution-io-ts-types'; import * as t from 'io-ts'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { RuleSignatureId } from './rule_schema_legacy'; - -import { status_code, message } from './schemas'; - // We use id: t.string intentionally and _never_ the id from global schemas as // sometimes echo back out the id that the user gave us and it is not guaranteed // to be a UUID but rather just a string const partial = t.exact( t.partial({ id: t.string, - rule_id: RuleSignatureId, + rule_id: NonEmptyString, list_id: NonEmptyString, item_id: NonEmptyString, }) @@ -28,8 +22,8 @@ const partial = t.exact( const required = t.exact( t.type({ error: t.type({ - status_code, - message, + status_code: PositiveInteger, + message: t.string, }), }) ); diff --git a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts index 6bdce7573ed4c..1e2db97225580 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts @@ -17,8 +17,8 @@ import type { ResponseAction, RuleResponseAction, } from '../api/detection_engine/model/rule_response_actions'; -import { RESPONSE_ACTION_TYPES } from '../api/detection_engine/model/rule_response_actions'; -import type { NormalizedRuleAction } from '../api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { ResponseActionTypesEnum } from '../api/detection_engine/model/rule_response_actions'; +import type { NormalizedRuleAction } from '../api/detection_engine/rule_management'; import type { RuleAction } from '@kbn/alerting-plugin/common'; describe('transform_actions', () => { @@ -93,7 +93,7 @@ describe('transform_actions', () => { }); test('it should transform ResponseAction[] to RuleResponseAction[]', () => { const ruleAction: ResponseAction = { - action_type_id: RESPONSE_ACTION_TYPES.OSQUERY, + action_type_id: ResponseActionTypesEnum['.osquery'], params: { ecs_mapping: {}, saved_query_id: undefined, @@ -117,7 +117,7 @@ describe('transform_actions', () => { test('it should transform RuleResponseAction[] to ResponseAction[]', () => { const alertAction: RuleResponseAction = { - actionTypeId: RESPONSE_ACTION_TYPES.OSQUERY, + actionTypeId: ResponseActionTypesEnum['.osquery'], params: { ecsMapping: {}, savedQueryId: undefined, diff --git a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts index 5750f35d893e2..1f3727e6e4e0b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts @@ -7,12 +7,12 @@ import type { RuleAction as AlertingRuleAction } from '@kbn/alerting-plugin/common'; import type { NormalizedAlertAction } from '@kbn/alerting-plugin/server/rules_client'; -import type { NormalizedRuleAction } from '../api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { NormalizedRuleAction } from '../api/detection_engine/rule_management'; import type { ResponseAction, RuleResponseAction, } from '../api/detection_engine/model/rule_response_actions'; -import { RESPONSE_ACTION_TYPES } from '../api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../api/detection_engine/model/rule_response_actions'; import type { RuleAction } from '../api/detection_engine/model'; export const transformRuleToAlertAction = ({ @@ -63,7 +63,12 @@ export const transformNormalizedRuleToAlertAction = ({ group, id, params: params as AlertingRuleAction['params'], - ...(alertsFilter && { alertsFilter }), + ...(alertsFilter && { + // We use "unknown" as the alerts filter type which is stricter than the one + // used in the alerting plugin (what they use is essentially "any"). So we + // have to to cast here + alertsFilter: alertsFilter as AlertingRuleAction['alertsFilter'], + }), ...(frequency && { frequency }), }); @@ -85,7 +90,7 @@ export const transformRuleToAlertResponseAction = ({ action_type_id: actionTypeId, params, }: ResponseAction): RuleResponseAction => { - if (actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY) { + if (actionTypeId === ResponseActionTypesEnum['.osquery']) { const { saved_query_id: savedQueryId, ecs_mapping: ecsMapping, @@ -113,7 +118,7 @@ export const transformAlertToRuleResponseAction = ({ actionTypeId, params, }: RuleResponseAction): ResponseAction => { - if (actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY) { + if (actionTypeId === ResponseActionTypesEnum['.osquery']) { const { savedQueryId, ecsMapping, packId, ...rest } = params; return { params: { diff --git a/x-pack/plugins/security_solution/common/risk_engine/risk_score_calculation/request_schema.ts b/x-pack/plugins/security_solution/common/risk_engine/risk_score_calculation/request_schema.ts index 6058f60e1e1c6..c05ca782aface 100644 --- a/x-pack/plugins/security_solution/common/risk_engine/risk_score_calculation/request_schema.ts +++ b/x-pack/plugins/security_solution/common/risk_engine/risk_score_calculation/request_schema.ts @@ -6,9 +6,6 @@ */ import * as t from 'io-ts'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { DataViewId } from '../../api/detection_engine/model/rule_schema_legacy'; import { afterKeysSchema } from '../after_keys'; import { identifierTypeSchema } from '../identifier_types'; import { riskWeightsSchema } from '../risk_weights/schema'; @@ -16,7 +13,7 @@ import { riskWeightsSchema } from '../risk_weights/schema'; export const riskScoreCalculationRequestSchema = t.exact( t.intersection([ t.type({ - data_view_id: DataViewId, + data_view_id: t.string, identifier_type: identifierTypeSchema, range: t.type({ start: t.string, diff --git a/x-pack/plugins/security_solution/common/risk_engine/risk_score_preview/request_schema.ts b/x-pack/plugins/security_solution/common/risk_engine/risk_score_preview/request_schema.ts index c440248311636..76ee6a303532b 100644 --- a/x-pack/plugins/security_solution/common/risk_engine/risk_score_preview/request_schema.ts +++ b/x-pack/plugins/security_solution/common/risk_engine/risk_score_preview/request_schema.ts @@ -6,9 +6,6 @@ */ import * as t from 'io-ts'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { DataViewId } from '../../api/detection_engine/model/rule_schema_legacy'; import { afterKeysSchema } from '../after_keys'; import { identifierTypeSchema } from '../identifier_types'; import { rangeSchema } from '../range'; @@ -17,7 +14,7 @@ import { riskWeightsSchema } from '../risk_weights/schema'; export const riskScorePreviewRequestSchema = t.exact( t.intersection([ t.type({ - data_view_id: DataViewId, + data_view_id: t.string, }), t.partial({ after_keys: afterKeysSchema, diff --git a/x-pack/plugins/security_solution/common/types/response_actions/index.ts b/x-pack/plugins/security_solution/common/types/response_actions/index.ts index 07124b6bc5e45..35333bdc54eb7 100644 --- a/x-pack/plugins/security_solution/common/types/response_actions/index.ts +++ b/x-pack/plugins/security_solution/common/types/response_actions/index.ts @@ -13,7 +13,7 @@ export interface RawEventData { _index: string; } -export enum RESPONSE_ACTION_TYPES { +export enum ResponseActionTypesEnum { OSQUERY = '.osquery', ENDPOINT = '.endpoint', } @@ -34,7 +34,7 @@ export interface ExpandedEventFieldsObject { type RuleParameters = Array<{ response_actions: Array<{ - action_type_id: RESPONSE_ACTION_TYPES; + action_type_id: ResponseActionTypesEnum; params: Record; }>; }>; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx index 289561c0bc4aa..274c649ece9dc 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx @@ -20,7 +20,7 @@ import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_fe import { useKibana } from '../../lib/kibana'; import { EventsViewType } from './event_details'; import * as i18n from './translations'; -import { RESPONSE_ACTION_TYPES } from '../../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine/model/rule_response_actions'; const TabContentWrapper = styled.div` height: 100%; @@ -71,7 +71,7 @@ export const useOsqueryTab = ({ } const osqueryResponseActions = responseActions.filter( - (responseAction) => responseAction.action_type_id === RESPONSE_ACTION_TYPES.OSQUERY + (responseAction) => responseAction.action_type_id === ResponseActionTypesEnum['.osquery'] ); if (!osqueryResponseActions?.length) { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts index 9e09a1754a04d..226d0a2bd16ed 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts @@ -16,9 +16,9 @@ import { getRulesSchemaMock, } from '../../../../common/api/detection_engine/model/rule_schema/mocks'; import { - BulkActionType, - BulkActionEditType, -} from '../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; + BulkActionTypeEnum, + BulkActionEditTypeEnum, +} from '../../../../common/api/detection_engine/rule_management'; import { rulesMock } from '../logic/mock'; import type { FindRulesReferencedByExceptionsListProp } from '../logic/types'; @@ -701,7 +701,9 @@ describe('Detections Rules API', () => { }); test('passes a query', async () => { - await performBulkAction({ bulkAction: { type: BulkActionType.enable, query: 'some query' } }); + await performBulkAction({ + bulkAction: { type: BulkActionTypeEnum.enable, query: 'some query' }, + }); expect(fetchMock).toHaveBeenCalledWith( '/api/detection_engine/rules/_bulk_action', @@ -720,7 +722,7 @@ describe('Detections Rules API', () => { test('passes ids', async () => { await performBulkAction({ - bulkAction: { type: BulkActionType.disable, ids: ['ruleId1', 'ruleId2'] }, + bulkAction: { type: BulkActionTypeEnum.disable, ids: ['ruleId1', 'ruleId2'] }, }); expect(fetchMock).toHaveBeenCalledWith( @@ -741,10 +743,10 @@ describe('Detections Rules API', () => { test('passes edit payload', async () => { await performBulkAction({ bulkAction: { - type: BulkActionType.edit, + type: BulkActionTypeEnum.edit, ids: ['ruleId1'], editPayload: [ - { type: BulkActionEditType.add_index_patterns, value: ['some-index-pattern'] }, + { type: BulkActionEditTypeEnum.add_index_patterns, value: ['some-index-pattern'] }, ], }, }); @@ -767,7 +769,7 @@ describe('Detections Rules API', () => { test('executes dry run', async () => { await performBulkAction({ - bulkAction: { type: BulkActionType.disable, query: 'some query' }, + bulkAction: { type: BulkActionTypeEnum.disable, query: 'some query' }, dryRun: true, }); @@ -787,7 +789,7 @@ describe('Detections Rules API', () => { test('returns result', async () => { const result = await performBulkAction({ bulkAction: { - type: BulkActionType.disable, + type: BulkActionTypeEnum.disable, query: 'some query', }, }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index 860e0fc86e850..70f0a56ef74cd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -27,12 +27,16 @@ import type { ReviewRuleInstallationResponseBody, } from '../../../../common/api/detection_engine/prebuilt_rules'; import type { + BulkDuplicateRules, + BulkActionEditPayload, + BulkActionType, CoverageOverviewResponse, GetRuleManagementFiltersResponse, } from '../../../../common/api/detection_engine/rule_management'; import { RULE_MANAGEMENT_FILTERS_URL, RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL, + BulkActionTypeEnum, } from '../../../../common/api/detection_engine/rule_management'; import type { BulkActionsDryRunErrCode } from '../../../../common/constants'; import { @@ -54,11 +58,6 @@ import { import type { RulesReferencedByExceptionListsSchema } from '../../../../common/api/detection_engine/rule_exceptions'; import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../common/api/detection_engine/rule_exceptions'; -import type { - BulkActionDuplicatePayload, - BulkActionEditPayload, -} from '../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionType } from '../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; import type { PreviewResponse, RuleResponse } from '../../../../common/api/detection_engine'; import { KibanaServices } from '../../../common/lib/kibana'; @@ -331,18 +330,18 @@ export type QueryOrIds = { query: string; ids?: undefined } | { query?: undefine type PlainBulkAction = { type: Exclude< BulkActionType, - BulkActionType.edit | BulkActionType.export | BulkActionType.duplicate + BulkActionTypeEnum['edit'] | BulkActionTypeEnum['export'] | BulkActionTypeEnum['duplicate'] >; } & QueryOrIds; type EditBulkAction = { - type: BulkActionType.edit; + type: BulkActionTypeEnum['edit']; editPayload: BulkActionEditPayload[]; } & QueryOrIds; type DuplicateBulkAction = { - type: BulkActionType.duplicate; - duplicatePayload?: BulkActionDuplicatePayload; + type: BulkActionTypeEnum['duplicate']; + duplicatePayload?: BulkDuplicateRules['duplicate']; } & QueryOrIds; export type BulkAction = PlainBulkAction | EditBulkAction | DuplicateBulkAction; @@ -368,9 +367,9 @@ export async function performBulkAction({ action: bulkAction.type, query: bulkAction.query, ids: bulkAction.ids, - edit: bulkAction.type === BulkActionType.edit ? bulkAction.editPayload : undefined, + edit: bulkAction.type === BulkActionTypeEnum.edit ? bulkAction.editPayload : undefined, duplicate: - bulkAction.type === BulkActionType.duplicate ? bulkAction.duplicatePayload : undefined, + bulkAction.type === BulkActionTypeEnum.duplicate ? bulkAction.duplicatePayload : undefined, }; return KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_BULK_ACTION, { @@ -392,7 +391,7 @@ export type BulkExportResponse = Blob; */ export async function bulkExportRules(queryOrIds: QueryOrIds): Promise { const params = { - action: BulkActionType.export, + action: BulkActionTypeEnum.export, query: queryOrIds.query, ids: queryOrIds.ids, }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts index 1a52bbb0a8194..9e54e41f1b091 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts @@ -7,7 +7,7 @@ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core/public'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import type { BulkActionErrorResponse, BulkActionResponse, PerformBulkActionProps } from '../api'; import { performBulkAction } from '../api'; import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../common/constants'; @@ -59,8 +59,8 @@ export const useBulkActionMutation = ( response?.attributes?.results?.updated ?? error?.body?.attributes?.results?.updated; switch (actionType) { - case BulkActionType.enable: - case BulkActionType.disable: { + case BulkActionTypeEnum.enable: + case BulkActionTypeEnum.disable: { invalidateFetchRuleByIdQuery(); invalidateFetchCoverageOverviewQuery(); if (updatedRules) { @@ -72,7 +72,7 @@ export const useBulkActionMutation = ( } break; } - case BulkActionType.delete: + case BulkActionTypeEnum.delete: invalidateFindRulesQuery(); invalidateFetchRuleByIdQuery(); invalidateFetchRuleManagementFilters(); @@ -81,12 +81,12 @@ export const useBulkActionMutation = ( invalidateFetchPrebuiltRulesUpgradeReviewQuery(); invalidateFetchCoverageOverviewQuery(); break; - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: invalidateFindRulesQuery(); invalidateFetchRuleManagementFilters(); invalidateFetchCoverageOverviewQuery(); break; - case BulkActionType.edit: + case BulkActionTypeEnum.edit: if (updatedRules) { // We have a list of updated rules, no need to invalidate all updateRulesCache(updatedRules); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts index 40877d8fcb2fd..99bad79536bd7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts @@ -6,54 +6,57 @@ */ import type { HTTPError } from '../../../../../common/detection_engine/types'; -import type { BulkActionEditPayload } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { - BulkActionEditType, +import type { + BulkActionEditPayload, BulkActionType, -} from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +} from '../../../../../common/api/detection_engine/rule_management'; +import { + BulkActionEditTypeEnum, + BulkActionTypeEnum, +} from '../../../../../common/api/detection_engine/rule_management'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; import type { BulkActionResponse, BulkActionSummary } from '../../api/api'; export function summarizeBulkSuccess(action: BulkActionType): string { switch (action) { - case BulkActionType.export: + case BulkActionTypeEnum.export: return i18n.RULES_BULK_EXPORT_SUCCESS; - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: return i18n.RULES_BULK_DUPLICATE_SUCCESS; - case BulkActionType.delete: + case BulkActionTypeEnum.delete: return i18n.RULES_BULK_DELETE_SUCCESS; - case BulkActionType.enable: + case BulkActionTypeEnum.enable: return i18n.RULES_BULK_ENABLE_SUCCESS; - case BulkActionType.disable: + case BulkActionTypeEnum.disable: return i18n.RULES_BULK_DISABLE_SUCCESS; - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return i18n.RULES_BULK_EDIT_SUCCESS; } } export function explainBulkSuccess( - action: Exclude, + action: Exclude, summary: BulkActionSummary ): string { switch (action) { - case BulkActionType.export: + case BulkActionTypeEnum.export: return getExportSuccessToastMessage(summary.succeeded, summary.total); - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: return i18n.RULES_BULK_DUPLICATE_SUCCESS_DESCRIPTION(summary.succeeded); - case BulkActionType.delete: + case BulkActionTypeEnum.delete: return i18n.RULES_BULK_DELETE_SUCCESS_DESCRIPTION(summary.succeeded); - case BulkActionType.enable: + case BulkActionTypeEnum.enable: return i18n.RULES_BULK_ENABLE_SUCCESS_DESCRIPTION(summary.succeeded); - case BulkActionType.disable: + case BulkActionTypeEnum.disable: return i18n.RULES_BULK_DISABLE_SUCCESS_DESCRIPTION(summary.succeeded); } } @@ -67,9 +70,9 @@ export function explainBulkEditSuccess( if ( editPayload.some( (x) => - x.type === BulkActionEditType.add_index_patterns || - x.type === BulkActionEditType.set_index_patterns || - x.type === BulkActionEditType.delete_index_patterns + x.type === BulkActionEditTypeEnum.add_index_patterns || + x.type === BulkActionEditTypeEnum.set_index_patterns || + x.type === BulkActionEditTypeEnum.delete_index_patterns ) ) { return `${i18n.RULES_BULK_EDIT_SUCCESS_DESCRIPTION( @@ -83,22 +86,22 @@ export function explainBulkEditSuccess( export function summarizeBulkError(action: BulkActionType): string { switch (action) { - case BulkActionType.export: + case BulkActionTypeEnum.export: return i18n.RULES_BULK_EXPORT_FAILURE; - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: return i18n.RULES_BULK_DUPLICATE_FAILURE; - case BulkActionType.delete: + case BulkActionTypeEnum.delete: return i18n.RULES_BULK_DELETE_FAILURE; - case BulkActionType.enable: + case BulkActionTypeEnum.enable: return i18n.RULES_BULK_ENABLE_FAILURE; - case BulkActionType.disable: + case BulkActionTypeEnum.disable: return i18n.RULES_BULK_DISABLE_FAILURE; - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return i18n.RULES_BULK_EDIT_FAILURE; } } @@ -112,22 +115,22 @@ export function explainBulkError(action: BulkActionType, error: HTTPError): stri } switch (action) { - case BulkActionType.export: + case BulkActionTypeEnum.export: return i18n.RULES_BULK_EXPORT_FAILURE_DESCRIPTION(summary.failed); - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: return i18n.RULES_BULK_DUPLICATE_FAILURE_DESCRIPTION(summary.failed); - case BulkActionType.delete: + case BulkActionTypeEnum.delete: return i18n.RULES_BULK_DELETE_FAILURE_DESCRIPTION(summary.failed); - case BulkActionType.enable: + case BulkActionTypeEnum.enable: return i18n.RULES_BULK_ENABLE_FAILURE_DESCRIPTION(summary.failed); - case BulkActionType.disable: + case BulkActionTypeEnum.disable: return i18n.RULES_BULK_DISABLE_FAILURE_DESCRIPTION(summary.failed); - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return i18n.RULES_BULK_EDIT_FAILURE_DESCRIPTION(summary.failed, summary.skipped); } } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts index 1ac62109cc626..968b40c7a6026 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts @@ -6,7 +6,7 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; import { useBulkExportMutation } from '../../api/hooks/use_bulk_export_mutation'; @@ -92,7 +92,7 @@ describe('useBulkExport', () => { expect(setLoadingRules).toHaveBeenCalledWith({ ids: ['ruleId1', 'ruleId2'], - action: BulkActionType.export, + action: BulkActionTypeEnum.export, }); }); @@ -101,7 +101,7 @@ describe('useBulkExport', () => { expect(setLoadingRules).toHaveBeenCalledWith({ ids: [], - action: BulkActionType.export, + action: BulkActionTypeEnum.export, }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts index 5554ba2296302..651b2b0e4b86c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts @@ -6,7 +6,7 @@ */ import { useCallback } from 'react'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; import { useBulkExportMutation } from '../../api/hooks/use_bulk_export_mutation'; import { useShowBulkErrorToast } from './use_show_bulk_error_toast'; @@ -24,12 +24,12 @@ export function useBulkExport() { async (queryOrIds: QueryOrIds) => { try { setLoadingRules?.({ - ids: queryOrIds.ids ?? guessRuleIdsForBulkAction(BulkActionType.export), - action: BulkActionType.export, + ids: queryOrIds.ids ?? guessRuleIdsForBulkAction(BulkActionTypeEnum.export), + action: BulkActionTypeEnum.export, }); return await mutateAsync(queryOrIds); } catch (error) { - showBulkErrorToast({ actionType: BulkActionType.export, error }); + showBulkErrorToast({ actionType: BulkActionTypeEnum.export, error }); } finally { setLoadingRules?.({ ids: [], action: null }); } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts index 94c26b278d1b1..7be106090bcfa 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts @@ -6,7 +6,7 @@ */ import { useCallback } from 'react'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { downloadBlob } from '../../../../common/utils/download_blob'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; import { getExportedRulesCounts } from '../../../rule_management_ui/components/rules_table/helpers'; @@ -27,11 +27,11 @@ export function useDownloadExportedRules() { try { downloadBlob(response, DEFAULT_EXPORT_FILENAME); showBulkSuccessToast({ - actionType: BulkActionType.export, + actionType: BulkActionTypeEnum.export, summary: await getExportedRulesCounts(response), }); } catch (error) { - showBulkErrorToast({ actionType: BulkActionType.export, error }); + showBulkErrorToast({ actionType: BulkActionTypeEnum.export, error }); } }, [showBulkSuccessToast, showBulkErrorToast] diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts index 3c211247b94ec..6309d8b629bc2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts @@ -6,7 +6,7 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../common/lib/telemetry'; import { useBulkActionMutation } from '../../api/hooks/use_bulk_action_mutation'; @@ -61,7 +61,7 @@ describe('useExecuteBulkAction', () => { it('executes bulk action', async () => { const bulkAction = { - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, query: 'some query', } as const; @@ -73,7 +73,7 @@ describe('useExecuteBulkAction', () => { describe('state handlers', () => { it('shows success toast upon completion', async () => { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1'], }); @@ -84,7 +84,7 @@ describe('useExecuteBulkAction', () => { it('does not shows success toast upon completion if suppressed', async () => { await executeBulkAction( { - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1'], }, { suppressSuccessToast: true } @@ -100,7 +100,7 @@ describe('useExecuteBulkAction', () => { }); await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1'], }); @@ -126,31 +126,31 @@ describe('useExecuteBulkAction', () => { it('sets the loading state before execution', async () => { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1', 'ruleId2'], }); expect(setLoadingRules).toHaveBeenCalledWith({ ids: ['ruleId1', 'ruleId2'], - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, }); }); it('sets the empty loading state before execution when query is set', async () => { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, query: 'some query', }); expect(setLoadingRules).toHaveBeenCalledWith({ ids: [], - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, }); }); it('clears loading state for the processing rules after execution', async () => { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1', 'ruleId2'], }); @@ -163,7 +163,7 @@ describe('useExecuteBulkAction', () => { }); await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1', 'ruleId2'], }); @@ -174,7 +174,7 @@ describe('useExecuteBulkAction', () => { describe('telemetry', () => { it('sends for enable action', async () => { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, query: 'some query', }); @@ -184,7 +184,7 @@ describe('useExecuteBulkAction', () => { it('sends for disable action', async () => { await executeBulkAction({ - type: BulkActionType.disable, + type: BulkActionTypeEnum.disable, query: 'some query', }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts index 9fbfb0c310f20..0a294647aad3f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts @@ -9,7 +9,8 @@ import type { NavigateToAppOptions } from '@kbn/core/public'; import { useCallback } from 'react'; import type { BulkActionResponse } from '..'; import { APP_UI_ID } from '../../../../../common/constants'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionType } from '../../../../../common/api/detection_engine/rule_management'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { SecurityPageName } from '../../../../app/types'; import { getEditRuleUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../common/lib/telemetry'; @@ -58,7 +59,7 @@ export const useExecuteBulkAction = (options?: UseExecuteBulkActionOptions) => { actionType: bulkAction.type, summary: response.attributes.summary, editPayload: - bulkAction.type === BulkActionType.edit ? bulkAction.editPayload : undefined, + bulkAction.type === BulkActionTypeEnum.edit ? bulkAction.editPayload : undefined, }); } @@ -83,14 +84,14 @@ export const useExecuteBulkAction = (options?: UseExecuteBulkActionOptions) => { }; function sendTelemetry(action: BulkActionType, response: BulkActionResponse): void { - if (action !== BulkActionType.disable && action !== BulkActionType.enable) { + if (action !== BulkActionTypeEnum.disable && action !== BulkActionTypeEnum.enable) { return; } if (response.attributes.results.updated.some((rule) => rule.immutable)) { track( METRIC_TYPE.COUNT, - action === BulkActionType.enable + action === BulkActionTypeEnum.enable ? TELEMETRY_EVENT.SIEM_RULE_ENABLED : TELEMETRY_EVENT.SIEM_RULE_DISABLED ); @@ -99,7 +100,7 @@ function sendTelemetry(action: BulkActionType, response: BulkActionResponse): vo if (response.attributes.results.updated.some((rule) => !rule.immutable)) { track( METRIC_TYPE.COUNT, - action === BulkActionType.disable + action === BulkActionTypeEnum.disable ? TELEMETRY_EVENT.CUSTOM_RULE_DISABLED : TELEMETRY_EVENT.CUSTOM_RULE_ENABLED ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts index ce262ce940f43..2a1acc7a3d4c8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts @@ -6,7 +6,8 @@ */ import { useCallback } from 'react'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionType } from '../../../../../common/api/detection_engine/rule_management'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; export function useGuessRuleIdsForBulkAction(): (bulkActionType: BulkActionType) => string[] { @@ -16,9 +17,9 @@ export function useGuessRuleIdsForBulkAction(): (bulkActionType: BulkActionType) (bulkActionType: BulkActionType) => { const allRules = rulesTableContext?.state.isAllSelected ? rulesTableContext.state.rules : []; const processingRules = - bulkActionType === BulkActionType.enable + bulkActionType === BulkActionTypeEnum.enable ? allRules.filter((x) => !x.enabled) - : bulkActionType === BulkActionType.disable + : bulkActionType === BulkActionTypeEnum.disable ? allRules.filter((x) => x.enabled) : allRules; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts index 3f9230a36da34..bb72429ad6b0b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts @@ -8,7 +8,7 @@ import { useCallback } from 'react'; import type { HTTPError } from '../../../../../common/detection_engine/types'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionType } from '../../../../../common/api/detection_engine/rule_management'; import { explainBulkError, summarizeBulkError } from './translations'; interface ShowBulkErrorToastProps { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts index dfc2ca5dcb918..03113c772818c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts @@ -8,8 +8,11 @@ import { useCallback } from 'react'; import type { BulkActionSummary } from '..'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { BulkActionEditPayload } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { + BulkActionEditPayload, + BulkActionType, +} from '../../../../../common/api/detection_engine/rule_management'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { explainBulkEditSuccess, explainBulkSuccess, summarizeBulkSuccess } from './translations'; interface ShowBulkSuccessToastProps { @@ -24,7 +27,7 @@ export function useShowBulkSuccessToast() { return useCallback( ({ actionType, summary, editPayload }: ShowBulkSuccessToastProps) => { const text = - actionType === BulkActionType.edit + actionType === BulkActionTypeEnum.edit ? explainBulkEditSuccess(editPayload ?? [], summary) : explainBulkSuccess(actionType, summary); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index d44c4effd265f..94a3d47c90ecf 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -5,12 +5,11 @@ * 2.0. */ -import * as t from 'io-ts'; +import * as z from 'zod'; import type { RuleSnooze } from '@kbn/alerting-plugin/common'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; -import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; import type { RuleSnoozeSettings } from '@kbn/triggers-actions-ui-plugin/public/types'; import type { WarningSchema } from '../../../../common/api/detection_engine'; import type { RuleExecutionStatus } from '../../../../common/api/detection_engine/rule_monitoring'; @@ -49,11 +48,11 @@ export interface PatchRuleProps { export type Rule = RuleResponse; -export type PaginationOptions = t.TypeOf; -export const PaginationOptions = t.type({ - page: PositiveInteger, - perPage: PositiveInteger, - total: PositiveInteger, +export type PaginationOptions = z.infer; +export const PaginationOptions = z.object({ + page: z.number().int().min(0), + perPage: z.number().int().min(0), + total: z.number().int().min(0), }); export interface FetchRulesProps { @@ -81,8 +80,8 @@ export interface RulesSnoozeSettingsBatchResponse { data: RuleSnoozeSettingsResponse[]; } -export type SortingOptions = t.TypeOf; -export const SortingOptions = t.type({ +export type SortingOptions = z.infer; +export const SortingOptions = z.object({ field: FindRulesSortField, order: SortOrder, }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx index 9e3e9c8b602b1..23bb9106d62cf 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx @@ -10,7 +10,7 @@ import { EuiConfirmModal } from '@elastic/eui'; import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; import { BulkActionRuleErrorsList } from './bulk_action_rule_errors_list'; -import { BulkActionType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import { assertUnreachable } from '../../../../../../common/utility_types'; import type { BulkActionForConfirmation, DryRunResult } from './types'; @@ -20,9 +20,9 @@ const getActionRejectedTitle = ( failedRulesCount: number ) => { switch (bulkAction) { - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return i18n.BULK_EDIT_CONFIRMATION_REJECTED_TITLE(failedRulesCount); - case BulkActionType.export: + case BulkActionTypeEnum.export: return i18n.BULK_EXPORT_CONFIRMATION_REJECTED_TITLE(failedRulesCount); default: assertUnreachable(bulkAction); @@ -34,9 +34,9 @@ const getActionConfirmLabel = ( succeededRulesCount: number ) => { switch (bulkAction) { - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return i18n.BULK_EDIT_CONFIRMATION_CONFIRM(succeededRulesCount); - case BulkActionType.export: + case BulkActionTypeEnum.export: return i18n.BULK_EXPORT_CONFIRMATION_CONFIRM(succeededRulesCount); default: assertUnreachable(bulkAction); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx index 05a27a17274a1..5b90a457a6bd4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx @@ -13,7 +13,7 @@ import { render, screen } from '@testing-library/react'; import { BulkActionRuleErrorsList } from './bulk_action_rule_errors_list'; import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; import type { DryRunResult } from './types'; -import { BulkActionType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; const Wrapper: FC = ({ children }) => { return ( @@ -26,7 +26,7 @@ const Wrapper: FC = ({ children }) => { describe('Component BulkEditRuleErrorsList', () => { test('should not render component if no errors present', () => { const { container } = render( - , + , { wrapper: Wrapper, } @@ -46,9 +46,12 @@ describe('Component BulkEditRuleErrorsList', () => { ruleIds: ['rule:1'], }, ]; - render(, { - wrapper: Wrapper, - }); + render( + , + { + wrapper: Wrapper, + } + ); expect(screen.getByText("2 rules can't be edited (test failure)")).toBeInTheDocument(); expect(screen.getByText("1 rule can't be edited (another failure)")).toBeInTheDocument(); @@ -80,9 +83,12 @@ describe('Component BulkEditRuleErrorsList', () => { ruleIds: ['rule:1', 'rule:2'], }, ]; - render(, { - wrapper: Wrapper, - }); + render( + , + { + wrapper: Wrapper, + } + ); expect(screen.getByText(value)).toBeInTheDocument(); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx index 674206446f85c..907e67f658bc4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx @@ -10,7 +10,7 @@ import { EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; -import { BulkActionType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import type { DryRunResult, BulkActionForConfirmation } from './types'; @@ -132,7 +132,7 @@ const BulkActionRuleErrorsListComponent = ({ {ruleErrors.map(({ message, errorCode, ruleIds }) => { const rulesCount = ruleIds.length; switch (bulkAction) { - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return ( ); - case BulkActionType.export: + case BulkActionTypeEnum.export: return ( { switch (editAction) { - case BulkActionEditType.add_index_patterns: - case BulkActionEditType.delete_index_patterns: - case BulkActionEditType.set_index_patterns: + case BulkActionEditTypeEnum.add_index_patterns: + case BulkActionEditTypeEnum.delete_index_patterns: + case BulkActionEditTypeEnum.set_index_patterns: return ; - case BulkActionEditType.add_tags: - case BulkActionEditType.delete_tags: - case BulkActionEditType.set_tags: + case BulkActionEditTypeEnum.add_tags: + case BulkActionEditTypeEnum.delete_tags: + case BulkActionEditTypeEnum.set_tags: return ; - case BulkActionEditType.set_timeline: + case BulkActionEditTypeEnum.set_timeline: return ; - case BulkActionEditType.add_rule_actions: - case BulkActionEditType.set_rule_actions: + case BulkActionEditTypeEnum.add_rule_actions: + case BulkActionEditTypeEnum.set_rule_actions: return ; - case BulkActionEditType.set_schedule: + case BulkActionEditTypeEnum.set_schedule: return ; default: diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx index c9d4900e9adc7..e124e23bd0aea 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx @@ -14,8 +14,8 @@ import * as i18n from '../../../../../../detections/pages/detection_engine/rules import { DEFAULT_INDEX_KEY } from '../../../../../../../common/constants'; import { useKibana } from '../../../../../../common/lib/kibana'; -import { BulkActionEditType } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import type { BulkActionEditPayload } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionEditTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management'; +import type { BulkActionEditPayload } from '../../../../../../../common/api/detection_engine/rule_management'; import type { FormSchema } from '../../../../../../shared_imports'; import { @@ -31,9 +31,9 @@ import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; const CommonUseField = getUseField({ component: Field }); type IndexPatternsEditActions = - | BulkActionEditType.add_index_patterns - | BulkActionEditType.delete_index_patterns - | BulkActionEditType.set_index_patterns; + | BulkActionEditTypeEnum['add_index_patterns'] + | BulkActionEditTypeEnum['delete_index_patterns'] + | BulkActionEditTypeEnum['set_index_patterns']; interface IndexPatternsFormData { index: string[]; @@ -70,7 +70,7 @@ const initialFormData: IndexPatternsFormData = { }; const getFormConfig = (editAction: IndexPatternsEditActions) => - editAction === BulkActionEditType.add_index_patterns + editAction === BulkActionEditTypeEnum.add_index_patterns ? { indexLabel: i18n.BULK_EDIT_FLYOUT_FORM_ADD_INDEX_PATTERNS_LABEL, indexHelpText: i18n.BULK_EDIT_FLYOUT_FORM_ADD_INDEX_PATTERNS_HELP_TEXT, @@ -115,13 +115,11 @@ const IndexPatternsFormComponent = ({ return; } - const payload = { + onConfirm({ value: data.index, - type: data.overwrite ? BulkActionEditType.set_index_patterns : editAction, + type: data.overwrite ? BulkActionEditTypeEnum.set_index_patterns : editAction, overwrite_data_views: data.overwriteDataViews, - }; - - onConfirm(payload); + }); }; return ( @@ -140,7 +138,7 @@ const IndexPatternsFormComponent = ({ }, }} /> - {editAction === BulkActionEditType.add_index_patterns && ( + {editAction === BulkActionEditTypeEnum.add_index_patterns && ( )} - {editAction === BulkActionEditType.add_index_patterns && ( + {editAction === BulkActionEditTypeEnum.add_index_patterns && ( )} - {editAction === BulkActionEditType.delete_index_patterns && ( + {editAction === BulkActionEditTypeEnum.delete_index_patterns && ( = { const initialFormData: TagsFormData = { tags: [], overwrite: false }; const getFormConfig = (editAction: TagsEditActions) => - editAction === BulkActionEditType.add_tags + editAction === BulkActionEditTypeEnum.add_tags ? { tagsLabel: i18n.BULK_EDIT_FLYOUT_FORM_ADD_TAGS_LABEL, tagsHelpText: i18n.BULK_EDIT_FLYOUT_FORM_ADD_TAGS_HELP_TEXT, @@ -97,12 +97,10 @@ const TagsFormComponent = ({ editAction, rulesCount, onClose, onConfirm }: TagsF return; } - const payload = { + onConfirm({ value: data.tags, - type: data.overwrite ? BulkActionEditType.set_tags : editAction, - }; - - onConfirm(payload); + type: data.overwrite ? BulkActionEditTypeEnum.set_tags : editAction, + }); }; return ( @@ -121,7 +119,7 @@ const TagsFormComponent = ({ editAction, rulesCount, onClose, onConfirm }: TagsF }, }} /> - {editAction === BulkActionEditType.add_tags ? ( + {editAction === BulkActionEditTypeEnum.add_tags ? ( { const timelineTitle = timelineId ? data.timeline.title : ''; onConfirm({ - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: timelineId, timeline_title: timelineTitle, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts index 000a7e37a9cec..409ee722c6383 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts @@ -6,14 +6,14 @@ */ import type { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; -import type { BulkActionType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; /** * Only 2 bulk actions are supported for for confirmation dry run modal: * * export * * edit */ -export type BulkActionForConfirmation = BulkActionType.export | BulkActionType.edit; +export type BulkActionForConfirmation = BulkActionTypeEnum['export'] | BulkActionTypeEnum['edit']; /** * transformed results of dry run diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx index a7c5e35ff3341..41802a4738b8d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx @@ -14,11 +14,14 @@ import { euiThemeVars } from '@kbn/ui-theme'; import React, { useCallback } from 'react'; import { convertRulesFilterToKQL } from '../../../../../../common/detection_engine/rule_management/rule_filtering'; import { DuplicateOptions } from '../../../../../../common/detection_engine/rule_management/constants'; -import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management'; -import { - BulkActionType, +import type { + BulkActionEditPayload, BulkActionEditType, } from '../../../../../../common/api/detection_engine/rule_management'; +import { + BulkActionTypeEnum, + BulkActionEditTypeEnum, +} from '../../../../../../common/api/detection_engine/rule_management'; import { isMlRule } from '../../../../../../common/machine_learning/helpers'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import { BULK_RULE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; @@ -106,7 +109,7 @@ export const useBulkActions = ({ : disabledRulesNoML.map(({ id }) => id); await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ...(isAllSelected ? { query: kql } : { ids: ruleIds }), }); }; @@ -118,7 +121,7 @@ export const useBulkActions = ({ const enabledIds = selectedRules.filter(({ enabled }) => enabled).map(({ id }) => id); await executeBulkAction({ - type: BulkActionType.disable, + type: BulkActionTypeEnum.disable, ...(isAllSelected ? { query: kql } : { ids: enabledIds }), }); }; @@ -132,7 +135,7 @@ export const useBulkActions = ({ return; } await executeBulkAction({ - type: BulkActionType.duplicate, + type: BulkActionTypeEnum.duplicate, duplicatePayload: { include_exceptions: modalDuplicationConfirmationResult === DuplicateOptions.withExceptions || @@ -159,7 +162,7 @@ export const useBulkActions = ({ startTransaction({ name: BULK_RULE_ACTIONS.DELETE }); await executeBulkAction({ - type: BulkActionType.delete, + type: BulkActionTypeEnum.delete, ...(isAllSelected ? { query: kql } : { ids: selectedRuleIds }), }); }; @@ -183,7 +186,7 @@ export const useBulkActions = ({ // they can either cancel action or proceed with export of succeeded rules const hasActionBeenConfirmed = await showBulkActionConfirmation( transformExportDetailsToDryRunResult(details), - BulkActionType.export + BulkActionTypeEnum.export ); if (hasActionBeenConfirmed === false) { return; @@ -201,7 +204,7 @@ export const useBulkActions = ({ setIsPreflightInProgress(true); const dryRunResult = await executeBulkActionsDryRun({ - type: BulkActionType.edit, + type: BulkActionTypeEnum.edit, ...(isAllSelected ? { query: convertRulesFilterToKQL(filterOptions) } : { ids: selectedRuleIds }), @@ -213,7 +216,7 @@ export const useBulkActions = ({ // User has cancelled edit action or there are no custom rules to proceed const hasActionBeenConfirmed = await showBulkActionConfirmation( dryRunResult, - BulkActionType.edit + BulkActionTypeEnum.edit ); if (hasActionBeenConfirmed === false) { return; @@ -264,7 +267,7 @@ export const useBulkActions = ({ }, 5 * 1000); await executeBulkAction({ - type: BulkActionType.edit, + type: BulkActionTypeEnum.edit, ...prepareSearchParams({ ...(isAllSelected ? { filterOptions } : { selectedRuleIds }), dryRunResult, @@ -330,7 +333,7 @@ export const useBulkActions = ({ name: i18n.BULK_ACTION_ADD_RULE_ACTIONS, 'data-test-subj': 'addRuleActionsBulk', disabled: !hasActionsPrivileges || isEditDisabled, - onClick: handleBulkEdit(BulkActionEditType.add_rule_actions), + onClick: handleBulkEdit(BulkActionEditTypeEnum.add_rule_actions), toolTipContent: !hasActionsPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES : undefined, @@ -342,7 +345,7 @@ export const useBulkActions = ({ name: i18n.BULK_ACTION_SET_SCHEDULE, 'data-test-subj': 'setScheduleBulk', disabled: isEditDisabled, - onClick: handleBulkEdit(BulkActionEditType.set_schedule), + onClick: handleBulkEdit(BulkActionEditTypeEnum.set_schedule), toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES : undefined, @@ -354,7 +357,7 @@ export const useBulkActions = ({ name: i18n.BULK_ACTION_APPLY_TIMELINE_TEMPLATE, 'data-test-subj': 'applyTimelineTemplateBulk', disabled: isEditDisabled, - onClick: handleBulkEdit(BulkActionEditType.set_timeline), + onClick: handleBulkEdit(BulkActionEditTypeEnum.set_timeline), toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES : undefined, @@ -407,7 +410,7 @@ export const useBulkActions = ({ key: i18n.BULK_ACTION_ADD_TAGS, name: i18n.BULK_ACTION_ADD_TAGS, 'data-test-subj': 'addTagsBulkEditRule', - onClick: handleBulkEdit(BulkActionEditType.add_tags), + onClick: handleBulkEdit(BulkActionEditTypeEnum.add_tags), disabled: isEditDisabled, toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES @@ -418,7 +421,7 @@ export const useBulkActions = ({ key: i18n.BULK_ACTION_DELETE_TAGS, name: i18n.BULK_ACTION_DELETE_TAGS, 'data-test-subj': 'deleteTagsBulkEditRule', - onClick: handleBulkEdit(BulkActionEditType.delete_tags), + onClick: handleBulkEdit(BulkActionEditTypeEnum.delete_tags), disabled: isEditDisabled, toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES @@ -435,7 +438,7 @@ export const useBulkActions = ({ key: i18n.BULK_ACTION_ADD_INDEX_PATTERNS, name: i18n.BULK_ACTION_ADD_INDEX_PATTERNS, 'data-test-subj': 'addIndexPatternsBulkEditRule', - onClick: handleBulkEdit(BulkActionEditType.add_index_patterns), + onClick: handleBulkEdit(BulkActionEditTypeEnum.add_index_patterns), disabled: isEditDisabled, toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES @@ -446,7 +449,7 @@ export const useBulkActions = ({ key: i18n.BULK_ACTION_DELETE_INDEX_PATTERNS, name: i18n.BULK_ACTION_DELETE_INDEX_PATTERNS, 'data-test-subj': 'deleteIndexPatternsBulkEditRule', - onClick: handleBulkEdit(BulkActionEditType.delete_index_patterns), + onClick: handleBulkEdit(BulkActionEditTypeEnum.delete_index_patterns), disabled: isEditDisabled, toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts index 9ce813fc6d9a2..5ef159bed856b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts @@ -11,11 +11,23 @@ import { useBoolState } from '../../../../../common/hooks/use_bool_state'; import type { DryRunResult, BulkActionForConfirmation } from './types'; +interface BulkActionsConfirmation { + bulkActionsDryRunResult: DryRunResult | undefined; + bulkAction: BulkActionForConfirmation | undefined; + isBulkActionConfirmationVisible: boolean; + showBulkActionConfirmation: ( + result: DryRunResult | undefined, + action: BulkActionForConfirmation + ) => Promise; + cancelBulkActionConfirmation: () => void; + approveBulkActionConfirmation: () => void; +} + /** * hook that controls bulk actions confirmation modal window and its content */ // TODO Why does this hook exist? Consider removing it altogether -export const useBulkActionsConfirmation = () => { +export const useBulkActionsConfirmation = (): BulkActionsConfirmation => { const [bulkAction, setBulkAction] = useState(); const [dryRunResult, setDryRunResult] = useState(); const [isBulkActionConfirmationVisible, showModal, hideModal] = useBoolState(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts index 260e187e46fbe..f2dc15233cb6b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts @@ -10,10 +10,20 @@ import { useAsyncConfirmation } from '../rules_table/use_async_confirmation'; import type { BulkActionEditPayload, BulkActionEditType, -} from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +} from '../../../../../../common/api/detection_engine/rule_management'; import { useBoolState } from '../../../../../common/hooks/use_bool_state'; -export const useBulkEditFormFlyout = () => { +interface UseBulkEditFormFlyout { + bulkEditActionType: BulkActionEditType | undefined; + isBulkEditFlyoutVisible: boolean; + handleBulkEditFormConfirm: (data: BulkActionEditPayload) => void; + handleBulkEditFormCancel: () => void; + completeBulkEditForm: ( + editActionType: BulkActionEditType + ) => Promise; +} + +export const useBulkEditFormFlyout = (): UseBulkEditFormFlyout => { const dataFormRef = useRef(null); const [actionType, setActionType] = useState(); const [isBulkEditFlyoutVisible, showBulkEditFlyout, hideBulkEditFlyout] = useBoolState(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts index 3adae50d99adf..0549306036fd2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts @@ -5,19 +5,20 @@ * 2.0. */ -import { BulkActionEditType } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditType } from '../../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management'; import { computeDryRunEditPayload } from './compute_dry_run_edit_payload'; describe('computeDryRunEditPayload', () => { - test.each([ - [BulkActionEditType.set_index_patterns, []], - [BulkActionEditType.delete_index_patterns, []], - [BulkActionEditType.add_index_patterns, []], - [BulkActionEditType.add_tags, []], - [BulkActionEditType.delete_index_patterns, []], - [BulkActionEditType.set_tags, []], - [BulkActionEditType.set_timeline, { timeline_id: '', timeline_title: '' }], + test.each<[BulkActionEditType, unknown]>([ + [BulkActionEditTypeEnum.set_index_patterns, []], + [BulkActionEditTypeEnum.delete_index_patterns, []], + [BulkActionEditTypeEnum.add_index_patterns, []], + [BulkActionEditTypeEnum.add_tags, []], + [BulkActionEditTypeEnum.delete_index_patterns, []], + [BulkActionEditTypeEnum.set_tags, []], + [BulkActionEditTypeEnum.set_timeline, { timeline_id: '', timeline_title: '' }], ])('should return correct payload for bulk edit action %s', (editAction, value) => { const payload = computeDryRunEditPayload(editAction); expect(payload).toHaveLength(1); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts index d31bbfaa91790..ba5d565e393d0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts @@ -5,8 +5,11 @@ * 2.0. */ -import type { BulkActionEditPayload } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionEditType } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { + BulkActionEditPayload, + BulkActionEditType, +} from '../../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management'; import { assertUnreachable } from '../../../../../../../common/utility_types'; /** @@ -17,9 +20,9 @@ import { assertUnreachable } from '../../../../../../../common/utility_types'; */ export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkActionEditPayload[] { switch (editAction) { - case BulkActionEditType.add_index_patterns: - case BulkActionEditType.delete_index_patterns: - case BulkActionEditType.set_index_patterns: + case BulkActionEditTypeEnum.add_index_patterns: + case BulkActionEditTypeEnum.delete_index_patterns: + case BulkActionEditTypeEnum.set_index_patterns: return [ { type: editAction, @@ -27,9 +30,9 @@ export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkAc }, ]; - case BulkActionEditType.add_tags: - case BulkActionEditType.delete_tags: - case BulkActionEditType.set_tags: + case BulkActionEditTypeEnum.add_tags: + case BulkActionEditTypeEnum.delete_tags: + case BulkActionEditTypeEnum.set_tags: return [ { type: editAction, @@ -37,7 +40,7 @@ export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkAc }, ]; - case BulkActionEditType.set_timeline: + case BulkActionEditTypeEnum.set_timeline: return [ { type: editAction, @@ -45,15 +48,15 @@ export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkAc }, ]; - case BulkActionEditType.add_rule_actions: - case BulkActionEditType.set_rule_actions: + case BulkActionEditTypeEnum.add_rule_actions: + case BulkActionEditTypeEnum.set_rule_actions: return [ { type: editAction, value: { actions: [] }, }, ]; - case BulkActionEditType.set_schedule: + case BulkActionEditTypeEnum.set_schedule: return [ { type: editAction, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx index e27910df0b7e0..fbb81fd0b66f4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx @@ -12,7 +12,7 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { of } from 'rxjs'; import { siemGuideId } from '../../../../../../../common/guided_onboarding/siem_guide_config'; -import { BulkActionType } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management'; import { useKibana } from '../../../../../../common/lib/kibana'; import { useFindRulesQuery } from '../../../../../rule_management/api/hooks/use_find_rules_query'; import { useExecuteBulkAction } from '../../../../../rule_management/logic/bulk_actions/use_execute_bulk_action'; @@ -113,7 +113,7 @@ export const RulesManagementTour = () => { const enableDemoRule = useCallback(async () => { if (demoRule) { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: [demoRule.id], }); } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_saved_state.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_saved_state.ts index 84c23a248a0db..1a4efe7517ac9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_saved_state.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_saved_state.ts @@ -5,41 +5,45 @@ * 2.0. */ -import * as t from 'io-ts'; -import { enumeration } from '@kbn/securitysolution-io-ts-types'; -import { SortingOptions, PaginationOptions } from '../../../../rule_management/logic'; -import { TRuleExecutionStatus } from '../../../../../../common/api/detection_engine/rule_monitoring/model/execution_status'; +import * as z from 'zod'; +import { RuleExecutionStatus } from '../../../../../../common/api/detection_engine'; +import { PaginationOptions, SortingOptions } from '../../../../rule_management/logic'; export enum RuleSource { Prebuilt = 'prebuilt', Custom = 'custom', } -export type RulesTableSavedFilter = t.TypeOf; -export const RulesTableSavedFilter = t.partial({ - searchTerm: t.string, - source: enumeration('RuleSource', RuleSource), - tags: t.array(t.string), - enabled: t.boolean, - ruleExecutionStatus: TRuleExecutionStatus, -}); - -export type RulesTableSavedSorting = t.TypeOf; -export const RulesTableSavedSorting = t.partial({ - field: SortingOptions.props.field, - order: SortingOptions.props.order, -}); - -export type RulesTableStorageSavedPagination = t.TypeOf; -export const RulesTableStorageSavedPagination = t.partial({ - perPage: PaginationOptions.props.perPage, -}); - -export type RulesTableUrlSavedPagination = t.TypeOf; -export const RulesTableUrlSavedPagination = t.partial({ - page: PaginationOptions.props.page, - perPage: PaginationOptions.props.perPage, -}); +export const RulesTableSavedFilter = z + .object({ + searchTerm: z.string(), + source: z.nativeEnum(RuleSource), + tags: z.array(z.string()), + enabled: z.boolean(), + ruleExecutionStatus: RuleExecutionStatus, + }) + .partial(); + +export type RulesTableSavedFilter = z.infer; + +export const RulesTableSavedSorting = SortingOptions.pick({ + field: true, + order: true, +}).partial(); + +export type RulesTableSavedSorting = z.infer; + +export const RulesTableStorageSavedPagination = PaginationOptions.pick({ + perPage: true, +}).partial(); + +export type RulesTableStorageSavedPagination = z.infer; + +export type RulesTableUrlSavedPagination = z.infer; +export const RulesTableUrlSavedPagination = PaginationOptions.pick({ + page: true, + perPage: true, +}).partial(); export type RulesTableStorageSavedState = RulesTableSavedFilter & RulesTableSavedSorting & diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_rules_table_saved_state.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_rules_table_saved_state.ts index bc1b28ee72a41..3055c9cbdcbba 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_rules_table_saved_state.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_rules_table_saved_state.ts @@ -6,7 +6,7 @@ */ import type { Storage } from '@kbn/kibana-utils-plugin/public'; -import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; +import { safeParseResult } from '@kbn/zod-helpers'; import { useGetInitialUrlParamValue } from '../../../../../common/utils/global_query_string/helpers'; import { RULES_TABLE_MAX_PAGE_SIZE } from '../../../../../../common/constants'; import { useKibana } from '../../../../../common/lib/kibana'; @@ -57,8 +57,8 @@ function validateState( urlState: RulesTableUrlSavedState | null, storageState: RulesTableStorageSavedState | null ): [RulesTableSavedFilter, RulesTableSavedSorting, RulesTableUrlSavedPagination] { - const [filterFromUrl] = validateNonExact(urlState, RulesTableSavedFilter); - const [filterFromStorage] = validateNonExact(storageState, RulesTableSavedFilter); + const filterFromUrl = safeParseResult(urlState, RulesTableSavedFilter); + const filterFromStorage = safeParseResult(storageState, RulesTableSavedFilter); // We have to expose filter, sorting and pagination objects by explicitly specifying each field // since urlState and/or storageState may contain unnecessary fields (e.g. outdated or explicitly added by user) // and validateNonExact doesn't truncate fields not included in the type RulesTableSavedFilter and etc. @@ -71,15 +71,15 @@ function validateState( filterFromUrl?.ruleExecutionStatus ?? filterFromStorage?.ruleExecutionStatus, }; - const [sortingFromUrl] = validateNonExact(urlState, RulesTableSavedSorting); - const [sortingFromStorage] = validateNonExact(storageState, RulesTableSavedSorting); + const sortingFromUrl = safeParseResult(urlState, RulesTableSavedSorting); + const sortingFromStorage = safeParseResult(storageState, RulesTableSavedSorting); const sorting = { field: sortingFromUrl?.field ?? sortingFromStorage?.field, order: sortingFromUrl?.order ?? sortingFromStorage?.order, - }; + } as const; - const [paginationFromUrl] = validateNonExact(urlState, RulesTableUrlSavedPagination); - const [paginationFromStorage] = validateNonExact(storageState, RulesTableStorageSavedPagination); + const paginationFromUrl = safeParseResult(urlState, RulesTableUrlSavedPagination); + const paginationFromStorage = safeParseResult(storageState, RulesTableStorageSavedPagination); const pagination = { page: paginationFromUrl?.page, // We don't persist page number in the session storage since it may be outdated when restored perPage: paginationFromUrl?.perPage ?? paginationFromStorage?.perPage, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index 3525793caa3a3..acf43ebf2c36e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -16,10 +16,7 @@ import { SecurityPageName, SHOW_RELATED_INTEGRATIONS_SETTING, } from '../../../../../common/constants'; -import type { - DurationMetric, - RuleExecutionSummary, -} from '../../../../../common/api/detection_engine/rule_monitoring'; +import type { RuleExecutionSummary } from '../../../../../common/api/detection_engine/rule_monitoring'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { RuleSnoozeBadge } from '../../../rule_management/components/rule_snooze_badge'; @@ -402,7 +399,7 @@ export const useMonitoringColumns = ({ tooltipContent={i18n.COLUMN_INDEXING_TIMES_TOOLTIP} /> ), - render: (value: DurationMetric | undefined) => ( + render: (value: number | undefined) => ( {value != null ? value.toFixed() : getEmptyTagValue()} @@ -419,7 +416,7 @@ export const useMonitoringColumns = ({ tooltipContent={i18n.COLUMN_QUERY_TIMES_TOOLTIP} /> ), - render: (value: DurationMetric | undefined) => ( + render: (value: number | undefined) => ( {value != null ? value.toFixed() : getEmptyTagValue()} @@ -459,7 +456,7 @@ export const useMonitoringColumns = ({ } /> ), - render: (value: DurationMetric | undefined) => ( + render: (value: number | undefined) => ( {value != null ? moment.duration(value, 'seconds').humanize() : getEmptyTagValue()} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx index 1d9d6ad45c8fa..04fc59da5e027 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx @@ -9,7 +9,7 @@ import type { DefaultItemAction } from '@elastic/eui'; import { EuiToolTip } from '@elastic/eui'; import React from 'react'; import { DuplicateOptions } from '../../../../../common/detection_engine/rule_management/constants'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useKibana } from '../../../../common/lib/kibana'; @@ -75,7 +75,7 @@ export const useRulesTableActions = ({ return; } const result = await executeBulkAction({ - type: BulkActionType.duplicate, + type: BulkActionTypeEnum.duplicate, ids: [rule.id], duplicatePayload: { include_exceptions: @@ -123,7 +123,7 @@ export const useRulesTableActions = ({ startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); await executeBulkAction({ - type: BulkActionType.delete, + type: BulkActionTypeEnum.delete, ids: [rule.id], }); }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx index 3a2424664f8ab..057a75d9a5a51 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx @@ -15,7 +15,7 @@ import React, { } from 'react'; import { invariant } from '../../../../../common/utils/invariant'; import { - BulkActionType, + BulkActionTypeEnum, CoverageOverviewRuleActivity, CoverageOverviewRuleSource, } from '../../../../../common/api/detection_engine'; @@ -114,7 +114,7 @@ export const CoverageOverviewDashboardContextProvider = ({ const enableAllDisabled = useCallback( async (ruleIds: string[]) => { - await executeBulkAction({ type: BulkActionType.enable, ids: ruleIds }); + await executeBulkAction({ type: BulkActionTypeEnum.enable, ids: ruleIds }); }, [executeBulkAction] ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts index b5b8f201f0ff3..a70a9bd66671b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts @@ -10,8 +10,8 @@ import type { GetRuleExecutionResultsResponse, } from '../../../../../common/api/detection_engine/rule_monitoring'; import { - LogLevel, - RuleExecutionEventType, + LogLevelEnum, + RuleExecutionEventTypeEnum, } from '../../../../../common/api/detection_engine/rule_monitoring'; import type { @@ -30,8 +30,8 @@ export const api: jest.Mocked = { { timestamp: '2021-12-29T10:42:59.996Z', sequence: 0, - level: LogLevel.info, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.info, + type: RuleExecutionEventTypeEnum['status-change'], execution_id: 'execution-id-1', message: 'Rule changed status to "succeeded". Rule execution completed without errors', }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts index d1317e2f74252..640cc1a86e423 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts @@ -12,11 +12,12 @@ import type { GetRuleExecutionResultsResponse, } from '../../../../common/api/detection_engine/rule_monitoring'; import { - LogLevel, - RuleExecutionEventType, + LogLevelEnum, + RuleExecutionEventTypeEnum, } from '../../../../common/api/detection_engine/rule_monitoring'; import { api } from './api_client'; +import type { FetchRuleExecutionEventsArgs } from './api_client_interface'; jest.mock('../../../common/lib/kibana'); @@ -74,7 +75,7 @@ describe('Rule Monitoring API Client', () => { const ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; - it.each([ + it.each<[string, Omit, Record]>([ [ 'search term filter', { searchTerm: 'something to search' }, @@ -82,12 +83,12 @@ describe('Rule Monitoring API Client', () => { ], [ 'event types filter', - { eventTypes: [RuleExecutionEventType.message] }, + { eventTypes: [RuleExecutionEventTypeEnum.message] }, { event_types: 'message' }, ], [ 'log level filter', - { logLevels: [LogLevel.warn, LogLevel.error] }, + { logLevels: [LogLevelEnum.warn, LogLevelEnum.error] }, { log_levels: 'warn,error' }, ], [ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/event_type_filter/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/event_type_filter/index.tsx index 2c87a184f5b1c..5edc079ef5c42 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/event_type_filter/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/event_type_filter/index.tsx @@ -7,8 +7,7 @@ import React, { useCallback } from 'react'; -import type { RuleExecutionEventType } from '../../../../../../../common/api/detection_engine/rule_monitoring'; -import { RULE_EXECUTION_EVENT_TYPES } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import { RuleExecutionEventType } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { EventTypeIndicator } from '../../indicators/event_type_indicator'; import { MultiselectFilter } from '../multiselect_filter'; @@ -28,7 +27,7 @@ const EventTypeFilterComponent: React.FC = ({ selectedItem dataTestSubj="eventTypeFilter" title={i18n.FILTER_TITLE} - items={RULE_EXECUTION_EVENT_TYPES} + items={RuleExecutionEventType.options} selectedItems={selectedItems} onSelectionChange={onChange} renderItem={renderItem} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/event_type_indicator/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/event_type_indicator/utils.ts index 07b3b3a6b096a..9e86215228078 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/event_type_indicator/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/event_type_indicator/utils.ts @@ -6,18 +6,19 @@ */ import type { IconType } from '@elastic/eui'; -import { RuleExecutionEventType } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import type { RuleExecutionEventType } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import { RuleExecutionEventTypeEnum } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { assertUnreachable } from '../../../../../../../common/utility_types'; import * as i18n from './translations'; export const getBadgeIcon = (type: RuleExecutionEventType): IconType => { switch (type) { - case RuleExecutionEventType.message: + case RuleExecutionEventTypeEnum.message: return 'console'; - case RuleExecutionEventType['status-change']: + case RuleExecutionEventTypeEnum['status-change']: return 'dot'; - case RuleExecutionEventType['execution-metrics']: + case RuleExecutionEventTypeEnum['execution-metrics']: return 'gear'; default: return assertUnreachable(type, 'Unknown rule execution event type'); @@ -26,11 +27,11 @@ export const getBadgeIcon = (type: RuleExecutionEventType): IconType => { export const getBadgeText = (type: RuleExecutionEventType): string => { switch (type) { - case RuleExecutionEventType.message: + case RuleExecutionEventTypeEnum.message: return i18n.TYPE_MESSAGE; - case RuleExecutionEventType['status-change']: + case RuleExecutionEventTypeEnum['status-change']: return i18n.TYPE_STATUS_CHANGE; - case RuleExecutionEventType['execution-metrics']: + case RuleExecutionEventTypeEnum['execution-metrics']: return i18n.TYPE_EXECUTION_METRICS; default: return assertUnreachable(type, 'Unknown rule execution event type'); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/log_level_indicator/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/log_level_indicator/utils.ts index 639c648de0241..702d3edddda5b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/log_level_indicator/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/log_level_indicator/utils.ts @@ -7,20 +7,21 @@ import { upperCase } from 'lodash'; import type { IconColor } from '@elastic/eui'; -import { LogLevel } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import type { LogLevel } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import { LogLevelEnum } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { assertUnreachable } from '../../../../../../../common/utility_types'; export const getBadgeColor = (logLevel: LogLevel): IconColor => { switch (logLevel) { - case LogLevel.trace: + case LogLevelEnum.trace: return 'hollow'; - case LogLevel.debug: + case LogLevelEnum.debug: return 'hollow'; - case LogLevel.info: + case LogLevelEnum.info: return 'default'; - case LogLevel.warn: + case LogLevelEnum.warn: return 'warning'; - case LogLevel.error: + case LogLevelEnum.error: return 'danger'; default: return assertUnreachable(logLevel, 'Unknown log level'); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/tables/use_sorting.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/tables/use_sorting.ts index 39e48c3997478..5fb9c0fb32215 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/tables/use_sorting.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/tables/use_sorting.ts @@ -11,11 +11,18 @@ import type { SortOrder } from '../../../../../../common/api/detection_engine'; type TableItem = Record; +interface SortingState { + sort: { + field: keyof T; + direction: SortOrder; + }; +} + export const useSorting = (defaultField: keyof T, defaultOrder: SortOrder) => { const [sortField, setSortField] = useState(defaultField); const [sortOrder, setSortOrder] = useState(defaultOrder); - const state = useMemo(() => { + const state = useMemo>(() => { return { sort: { field: sortField, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx index 866e0e44b6c77..5459968b6c497 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx @@ -10,8 +10,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook, cleanup } from '@testing-library/react-hooks'; import { - LogLevel, - RuleExecutionEventType, + LogLevelEnum, + RuleExecutionEventTypeEnum, } from '../../../../../common/api/detection_engine/rule_monitoring'; import { useExecutionEvents } from './use_execution_events'; @@ -85,8 +85,8 @@ describe('useExecutionEvents', () => { { timestamp: '2021-12-29T10:42:59.996Z', sequence: 0, - level: LogLevel.info, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.info, + type: RuleExecutionEventTypeEnum['status-change'], execution_id: 'execution-id-1', message: 'Rule changed status to "succeeded". Rule execution completed without errors', }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/constants.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/constants.ts index d99be0c13e70b..19d6111b5a936 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/constants.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/constants.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { RESPONSE_ACTION_TYPES } from '../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../common/api/detection_engine/model/rule_response_actions'; export const getActionDetails = (actionTypeId: string) => { switch (actionTypeId) { - case RESPONSE_ACTION_TYPES.OSQUERY: + case ResponseActionTypesEnum['.osquery']: return { logo: 'logoOsquery', name: 'Osquery' }; - case RESPONSE_ACTION_TYPES.ENDPOINT: + case ResponseActionTypesEnum['.endpoint']: return { logo: 'logoSecurity', name: 'Endpoint Security' }; // update when new responseActions are provided default: diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts index ad3e3f8392eb1..e8afdd91d1ff3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts @@ -6,10 +6,9 @@ */ import type { EnabledFeatures } from '@kbn/spaces-plugin/public/management/edit_space/enabled_features'; -import type { ResponseActionTypes } from '../../../common/api/detection_engine/model/rule_response_actions'; import { - RESPONSE_ACTION_TYPES, - SUPPORTED_RESPONSE_ACTION_TYPES, + ResponseActionTypes, + ResponseActionTypesEnum, } from '../../../common/api/detection_engine/model/rule_response_actions'; export interface ResponseActionType { @@ -29,9 +28,9 @@ export const getSupportedResponseActions = ( userPermissions: EnabledFeatures ): ResponseActionType[] => actionTypes.reduce((acc: ResponseActionType[], actionType) => { - const isEndpointAction = actionType.id === RESPONSE_ACTION_TYPES.ENDPOINT; + const isEndpointAction = actionType.id === ResponseActionTypesEnum['.endpoint']; if (!enabledFeatures.endpoint && isEndpointAction) return acc; - if (SUPPORTED_RESPONSE_ACTION_TYPES.includes(actionType.id)) + if (ResponseActionTypes.options.includes(actionType.id)) return [ ...acc, { ...actionType, disabled: isEndpointAction ? !userPermissions.endpoint : undefined }, @@ -39,14 +38,14 @@ export const getSupportedResponseActions = ( return acc; }, []); -export const responseActionTypes = [ +export const responseActionTypes: ResponseActionType[] = [ { - id: RESPONSE_ACTION_TYPES.OSQUERY, + id: ResponseActionTypesEnum['.osquery'], name: 'Osquery', iconClass: 'logoOsquery', }, { - id: RESPONSE_ACTION_TYPES.ENDPOINT, + id: ResponseActionTypesEnum['.endpoint'], name: 'Endpoint Security', iconClass: 'logoSecurity', }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_action_type_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_action_type_form.tsx index 7b176b96c2948..97f3e932e81fe 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_action_type_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_action_type_form.tsx @@ -21,7 +21,7 @@ import styled from 'styled-components'; import { useCheckEndpointPermissions } from './endpoint/check_permissions'; import { EndpointResponseAction } from './endpoint/endpoint_response_action'; import type { RuleResponseAction } from '../../../common/api/detection_engine/model/rule_response_actions'; -import { RESPONSE_ACTION_TYPES } from '../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../common/api/detection_engine/model/rule_response_actions'; import { OsqueryResponseAction } from './osquery/osquery_response_action'; import { getActionDetails } from './constants'; import { useFormData } from '../../shared_imports'; @@ -48,10 +48,10 @@ const ResponseActionTypeFormComponent = ({ item, onDeleteAction }: ResponseActio const editDisabled = useCheckEndpointPermissions(action) ?? false; const getResponseActionTypeForm = useMemo(() => { - if (action?.actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY) { + if (action?.actionTypeId === ResponseActionTypesEnum['.osquery']) { return ; } - if (action?.actionTypeId === RESPONSE_ACTION_TYPES.ENDPOINT) { + if (action?.actionTypeId === ResponseActionTypesEnum['.endpoint']) { return ; } // Place for other ResponseActionTypes diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/utils.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/utils.tsx index 5b1e57e6386f4..22d190d80b9c4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/utils.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/utils.tsx @@ -11,7 +11,7 @@ import { filter, reduce } from 'lodash'; import type { ECSMapping } from '@kbn/osquery-io-ts-types'; import type { RuleResponseAction } from '../../../common/api/detection_engine/model/rule_response_actions'; -import { RESPONSE_ACTION_TYPES } from '../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../common/api/detection_engine/model/rule_response_actions'; import { OsqueryParser } from '../../common/components/markdown_editor/plugins/osquery/parser'; interface OsqueryNoteQuery { @@ -38,7 +38,7 @@ export const getResponseActionsFromNote = ( (acc: { responseActions: RuleResponseAction[] }, { configuration }: OsqueryNoteQuery) => { const responseActionPath = 'responseActions'; acc[responseActionPath].push({ - actionTypeId: RESPONSE_ACTION_TYPES.OSQUERY, + actionTypeId: ResponseActionTypesEnum['.osquery'], params: { savedQueryId: undefined, packId: undefined, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx index 50c8a5bf50d1b..9a351af0803c7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx @@ -16,7 +16,7 @@ import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { APP_UI_ID, SecurityPageName } from '../../../../../common/constants'; import { DuplicateOptions } from '../../../../../common/detection_engine/rule_management/constants'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; @@ -94,7 +94,7 @@ const RuleActionsOverflowComponent = ({ return; } const result = await executeBulkAction({ - type: BulkActionType.duplicate, + type: BulkActionTypeEnum.duplicate, ids: [rule.id], duplicatePayload: { include_exceptions: @@ -156,7 +156,7 @@ const RuleActionsOverflowComponent = ({ startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); await executeBulkAction({ - type: BulkActionType.delete, + type: BulkActionTypeEnum.delete, ids: [rule.id], }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx index 7e4881800f738..35434a711768e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx @@ -9,7 +9,7 @@ import type { EuiSwitchEvent } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSwitch } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useExecuteBulkAction } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action'; @@ -60,7 +60,7 @@ export const RuleSwitchComponent = ({ await startMlJobsIfNeeded?.(); } const bulkActionResponse = await executeBulkAction({ - type: enableRule ? BulkActionType.enable : BulkActionType.disable, + type: enableRule ? BulkActionTypeEnum.enable : BulkActionTypeEnum.disable, ids: [id], }); if (bulkActionResponse?.attributes.results.updated.length) { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts index a370f2a89cb6f..fb1285fb89f05 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts @@ -15,7 +15,7 @@ import { visitRuleActions, } from '../../tasks/response_actions'; import { cleanupRule, generateRandomStringName, loadRule } from '../../tasks/api_fixtures'; -import { RESPONSE_ACTION_TYPES } from '../../../../../common/api/detection_engine'; +import { ResponseActionTypesEnum } from '../../../../../common/api/detection_engine'; import { login, ROLE } from '../../tasks/login'; describe( @@ -78,7 +78,7 @@ describe( cy.getByTestSubj(`command-type-${testedCommand}`).click(); cy.intercept('POST', '/api/detection_engine/rules', (request) => { const result = { - action_type_id: RESPONSE_ACTION_TYPES.ENDPOINT, + action_type_id: ResponseActionTypesEnum['.endpoint'], params: { command: testedCommand, comment: 'example1', @@ -127,7 +127,7 @@ describe( cy.getByTestSubj('ruleEditSubmitButton').click(); cy.wait('@updateResponseAction').should(({ request }) => { const query = { - action_type_id: RESPONSE_ACTION_TYPES.ENDPOINT, + action_type_id: ResponseActionTypesEnum['.endpoint'], params: { command: testedCommand, comment: newDescription, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.test.ts index 73350b48941db..0ec1d5580f40b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.test.ts @@ -17,7 +17,7 @@ describe('Prebuilt rule asset schema', () => { const result = PrebuiltRuleAsset.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toMatchInlineSnapshot( - `"name: Required, description: Required, risk_score: Required, severity: Required, Invalid input, rule_id: Required, version: Required"` + `"name: Required, description: Required, risk_score: Required, severity: Required, rule_id: Required, and 26 more"` ); }); @@ -40,7 +40,7 @@ describe('Prebuilt rule asset schema', () => { const result = PrebuiltRuleAsset.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toMatchInlineSnapshot( - `"name: Required, description: Required, risk_score: Required, severity: Required, Invalid input, version: Required"` + `"name: Required, description: Required, risk_score: Required, severity: Required, version: Required, and 25 more"` ); }); @@ -176,7 +176,9 @@ describe('Prebuilt rule asset schema', () => { const result = PrebuiltRuleAsset.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", index.0: Expected string, received number, index.0: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", and 20 more"` + ); }); test('saved_query type can have filters with it', () => { @@ -198,7 +200,9 @@ describe('Prebuilt rule asset schema', () => { const result = PrebuiltRuleAsset.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", filters: Expected array, received string, filters: Expected array, received string, type: Invalid literal value, expected \\"saved_query\\", and 20 more"` + ); }); test('language validates with kuery', () => { @@ -231,7 +235,9 @@ describe('Prebuilt rule asset schema', () => { const result = PrebuiltRuleAsset.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 19 more"` + ); }); test('max_signals cannot be negative', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts index 428010033c5b2..cd01a251a3c75 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts @@ -486,7 +486,7 @@ describe('Perform bulk action route', () => { }); const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "undefined" supplied to "action",Invalid value "undefined" supplied to "edit"' + 'action: Invalid literal value, expected "delete", action: Invalid literal value, expected "disable", action: Invalid literal value, expected "enable", action: Invalid literal value, expected "export", action: Invalid literal value, expected "duplicate", and 2 more' ); }); @@ -498,7 +498,7 @@ describe('Perform bulk action route', () => { }); const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "unknown" supplied to "action",Invalid value "undefined" supplied to "edit"' + 'action: Invalid literal value, expected "delete", action: Invalid literal value, expected "disable", action: Invalid literal value, expected "enable", action: Invalid literal value, expected "export", action: Invalid literal value, expected "duplicate", and 2 more' ); }); @@ -531,7 +531,9 @@ describe('Perform bulk action route', () => { body: { ...getPerformBulkActionSchemaMock(), ids: 'test fake' }, }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid value "test fake" supplied to "ids"'); + expect(result.badRequest).toHaveBeenCalledWith( + 'ids: Expected array, received string, action: Invalid literal value, expected "delete", ids: Expected array, received string, ids: Expected array, received string, action: Invalid literal value, expected "enable", and 7 more' + ); }); it('rejects payload if there is more than 100 ids in payload', async () => { @@ -577,7 +579,9 @@ describe('Perform bulk action route', () => { body: { ...getPerformBulkActionSchemaMock(), ids: [] }, }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid value "[]" supplied to "ids"'); + expect(result.badRequest).toHaveBeenCalledWith( + 'ids: Array must contain at least 1 element(s)' + ); }); it('rejects payloads if property "edit" actions is empty', async () => { @@ -588,7 +592,7 @@ describe('Perform bulk action route', () => { }); const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - expect.stringContaining('Invalid value "[]" supplied to "edit"') + expect.stringContaining('edit: Array must contain at least 1 element(s)') ); }); @@ -601,7 +605,9 @@ describe('Perform bulk action route', () => { }); const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - expect.stringContaining('Invalid value "invalid" supplied to "dry_run"') + expect.stringContaining( + "dry_run: Invalid enum value. Expected 'true' | 'false', received 'invalid', dry_run: Expected boolean, received string" + ) ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts index 14022e9e44af2..8af5eeaa1a021 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts @@ -20,22 +20,24 @@ import { MAX_RULES_TO_UPDATE_IN_PARALLEL, RULES_TABLE_MAX_PAGE_SIZE, } from '../../../../../../../common/constants'; -import type { PerformBulkActionResponse } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { + BulkEditActionResponse, + PerformBulkActionResponse, +} from '../../../../../../../common/api/detection_engine/rule_management'; import { - BulkActionType, + BulkActionTypeEnum, PerformBulkActionRequestBody, PerformBulkActionRequestQuery, -} from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +} from '../../../../../../../common/api/detection_engine/rule_management'; import type { NormalizedRuleError, RuleDetailsInError, - BulkEditActionResponse, BulkEditActionResults, BulkEditActionSummary, } from '../../../../../../../common/api/detection_engine'; import type { SetupPlugins } from '../../../../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { routeLimitedConcurrencyTag } from '../../../../../../utils/route_limited_concurrency_tag'; import type { PromisePoolError, PromisePoolOutcome } from '../../../../../../utils/promise_pool'; import { initPromisePool } from '../../../../../../utils/promise_pool'; @@ -249,8 +251,8 @@ export const performBulkActionRoute = ( version: '2023-10-31', validate: { request: { - body: buildRouteValidation(PerformBulkActionRequestBody), - query: buildRouteValidation(PerformBulkActionRequestQuery), + body: buildRouteValidationWithZod(PerformBulkActionRequestBody), + query: buildRouteValidationWithZod(PerformBulkActionRequestQuery), }, }, }, @@ -272,10 +274,10 @@ export const performBulkActionRoute = ( }); } - const isDryRun = request.query.dry_run === 'true'; + const isDryRun = request.query.dry_run; // dry run is not supported for export, as it doesn't change ES state and has different response format(exported JSON file) - if (isDryRun && body.action === BulkActionType.export) { + if (isDryRun && body.action === BulkActionTypeEnum.export) { return siemResponse.error({ body: `Export action doesn't support dry_run mode`, statusCode: 400, @@ -318,7 +320,7 @@ export const performBulkActionRoute = ( // handling this action before switch statement as bulkEditRules fetch rules within // rulesClient method, hence there is no need to use fetchRulesByQueryOrIds utility - if (body.action === BulkActionType.edit && !isDryRun) { + if (body.action === BulkActionTypeEnum.edit && !isDryRun) { const { rules, errors, skipped } = await bulkEditRules({ rulesClient, filter: query, @@ -348,7 +350,7 @@ export const performBulkActionRoute = ( let deleted: RuleAlertType[] = []; switch (body.action) { - case BulkActionType.enable: + case BulkActionTypeEnum.enable: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -375,7 +377,7 @@ export const performBulkActionRoute = ( .map(({ result }) => result) .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkActionType.disable: + case BulkActionTypeEnum.disable: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -403,7 +405,7 @@ export const performBulkActionRoute = ( .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkActionType.delete: + case BulkActionTypeEnum.delete: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -427,7 +429,7 @@ export const performBulkActionRoute = ( .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -486,7 +488,7 @@ export const performBulkActionRoute = ( .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkActionType.export: + case BulkActionTypeEnum.export: const exported = await getExportByObjectIds( rulesClient, exceptionsClient, @@ -510,7 +512,7 @@ export const performBulkActionRoute = ( // will be processed only when isDryRun === true // during dry run only validation is getting performed and rule is not saved in ES - case BulkActionType.edit: + case BulkActionTypeEnum.edit: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts index fc3d87d32b432..ca3cde890b738 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts @@ -191,7 +191,9 @@ describe('Bulk patch rules route', () => { }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('0: Invalid input'); + expect(result.badRequest).toHaveBeenCalledWith( + '0.type: Invalid literal value, expected "eql", 0.language: Invalid literal value, expected "eql", 0.type: Invalid literal value, expected "query", 0.type: Invalid literal value, expected "saved_query", 0.type: Invalid literal value, expected "threshold", and 5 more' + ); }); test('allows rule type of query and custom from and interval', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts index 5fed0b4e3446a..a1d74b1445508 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts @@ -236,7 +236,9 @@ describe('Create rule route', () => { }, }); const result = await server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid input'); + expect(result.badRequest).toHaveBeenCalledWith( + 'type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "saved_query", saved_id: Required, type: Invalid literal value, expected "threshold", and 18 more' + ); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts index 76d63ddcd54b0..b9a68994a0e58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts @@ -76,7 +76,7 @@ describe('Find rules route', () => { expect(result.ok).toHaveBeenCalled(); }); - test('rejects unknown query params', async () => { + test('ignores unknown query params', async () => { const request = requestMock.create({ method: 'get', path: DETECTION_ENGINE_RULES_URL_FIND, @@ -86,7 +86,7 @@ describe('Find rules route', () => { }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('invalid keys "invalid_value"'); + expect(result.ok).toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts index 76496d26cb856..3cbd164586a9d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts @@ -18,7 +18,7 @@ import { import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import { findRules } from '../../../logic/search/find_rules'; import { buildSiemResponse } from '../../../../routes/utils'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { transformFindAlerts } from '../../../utils/utils'; export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { @@ -35,7 +35,7 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log version: '2023-10-31', validate: { request: { - query: buildRouteValidation(FindRulesRequestQuery), + query: buildRouteValidationWithZod(FindRulesRequestQuery), }, }, }, @@ -63,11 +63,7 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log }); const transformed = transformFindAlerts(rules); - if (transformed == null) { - return siemResponse.error({ statusCode: 500, body: 'Internal error transforming' }); - } else { - return response.ok({ body: transformed ?? {} }); - } + return response.ok({ body: transformed ?? {} }); } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts index 677556f314239..1255287cf52f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts @@ -199,7 +199,9 @@ describe('Patch rule route', () => { }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid input'); + expect(result.badRequest).toHaveBeenCalledWith( + 'type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "query", type: Invalid literal value, expected "saved_query", type: Invalid literal value, expected "threshold", and 5 more' + ); }); test('allows rule type of query and custom from and interval', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts index e580f5cc11662..f95b10fa6154f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts @@ -23,7 +23,7 @@ import { getUpdateRulesSchemaMock, } from '../../../../../../../common/api/detection_engine/model/rule_schema/mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; -import { RESPONSE_ACTION_TYPES } from '../../../../../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../../../../../common/api/detection_engine/model/rule_response_actions'; jest.mock('../../../../../machine_learning/authz'); @@ -245,7 +245,7 @@ describe('Update rule route', () => { ...getQueryRuleParams(), responseActions: [ { - actionTypeId: RESPONSE_ACTION_TYPES.ENDPOINT, + actionTypeId: ResponseActionTypesEnum['.endpoint'], params: { command: 'isolate', comment: '', @@ -283,7 +283,9 @@ describe('Update rule route', () => { }, }); const result = await server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid input'); + expect(result.badRequest).toHaveBeenCalledWith( + 'type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "saved_query", saved_id: Required, type: Invalid literal value, expected "threshold", and 18 more' + ); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts index e598715e8f9ec..e214b7dc3b341 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts @@ -5,13 +5,16 @@ * 2.0. */ -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import { bulkEditActionToRulesClientOperation } from './action_to_rules_client_operation'; describe('bulkEditActionToRulesClientOperation', () => { test('should transform tags bulk edit actions correctly', () => { expect( - bulkEditActionToRulesClientOperation({ type: BulkActionEditType.add_tags, value: ['test'] }) + bulkEditActionToRulesClientOperation({ + type: BulkActionEditTypeEnum.add_tags, + value: ['test'], + }) ).toEqual([ { field: 'tags', @@ -22,7 +25,7 @@ describe('bulkEditActionToRulesClientOperation', () => { }); expect( - bulkEditActionToRulesClientOperation({ type: BulkActionEditType.set_tags, value: ['test'] }) + bulkEditActionToRulesClientOperation({ type: BulkActionEditTypeEnum.set_tags, value: ['test'] }) ).toEqual([ { field: 'tags', @@ -32,7 +35,10 @@ describe('bulkEditActionToRulesClientOperation', () => { ]); expect( - bulkEditActionToRulesClientOperation({ type: BulkActionEditType.delete_tags, value: ['test'] }) + bulkEditActionToRulesClientOperation({ + type: BulkActionEditTypeEnum.delete_tags, + value: ['test'], + }) ).toEqual([ { field: 'tags', @@ -44,7 +50,7 @@ describe('bulkEditActionToRulesClientOperation', () => { test('should transform schedule bulk edit correctly', () => { expect( bulkEditActionToRulesClientOperation({ - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: '100m', lookback: '10m', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts index dfd4ee64c0787..eac694f97944b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts @@ -8,8 +8,8 @@ import type { BulkEditOperation } from '@kbn/alerting-plugin/server'; import { transformNormalizedRuleToAlertAction } from '../../../../../../common/detection_engine/transform_actions'; -import type { BulkActionEditForRuleAttributes } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditForRuleAttributes } from '../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import { assertUnreachable } from '../../../../../../common/utility_types'; import { transformToActionFrequency } from '../../normalization/rule_actions'; @@ -23,7 +23,7 @@ export const bulkEditActionToRulesClientOperation = ( ): BulkEditOperation[] => { switch (action.type) { // tags actions - case BulkActionEditType.add_tags: + case BulkActionEditTypeEnum.add_tags: return [ { field: 'tags', @@ -32,7 +32,7 @@ export const bulkEditActionToRulesClientOperation = ( }, ]; - case BulkActionEditType.delete_tags: + case BulkActionEditTypeEnum.delete_tags: return [ { field: 'tags', @@ -41,7 +41,7 @@ export const bulkEditActionToRulesClientOperation = ( }, ]; - case BulkActionEditType.set_tags: + case BulkActionEditTypeEnum.set_tags: return [ { field: 'tags', @@ -51,7 +51,7 @@ export const bulkEditActionToRulesClientOperation = ( ]; // rule actions - case BulkActionEditType.add_rule_actions: + case BulkActionEditTypeEnum.add_rule_actions: return [ { field: 'actions', @@ -62,7 +62,7 @@ export const bulkEditActionToRulesClientOperation = ( }, ]; - case BulkActionEditType.set_rule_actions: + case BulkActionEditTypeEnum.set_rule_actions: return [ { field: 'actions', @@ -74,7 +74,7 @@ export const bulkEditActionToRulesClientOperation = ( ]; // schedule actions - case BulkActionEditType.set_schedule: + case BulkActionEditTypeEnum.set_schedule: return [ { field: 'schedule', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts index 76034819b508d..fd2f1644480c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts @@ -7,7 +7,7 @@ import type { RulesClient } from '@kbn/alerting-plugin/server'; -import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management'; import type { MlAuthz } from '../../../../machine_learning/authz'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts index 0337558099532..93044fc0fed18 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts @@ -6,7 +6,7 @@ */ import { addItemsToArray, deleteItemsFromArray, ruleParamsModifier } from './rule_params_modifier'; -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import type { RuleAlertType } from '../../../rule_schema'; describe('addItemsToArray', () => { @@ -47,7 +47,7 @@ describe('ruleParamsModifier', () => { test('should increment version if rule is custom (immutable === false)', () => { const { modifiedParams } = ruleParamsModifier(ruleParamsMock, [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['my-index-*'], }, ]); @@ -57,7 +57,7 @@ describe('ruleParamsModifier', () => { test('should not increment version if rule is prebuilt (immutable === true)', () => { const { modifiedParams } = ruleParamsModifier({ ...ruleParamsMock, immutable: true }, [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['my-index-*'], }, ]); @@ -130,7 +130,7 @@ describe('ruleParamsModifier', () => { { ...ruleParamsMock, index: existingIndexPatterns } as RuleAlertType['params'], [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: indexPatternsToAdd, }, ] @@ -194,7 +194,7 @@ describe('ruleParamsModifier', () => { { ...ruleParamsMock, index: existingIndexPatterns } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: indexPatternsToDelete, }, ] @@ -249,7 +249,7 @@ describe('ruleParamsModifier', () => { { ...ruleParamsMock, index: existingIndexPatterns } as RuleAlertType['params'], [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: indexPatternsToOverwrite, }, ] @@ -267,7 +267,7 @@ describe('ruleParamsModifier', () => { { dataViewId: testDataViewId } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['index-2-*'], }, ] @@ -281,7 +281,7 @@ describe('ruleParamsModifier', () => { { dataViewId: 'test-data-view', index: ['test-*'] } as RuleAlertType['params'], [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['index'], overwrite_data_views: true, }, @@ -296,7 +296,7 @@ describe('ruleParamsModifier', () => { { dataViewId: 'test-data-view', index: ['test-*'] } as RuleAlertType['params'], [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index'], overwrite_data_views: true, }, @@ -311,7 +311,7 @@ describe('ruleParamsModifier', () => { { dataViewId: 'test-data-view', index: ['test-*', 'index'] } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['index'], overwrite_data_views: true, }, @@ -327,7 +327,7 @@ describe('ruleParamsModifier', () => { { dataViewId: 'test-data-view', index: undefined } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['index'], overwrite_data_views: true, }, @@ -342,7 +342,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'machine_learning' } as RuleAlertType['params'], [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['my-index-*'], }, ]) @@ -355,7 +355,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'machine_learning' } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['my-index-*'], }, ]) @@ -368,7 +368,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'machine_learning' } as RuleAlertType['params'], [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['my-index-*'], }, ]) @@ -381,7 +381,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'esql' } as RuleAlertType['params'], [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['my-index-*'], }, ]) @@ -392,7 +392,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'esql' } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['my-index-*'], }, ]) @@ -403,7 +403,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'esql' } as RuleAlertType['params'], [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['my-index-*'], }, ]) @@ -417,7 +417,7 @@ describe('ruleParamsModifier', () => { test('should set timeline', () => { const { modifiedParams, isParamsUpdateSkipped } = ruleParamsModifier(ruleParamsMock, [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: '91832785-286d-4ebe-b884-1a208d111a70', timeline_title: 'Test timeline', @@ -438,7 +438,7 @@ describe('ruleParamsModifier', () => { const FROM_IN_SECONDS = (INTERVAL_IN_MINUTES + LOOKBACK_IN_MINUTES) * 60; const { modifiedParams, isParamsUpdateSkipped } = ruleParamsModifier(ruleParamsMock, [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: `${INTERVAL_IN_MINUTES}m`, lookback: `${LOOKBACK_IN_MINUTES}m`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts index a519aee713bec..2994d2bf7f157 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts @@ -12,8 +12,8 @@ import type { RuleAlertType } from '../../../rule_schema'; import type { BulkActionEditForRuleParams, BulkActionEditPayloadIndexPatterns, -} from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +} from '../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import { invariant } from '../../../../../../common/utils/invariant'; export const addItemsToArray = (arr: T[], items: T[]): T[] => @@ -52,11 +52,11 @@ const shouldSkipIndexPatternsBulkAction = ( return true; } - if (action.type === BulkActionEditType.add_index_patterns) { + if (action.type === BulkActionEditTypeEnum.add_index_patterns) { return hasIndexPatterns(indexPatterns, action); } - if (action.type === BulkActionEditType.delete_index_patterns) { + if (action.type === BulkActionEditTypeEnum.delete_index_patterns) { return hasNotIndexPattern(indexPatterns, action); } @@ -80,7 +80,7 @@ const applyBulkActionEditToRuleParams = ( switch (action.type) { // index_patterns actions // index pattern is not present in machine learning rule type, so we throw error on it - case BulkActionEditType.add_index_patterns: { + case BulkActionEditTypeEnum.add_index_patterns: { invariant( ruleParams.type !== 'machine_learning', "Index patterns can't be added. Machine learning rule doesn't have index patterns property" @@ -102,7 +102,7 @@ const applyBulkActionEditToRuleParams = ( ruleParams.index = addItemsToArray(ruleParams.index ?? [], action.value); break; } - case BulkActionEditType.delete_index_patterns: { + case BulkActionEditTypeEnum.delete_index_patterns: { invariant( ruleParams.type !== 'machine_learning', "Index patterns can't be deleted. Machine learning rule doesn't have index patterns property" @@ -129,7 +129,7 @@ const applyBulkActionEditToRuleParams = ( } break; } - case BulkActionEditType.set_index_patterns: { + case BulkActionEditTypeEnum.set_index_patterns: { invariant( ruleParams.type !== 'machine_learning', "Index patterns can't be overwritten. Machine learning rule doesn't have index patterns property" @@ -152,7 +152,7 @@ const applyBulkActionEditToRuleParams = ( break; } // timeline actions - case BulkActionEditType.set_timeline: { + case BulkActionEditTypeEnum.set_timeline: { ruleParams = { ...ruleParams, timelineId: action.value.timeline_id || undefined, @@ -162,7 +162,7 @@ const applyBulkActionEditToRuleParams = ( break; } // update look-back period in from and meta.from fields - case BulkActionEditType.set_schedule: { + case BulkActionEditTypeEnum.set_schedule: { const interval = parseInterval(action.value.interval) ?? moment.duration(0); const parsedFrom = parseInterval(action.value.lookback) ?? moment.duration(0); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts index 5bde6c29e6082..cdaa6ed1afb80 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts @@ -5,20 +5,20 @@ * 2.0. */ -import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import { splitBulkEditActions } from './split_bulk_edit_actions'; const bulkEditActions: BulkActionEditPayload[] = [ - { type: BulkActionEditType.add_index_patterns, value: ['test'] }, - { type: BulkActionEditType.set_index_patterns, value: ['test'] }, - { type: BulkActionEditType.delete_index_patterns, value: ['test'] }, - { type: BulkActionEditType.add_tags, value: ['test'] }, - { type: BulkActionEditType.delete_tags, value: ['test'] }, - { type: BulkActionEditType.set_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.add_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.set_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.delete_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.add_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.delete_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.set_tags, value: ['test'] }, { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: 'a-1', timeline_title: 'Test title' }, }, ]; @@ -28,16 +28,16 @@ describe('splitBulkEditActions', () => { const { attributesActions, paramsActions } = splitBulkEditActions(bulkEditActions); expect(attributesActions).toEqual([ - { type: BulkActionEditType.add_tags, value: ['test'] }, - { type: BulkActionEditType.delete_tags, value: ['test'] }, - { type: BulkActionEditType.set_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.add_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.delete_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.set_tags, value: ['test'] }, ]); expect(paramsActions).toEqual([ - { type: BulkActionEditType.add_index_patterns, value: ['test'] }, - { type: BulkActionEditType.set_index_patterns, value: ['test'] }, - { type: BulkActionEditType.delete_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.add_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.set_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.delete_index_patterns, value: ['test'] }, { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: 'a-1', timeline_title: 'Test title' }, }, ]); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts index 2896acbea0e85..da626722155ed 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import type { BulkActionEditPayload, BulkActionEditForRuleAttributes, BulkActionEditForRuleParams, -} from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +} from '../../../../../../common/api/detection_engine/rule_management'; /** * Split bulk edit actions in 2 chunks: actions applied to params and @@ -29,15 +29,15 @@ export const splitBulkEditActions = (actions: BulkActionEditPayload[]) => { return actions.reduce((acc, action) => { switch (action.type) { - case BulkActionEditType.set_schedule: + case BulkActionEditTypeEnum.set_schedule: acc.attributesActions.push(action); acc.paramsActions.push(action); break; - case BulkActionEditType.add_tags: - case BulkActionEditType.set_tags: - case BulkActionEditType.delete_tags: - case BulkActionEditType.add_rule_actions: - case BulkActionEditType.set_rule_actions: + case BulkActionEditTypeEnum.add_tags: + case BulkActionEditTypeEnum.set_tags: + case BulkActionEditTypeEnum.delete_tags: + case BulkActionEditTypeEnum.add_rule_actions: + case BulkActionEditTypeEnum.set_rule_actions: acc.attributesActions.push(action); break; default: diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts index 214fc16b40a49..d624d9033f299 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; /** * helper utility that defines whether bulk edit action is related to index patterns, i.e. one of: @@ -14,7 +15,7 @@ import { BulkActionEditType } from '../../../../../../common/api/detection_engin */ export const isIndexPatternsBulkEditAction = (editAction: BulkActionEditType) => [ - BulkActionEditType.add_index_patterns, - BulkActionEditType.delete_index_patterns, - BulkActionEditType.set_index_patterns, + BulkActionEditTypeEnum.add_index_patterns, + BulkActionEditTypeEnum.delete_index_patterns, + BulkActionEditTypeEnum.set_index_patterns, ].includes(editAction); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts index fc8d13c27c567..4a1aef9ed28d7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts @@ -10,8 +10,8 @@ import { invariant } from '../../../../../../common/utils/invariant'; import { isMlRule } from '../../../../../../common/machine_learning/helpers'; import { isEsqlRule } from '../../../../../../common/detection_engine/utils'; import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; -import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import type { RuleAlertType } from '../../../rule_schema'; import { isIndexPatternsBulkEditAction } from './utils'; import { throwDryRunError } from './dry_run'; @@ -100,7 +100,9 @@ export const validateBulkEditRule = async ({ */ const istEditApplicableToImmutableRule = (edit: BulkActionEditPayload[]): boolean => { return edit.every(({ type }) => - [BulkActionEditType.set_rule_actions, BulkActionEditType.add_rule_actions].includes(type) + [BulkActionEditTypeEnum.set_rule_actions, BulkActionEditTypeEnum.add_rule_actions].includes( + type + ) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts index 8fb5f348ae224..892610df03bea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts @@ -5,42 +5,29 @@ * 2.0. */ -import * as t from 'io-ts'; - import type { FindResult, RulesClient } from '@kbn/alerting-plugin/server'; -import { NonEmptyString, UUID } from '@kbn/securitysolution-io-ts-types'; -import type { FindRulesSortFieldOrUndefined } from '../../../../../../common/api/detection_engine/rule_management'; +import type { FindRulesSortField } from '../../../../../../common/api/detection_engine/rule_management'; -import type { - FieldsOrUndefined, - PageOrUndefined, - PerPageOrUndefined, - QueryFilterOrUndefined, - SortOrderOrUndefined, -} from '../../../../../../common/api/detection_engine'; +import type { Page, PerPage, SortOrder } from '../../../../../../common/api/detection_engine'; import type { RuleParams } from '../../../rule_schema'; import { enrichFilterWithRuleTypeMapping } from './enrich_filter_with_rule_type_mappings'; import { transformSortField } from './transform_sort_field'; -type HasReferences = t.TypeOf; -const HasReferences = t.type({ - type: NonEmptyString, - id: UUID, -}); - -type HasReferencesOrUndefined = t.TypeOf; -const HasReferencesOrUndefined = t.union([HasReferences, t.undefined]); +interface HasReferences { + type: string; + id: string; +} export interface FindRuleOptions { rulesClient: RulesClient; - filter: QueryFilterOrUndefined; - fields: FieldsOrUndefined; - sortField: FindRulesSortFieldOrUndefined; - sortOrder: SortOrderOrUndefined; - page: PageOrUndefined; - perPage: PerPageOrUndefined; - hasReference?: HasReferencesOrUndefined; + filter: string | undefined; + fields: string[] | undefined; + sortField: FindRulesSortField | undefined; + sortOrder: SortOrder | undefined; + page: Page | undefined; + perPage: PerPage | undefined; + hasReference?: HasReferences | undefined; } export const findRules = ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts index 53573879d07df..b55e51882345a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { FindRulesSortFieldOrUndefined } from '../../../../../../common/api/detection_engine/rule_management'; +import type { FindRulesSortField } from '../../../../../../common/api/detection_engine/rule_management'; import { assertUnreachable } from '../../../../../../common/utility_types'; /** @@ -37,7 +37,7 @@ import { assertUnreachable } from '../../../../../../common/utility_types'; * @param sortField Sort field parameter from the request * @returns Sort field matching the Alerting framework schema */ -export function transformSortField(sortField: FindRulesSortFieldOrUndefined): string | undefined { +export function transformSortField(sortField?: FindRulesSortField): string | undefined { if (!sortField) { return undefined; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts index 07b9c9d0cbcd8..a513e8468d577 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts @@ -113,7 +113,8 @@ describe('validate', () => { const validatedOrError = transformValidateBulkError('rule-1', ruleAlert); const expected: BulkError = { error: { - message: 'Invalid input', + message: + 'name: Required, type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", name: Required, name: Required, and 22 more', status_code: 500, }, rule_id: 'rule-1', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts index c01f09f1b0534..cf6054c689cdd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts @@ -9,8 +9,8 @@ import { serverMock, requestContextMock, requestMock } from '../../../../routes/ import { GET_RULE_EXECUTION_EVENTS_URL, - LogLevel, - RuleExecutionEventType, + LogLevelEnum, + RuleExecutionEventTypeEnum, } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { getRuleExecutionEventsResponseMock } from '../../../../../../../common/api/detection_engine/rule_monitoring/mocks'; import type { GetExecutionEventsArgs } from '../../../logic/rule_execution_log'; @@ -35,8 +35,8 @@ describe('getRuleExecutionEventsRoute', () => { ruleId: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', }, query: { - event_types: `${RuleExecutionEventType['status-change']}`, - log_levels: `${LogLevel.debug},${LogLevel.info}`, + event_types: `${RuleExecutionEventTypeEnum['status-change']}`, + log_levels: `${LogLevelEnum.debug},${LogLevelEnum.info}`, page: 3, }, }); @@ -44,8 +44,8 @@ describe('getRuleExecutionEventsRoute', () => { it('passes request arguments to rule execution log', async () => { const expectedArgs: GetExecutionEventsArgs = { ruleId: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - eventTypes: [RuleExecutionEventType['status-change']], - logLevels: [LogLevel.debug, LogLevel.info], + eventTypes: [RuleExecutionEventTypeEnum['status-change']], + logLevels: [LogLevelEnum.debug, LogLevelEnum.info], sortOrder: 'desc', page: 3, perPage: 20, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts index 1049cbb5c89e1..4a01a6550cabc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts @@ -7,7 +7,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import type { IKibanaResponse } from '@kbn/core/server'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../routes/utils'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; @@ -36,8 +36,8 @@ export const getRuleExecutionEventsRoute = (router: SecuritySolutionPluginRouter version: '1', validate: { request: { - params: buildRouteValidation(GetRuleExecutionEventsRequestParams), - query: buildRouteValidation(GetRuleExecutionEventsRequestQuery), + params: buildRouteValidationWithZod(GetRuleExecutionEventsRequestParams), + query: buildRouteValidationWithZod(GetRuleExecutionEventsRequestQuery), }, }, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/detection_engine_health/event_log/aggregations/rule_execution_stats.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/detection_engine_health/event_log/aggregations/rule_execution_stats.ts index 4151355419586..5b667770ffa5d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/detection_engine_health/event_log/aggregations/rule_execution_stats.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/detection_engine_health/event_log/aggregations/rule_execution_stats.ts @@ -5,31 +5,32 @@ * 2.0. */ -import { mapValues } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { mapValues } from 'lodash'; import type { AggregatedMetric, + HealthOverviewStats, + LogLevel, NumberOfDetectedGaps, NumberOfExecutions, NumberOfLoggedMessages, - HealthOverviewStats, - TopMessages, RuleExecutionStatus, + TopMessages, } from '../../../../../../../../common/api/detection_engine/rule_monitoring'; import { - RuleExecutionEventType, + LogLevelEnum, + RuleExecutionEventTypeEnum, RuleExecutionStatusEnum, - LogLevel, } from '../../../../../../../../common/api/detection_engine/rule_monitoring'; -import { DEFAULT_PERCENTILES } from '../../../utils/es_aggregations'; -import type { RawData } from '../../../utils/normalization'; -import * as f from '../../../event_log/event_log_fields'; import { ALERTING_PROVIDER, RULE_EXECUTION_LOG_PROVIDER, } from '../../../event_log/event_log_constants'; +import * as f from '../../../event_log/event_log_fields'; +import { DEFAULT_PERCENTILES } from '../../../utils/es_aggregations'; +import type { RawData } from '../../../utils/normalization'; export type RuleExecutionStatsAggregationLevel = 'whole-interval' | 'histogram'; @@ -74,7 +75,7 @@ export const getRuleExecutionStatsAggregation = ( bool: { filter: [ { term: { [f.EVENT_PROVIDER]: RULE_EXECUTION_LOG_PROVIDER } }, - { term: { [f.EVENT_ACTION]: RuleExecutionEventType['status-change'] } }, + { term: { [f.EVENT_ACTION]: RuleExecutionEventTypeEnum['status-change'] } }, ], must_not: [ { @@ -101,7 +102,7 @@ export const getRuleExecutionStatsAggregation = ( bool: { filter: [ { term: { [f.EVENT_PROVIDER]: RULE_EXECUTION_LOG_PROVIDER } }, - { term: { [f.EVENT_ACTION]: RuleExecutionEventType['execution-metrics'] } }, + { term: { [f.EVENT_ACTION]: RuleExecutionEventTypeEnum['execution-metrics'] } }, ], }, }, @@ -144,8 +145,8 @@ export const getRuleExecutionStatsAggregation = ( { terms: { [f.EVENT_ACTION]: [ - RuleExecutionEventType['status-change'], - RuleExecutionEventType.message, + RuleExecutionEventTypeEnum['status-change'], + RuleExecutionEventTypeEnum.message, ], }, }, @@ -162,7 +163,7 @@ export const getRuleExecutionStatsAggregation = ( ? { errors: { filter: { - term: { [f.LOG_LEVEL]: LogLevel.error }, + term: { [f.LOG_LEVEL]: LogLevelEnum.error }, }, aggs: { topErrors: { @@ -176,7 +177,7 @@ export const getRuleExecutionStatsAggregation = ( }, warnings: { filter: { - term: { [f.LOG_LEVEL]: LogLevel.warn }, + term: { [f.LOG_LEVEL]: LogLevelEnum.warn }, }, aggs: { topWarnings: { @@ -263,11 +264,11 @@ const normalizeNumberOfLoggedMessages = ( return { total: Number(messageContainingEvents.doc_count || 0), by_level: { - error: getMessageCount(LogLevel.error), - warn: getMessageCount(LogLevel.warn), - info: getMessageCount(LogLevel.info), - debug: getMessageCount(LogLevel.debug), - trace: getMessageCount(LogLevel.trace), + error: getMessageCount(LogLevelEnum.error), + warn: getMessageCount(LogLevelEnum.warn), + info: getMessageCount(LogLevelEnum.info), + debug: getMessageCount(LogLevelEnum.debug), + trace: getMessageCount(LogLevelEnum.trace), }, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/register_event_log_provider.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/register_event_log_provider.ts index 61a321c427205..6c1accab273ad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/register_event_log_provider.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/register_event_log_provider.ts @@ -12,6 +12,6 @@ import { RULE_EXECUTION_LOG_PROVIDER } from './event_log_constants'; export const registerEventLogProvider = (eventLogService: IEventLogService) => { eventLogService.registerProviderActions( RULE_EXECUTION_LOG_PROVIDER, - Object.keys(RuleExecutionEventType) + RuleExecutionEventType.options ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts index 101884b284ebc..8e9a2970f5dbf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts @@ -16,9 +16,9 @@ import type { import type { RuleExecutionSettings, RuleExecutionStatus, + LogLevel, } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { - LogLevel, logLevelFromExecutionStatus, LogLevelSetting, logLevelToNumber, @@ -38,6 +38,7 @@ import type { StatusChangeArgs, } from './client_interface'; import type { RuleExecutionMetrics } from '../../../../../../../common/api/detection_engine/rule_monitoring/model'; +import { LogLevelEnum } from '../../../../../../../common/api/detection_engine/rule_monitoring/model'; export const createRuleExecutionLogClientForExecutors = ( settings: RuleExecutionSettings, @@ -59,23 +60,23 @@ export const createRuleExecutionLogClientForExecutors = ( }, trace(...messages: string[]): void { - writeMessage(messages, LogLevel.trace); + writeMessage(messages, LogLevelEnum.trace); }, debug(...messages: string[]): void { - writeMessage(messages, LogLevel.debug); + writeMessage(messages, LogLevelEnum.debug); }, info(...messages: string[]): void { - writeMessage(messages, LogLevel.info); + writeMessage(messages, LogLevelEnum.info); }, warn(...messages: string[]): void { - writeMessage(messages, LogLevel.warn); + writeMessage(messages, LogLevelEnum.warn); }, error(...messages: string[]): void { - writeMessage(messages, LogLevel.error); + writeMessage(messages, LogLevelEnum.error); }, async logStatusChange(args: StatusChangeArgs): Promise { @@ -107,19 +108,19 @@ export const createRuleExecutionLogClientForExecutors = ( const writeMessageToConsole = (message: string, logLevel: LogLevel, logMeta: ExtMeta): void => { switch (logLevel) { - case LogLevel.trace: + case LogLevelEnum.trace: logger.trace(`${message} ${baseLogSuffix}`, logMeta); break; - case LogLevel.debug: + case LogLevelEnum.debug: logger.debug(`${message} ${baseLogSuffix}`, logMeta); break; - case LogLevel.info: + case LogLevelEnum.info: logger.info(`${message} ${baseLogSuffix}`, logMeta); break; - case LogLevel.warn: + case LogLevelEnum.warn: logger.warn(`${message} ${baseLogSuffix}`, logMeta); break; - case LogLevel.error: + case LogLevelEnum.error: logger.error(`${message} ${baseLogSuffix}`, logMeta); break; default: @@ -152,7 +153,7 @@ export const createRuleExecutionLogClientForExecutors = ( const writeExceptionToConsole = (e: unknown, message: string, logMeta: ExtMeta): void => { const logReason = e instanceof Error ? e.stack ?? e.message : String(e); - writeMessageToConsole(`${message}. Reason: ${logReason}`, LogLevel.error, logMeta); + writeMessageToConsole(`${message}. Reason: ${logReason}`, LogLevelEnum.error, logMeta); }; const writeStatusChangeToConsole = (args: NormalizedStatusChangeArgs, logMeta: ExtMeta): void => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts index fae8b6cfe9f5c..669f3d7e5ee04 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts @@ -9,7 +9,6 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IEventLogClient, IValidatedEvent } from '@kbn/event-log-plugin/server'; import { MAX_EXECUTION_EVENTS_DISPLAYED } from '@kbn/securitysolution-rules'; -import { prepareKQLStringParam } from '../../../../../../../common/utils/kql'; import type { GetRuleExecutionEventsResponse, GetRuleExecutionResultsResponse, @@ -17,10 +16,11 @@ import type { } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { LogLevel, - logLevelFromString, + LogLevelEnum, RuleExecutionEventType, - ruleExecutionEventTypeFromString, + RuleExecutionEventTypeEnum, } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import { prepareKQLStringParam } from '../../../../../../../common/utils/kql'; import { assertUnreachable } from '../../../../../../../common/utility_types'; import { invariant } from '../../../../../../../common/utils/invariant'; @@ -38,11 +38,11 @@ import { } from './aggregations/execution_results'; import type { ExecutionUuidAggResult } from './aggregations/execution_results/types'; -import * as f from '../../event_log/event_log_fields'; import { RULE_EXECUTION_LOG_PROVIDER, RULE_SAVED_OBJECT_TYPE, } from '../../event_log/event_log_constants'; +import * as f from '../../event_log/event_log_fields'; export interface IEventLogReader { getExecutionEvents(args: GetExecutionEventsArgs): Promise; @@ -211,25 +211,27 @@ const normalizeEventSequence = (event: RawEvent): number => { const normalizeLogLevel = (event: RawEvent): LogLevel => { const value = event.log?.level; if (!value) { - return LogLevel.debug; + return LogLevelEnum.debug; } - return logLevelFromString(value) ?? LogLevel.trace; + const result = LogLevel.safeParse(value); + return result.success ? result.data : LogLevelEnum.trace; }; const normalizeEventType = (event: RawEvent): RuleExecutionEventType => { const value = event.event?.action; invariant(value, 'Required "event.action" field is not found'); - return ruleExecutionEventTypeFromString(value) ?? RuleExecutionEventType.message; + const result = RuleExecutionEventType.safeParse(value); + return result.success ? result.data : RuleExecutionEventTypeEnum.message; }; const normalizeEventMessage = (event: RawEvent, type: RuleExecutionEventType): string => { - if (type === RuleExecutionEventType.message) { + if (type === RuleExecutionEventTypeEnum.message) { return event.message || ''; } - if (type === RuleExecutionEventType['status-change']) { + if (type === RuleExecutionEventTypeEnum['status-change']) { invariant( event.kibana?.alert?.rule?.execution?.status, 'Required "kibana.alert.rule.execution.status" field is not found' @@ -241,7 +243,7 @@ const normalizeEventMessage = (event: RawEvent, type: RuleExecutionEventType): s return `Rule changed status to "${status}". ${message}`; } - if (type === RuleExecutionEventType['execution-metrics']) { + if (type === RuleExecutionEventTypeEnum['execution-metrics']) { invariant( event.kibana?.alert?.rule?.execution?.metrics, 'Required "kibana.alert.rule.execution.metrics" field is not found' diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_writer.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_writer.ts index 89696e7175a30..b0963546100e2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_writer.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_writer.ts @@ -8,17 +8,20 @@ import { SavedObjectsUtils } from '@kbn/core/server'; import type { IEventLogService } from '@kbn/event-log-plugin/server'; import { SAVED_OBJECT_REL_PRIMARY } from '@kbn/event-log-plugin/server'; +import type { LogLevel } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { - LogLevel, logLevelFromExecutionStatus, logLevelToNumber, - RuleExecutionEventType, ruleExecutionStatusToNumber, } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import type { RuleExecutionMetrics, RuleExecutionStatus, } from '../../../../../../../common/api/detection_engine/rule_monitoring/model'; +import { + LogLevelEnum, + RuleExecutionEventTypeEnum, +} from '../../../../../../../common/api/detection_engine/rule_monitoring/model'; import { RULE_SAVED_OBJECT_TYPE, RULE_EXECUTION_LOG_PROVIDER, @@ -74,7 +77,7 @@ export const createEventLogWriter = (eventLogService: IEventLogService): IEventL }, event: { kind: 'event', - action: RuleExecutionEventType.message, + action: RuleExecutionEventTypeEnum.message, sequence: sequence++, severity: logLevelToNumber(args.logLevel), }, @@ -116,7 +119,7 @@ export const createEventLogWriter = (eventLogService: IEventLogService): IEventL }, event: { kind: 'event', - action: RuleExecutionEventType['status-change'], + action: RuleExecutionEventTypeEnum['status-change'], sequence: sequence++, severity: logLevelToNumber(logLevel), }, @@ -148,7 +151,7 @@ export const createEventLogWriter = (eventLogService: IEventLogService): IEventL }, logExecutionMetrics: (args: ExecutionMetricsArgs): void => { - const logLevel = LogLevel.debug; + const logLevel = LogLevelEnum.debug; eventLogger.logEvent({ '@timestamp': nowISO(), rule: { @@ -159,7 +162,7 @@ export const createEventLogWriter = (eventLogService: IEventLogService): IEventL }, event: { kind: 'metric', - action: RuleExecutionEventType['execution-metrics'], + action: RuleExecutionEventTypeEnum['execution-metrics'], sequence: sequence++, severity: logLevelToNumber(logLevel), }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts index fcec7e98c06c2..672434bfc94d0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts @@ -7,7 +7,7 @@ import { getScheduleNotificationResponseActionsService } from './schedule_notification_response_actions'; import type { RuleResponseAction } from '../../../../common/api/detection_engine/model/rule_response_actions'; -import { RESPONSE_ACTION_TYPES } from '../../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine/model/rule_response_actions'; describe('ScheduleNotificationResponseActions', () => { const signalOne = { agent: { id: 'agent-id-1' }, _id: 'alert-id-1', user: { id: 'S-1-5-20' } }; @@ -68,7 +68,7 @@ describe('ScheduleNotificationResponseActions', () => { it('should handle osquery response actions with query', async () => { const responseActions: RuleResponseAction[] = [ { - actionTypeId: RESPONSE_ACTION_TYPES.OSQUERY, + actionTypeId: ResponseActionTypesEnum['.osquery'], params: { ...defaultQueryParams, query: simpleQuery, @@ -86,7 +86,7 @@ describe('ScheduleNotificationResponseActions', () => { it('should handle osquery response actions with packs', async () => { const responseActions: RuleResponseAction[] = [ { - actionTypeId: RESPONSE_ACTION_TYPES.OSQUERY, + actionTypeId: ResponseActionTypesEnum['.osquery'], params: { ...defaultPackParams, queries: [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts index 25efe01d15f05..a02fafe69d8f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts @@ -8,7 +8,7 @@ import { each } from 'lodash'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import type { SetupPlugins } from '../../../plugin_contract'; -import { RESPONSE_ACTION_TYPES } from '../../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine/model/rule_response_actions'; import { osqueryResponseAction } from './osquery_response_action'; import { endpointResponseAction } from './endpoint_response_action'; import type { ScheduleNotificationActions } from '../rule_types/types'; @@ -29,14 +29,14 @@ export const getScheduleNotificationResponseActionsService = each(responseActions, (responseAction) => { if ( - responseAction.actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY && + responseAction.actionTypeId === ResponseActionTypesEnum['.osquery'] && osqueryCreateActionService ) { osqueryResponseAction(responseAction, osqueryCreateActionService, { alerts, }); } - if (responseAction.actionTypeId === RESPONSE_ACTION_TYPES.ENDPOINT) { + if (responseAction.actionTypeId === ResponseActionTypesEnum['.endpoint']) { endpointResponseAction(responseAction, endpointAppContextService, { alerts, }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts index 382e902424b5f..6404da38cdde7 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts @@ -446,7 +446,9 @@ export default ({ getService }: FtrProviderContext): void => { .send([{ ...getSimpleRule(), investigation_fields: ['foo'] }]) .expect(400); - expect(body.message).to.eql('[request body]: 0: Invalid input'); + expect(body.message).to.eql( + '[request body]: 0.investigation_fields: Expected object, received array, 0.type: Invalid literal value, expected "eql", 0.language: Invalid literal value, expected "eql", 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, and 22 more' + ); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts index 1b2fcee9c7143..b2000305a4dbb 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts @@ -719,7 +719,9 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(400); - expect(body.message).to.eql('[request body]: Invalid input'); + expect(body.message).to.eql( + '[request body]: investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, and 3 more' + ); }); it('should patch a rule with a legacy investigation field and transform response', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts index 4e7010a37dd49..91c18b2cfa6cc 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts @@ -542,7 +542,9 @@ export default ({ getService }: FtrProviderContext) => { ]) .expect(400); - expect(body.message).to.eql('[request body]: 0: Invalid input'); + expect(body.message).to.eql( + '[request body]: 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, and 3 more' + ); }); it('should patch a rule with a legacy investigation field and transform field in response', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index e9234d47a1816..2529e794089a9 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -16,8 +16,8 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { - BulkActionType, - BulkActionEditType, + BulkActionTypeEnum, + BulkActionEditTypeEnum, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; import { getCreateExceptionListDetectionSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; @@ -106,7 +106,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule()); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.export }) + .send({ query: '', action: BulkActionTypeEnum.export }) .expect(200) .expect('Content-Type', 'application/ndjson') .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') @@ -177,7 +177,7 @@ export default ({ getService }: FtrProviderContext): void => { }; const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.export }) + .send({ query: '', action: BulkActionTypeEnum.export }) .expect(200) .expect('Content-Type', 'application/ndjson') .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') @@ -234,7 +234,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, testRule); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.delete }) + .send({ query: '', action: BulkActionTypeEnum.delete }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -269,7 +269,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.delete }) + .send({ query: '', action: BulkActionTypeEnum.delete }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -290,7 +290,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId)); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.enable }) + .send({ query: '', action: BulkActionTypeEnum.enable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -326,7 +326,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.enable }) + .send({ query: '', action: BulkActionTypeEnum.enable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -363,7 +363,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId, true)); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.disable }) + .send({ query: '', action: BulkActionTypeEnum.disable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -399,7 +399,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.disable }) + .send({ query: '', action: BulkActionTypeEnum.disable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -437,7 +437,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.duplicate, + action: BulkActionTypeEnum.duplicate, duplicate: { include_exceptions: false, include_expired_exceptions: false }, }) .expect(200); @@ -516,7 +516,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.duplicate, + action: BulkActionTypeEnum.duplicate, duplicate: { include_exceptions: true, include_expired_exceptions: true }, }) .expect(200); @@ -622,7 +622,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.duplicate, + action: BulkActionTypeEnum.duplicate, duplicate: { include_exceptions: true, include_expired_exceptions: false }, }) .expect(200); @@ -696,7 +696,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.duplicate, + action: BulkActionTypeEnum.duplicate, duplicate: { include_exceptions: false, include_expired_exceptions: false }, }) .expect(200); @@ -778,10 +778,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: tagsToOverwrite, }, ], @@ -835,10 +835,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_tags, + type: BulkActionEditTypeEnum.delete_tags, value: tagsToDelete, }, ], @@ -891,10 +891,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: addedTags, }, ], @@ -925,21 +925,21 @@ export default ({ getService }: FtrProviderContext): void => { existingTags: ['tag1', 'tag2', 'tag3'], tagsToUpdate: [], resultingTags: ['tag1', 'tag2', 'tag3'], - operation: BulkActionEditType.delete_tags, + operation: BulkActionEditTypeEnum.delete_tags, }, { caseName: '0 existing tags - 2 tags = 0 tags', existingTags: [], tagsToUpdate: ['tag4', 'tag5'], resultingTags: [], - operation: BulkActionEditType.delete_tags, + operation: BulkActionEditTypeEnum.delete_tags, }, { caseName: '3 existing tags - 2 other tags (none of them) = 3 tags', existingTags: ['tag1', 'tag2', 'tag3'], tagsToUpdate: ['tag4', 'tag5'], resultingTags: ['tag1', 'tag2', 'tag3'], - operation: BulkActionEditType.delete_tags, + operation: BulkActionEditTypeEnum.delete_tags, }, // Add no-ops { @@ -947,14 +947,14 @@ export default ({ getService }: FtrProviderContext): void => { existingTags: ['tag1', 'tag2', 'tag3'], tagsToUpdate: ['tag1', 'tag2'], resultingTags: ['tag1', 'tag2', 'tag3'], - operation: BulkActionEditType.add_tags, + operation: BulkActionEditTypeEnum.add_tags, }, { caseName: '3 existing tags + 0 tags = 3 tags', existingTags: ['tag1', 'tag2', 'tag3'], tagsToUpdate: [], resultingTags: ['tag1', 'tag2', 'tag3'], - operation: BulkActionEditType.add_tags, + operation: BulkActionEditTypeEnum.add_tags, }, ]; @@ -968,8 +968,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type: operation, value: tagsToUpdate, @@ -1007,10 +1007,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['initial-index-*'], }, ], @@ -1042,10 +1042,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index3-*'], }, ], @@ -1079,10 +1079,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['index2-*'], }, ], @@ -1113,10 +1113,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [mlRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index-*'], }, ], @@ -1143,10 +1143,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [esqlRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index-*'], }, ], @@ -1176,10 +1176,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['simple-index-*'], }, ], @@ -1209,10 +1209,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: [], }, ], @@ -1244,21 +1244,21 @@ export default ({ getService }: FtrProviderContext): void => { existingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], indexPatternsToUpdate: [], resultingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], - operation: BulkActionEditType.delete_index_patterns, + operation: BulkActionEditTypeEnum.delete_index_patterns, }, { caseName: '0 existing indeces - 2 indeces = 0 indeces', existingIndexPatterns: [], indexPatternsToUpdate: ['index1-*', 'index2-*'], resultingIndexPatterns: [], - operation: BulkActionEditType.delete_index_patterns, + operation: BulkActionEditTypeEnum.delete_index_patterns, }, { caseName: '3 existing indeces - 2 other indeces (none of them) = 3 indeces', existingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], indexPatternsToUpdate: ['index8-*', 'index9-*'], resultingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], - operation: BulkActionEditType.delete_index_patterns, + operation: BulkActionEditTypeEnum.delete_index_patterns, }, // Add no-ops { @@ -1266,14 +1266,14 @@ export default ({ getService }: FtrProviderContext): void => { existingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], indexPatternsToUpdate: ['index1-*', 'index2-*'], resultingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], - operation: BulkActionEditType.add_index_patterns, + operation: BulkActionEditTypeEnum.add_index_patterns, }, { caseName: '3 existing indeces + 0 indeces = 3 indeces', existingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], indexPatternsToUpdate: [], resultingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], - operation: BulkActionEditType.add_index_patterns, + operation: BulkActionEditTypeEnum.add_index_patterns, }, ]; @@ -1296,8 +1296,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type: operation, value: indexPatternsToUpdate, @@ -1353,10 +1353,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setTagsBody } = await postBulkAction().send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['reset-tag'], }, ], @@ -1401,10 +1401,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: timelineId, timeline_title: timelineTitle, @@ -1444,10 +1444,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: '', timeline_title: '', @@ -1476,10 +1476,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [mlRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index-*'], }, ], @@ -1509,10 +1509,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['simple-index-*'], }, ], @@ -1538,10 +1538,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: ['test'], }, ], @@ -1559,35 +1559,35 @@ export default ({ getService }: FtrProviderContext): void => { describe('prebuilt rules', () => { const cases = [ { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: ['new-tag'], }, { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['new-tag'], }, { - type: BulkActionEditType.delete_tags, + type: BulkActionEditTypeEnum.delete_tags, value: ['new-tag'], }, { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['test-*'], }, { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['test-*'], }, { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['test-*'], }, { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: 'mock-id', timeline_title: 'mock-title' }, }, { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: '1m', lookback: '1m' }, }, ]; @@ -1599,8 +1599,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [prebuiltRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type, value, @@ -1648,10 +1648,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [ @@ -1706,10 +1706,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [ @@ -1766,10 +1766,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [], @@ -1818,10 +1818,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [ @@ -1871,10 +1871,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [ @@ -1931,10 +1931,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [ @@ -2004,10 +2004,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [ @@ -2069,10 +2069,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [], @@ -2120,10 +2120,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [], @@ -2147,10 +2147,10 @@ export default ({ getService }: FtrProviderContext): void => { describe('prebuilt rules', () => { const cases = [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, }, { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, }, ]; cases.forEach(({ type }) => { @@ -2162,8 +2162,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [prebuiltRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type, value: { @@ -2220,10 +2220,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [prebuiltRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [ @@ -2235,7 +2235,7 @@ export default ({ getService }: FtrProviderContext): void => { }, }, { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['tag-1'], }, ], @@ -2290,10 +2290,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: payloadThrottle, actions: [], @@ -2331,62 +2331,63 @@ export default ({ getService }: FtrProviderContext): void => { expectedThrottle: undefined, }, ]; - [BulkActionEditType.set_rule_actions, BulkActionEditType.add_rule_actions].forEach( - (ruleAction) => { - casesForNonEmptyActions.forEach(({ payloadThrottle, expectedThrottle }) => { - it(`throttle is updated correctly for rule action "${ruleAction}", if payload throttle="${payloadThrottle}" and actions non empty`, async () => { - // create a new connector - const webHookConnector = await createWebHookConnector(); - - const ruleId = 'ruleId'; - const createdRule = await createRule(supertest, log, getSimpleRule(ruleId)); - - const { body } = await postBulkAction() - .send({ - ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ - { - type: BulkActionEditType.set_rule_actions, - value: { - throttle: payloadThrottle, - actions: [ - { - id: webHookConnector.id, - group: 'default', - params: { body: '{}' }, - }, - ], - }, + [ + BulkActionEditTypeEnum.set_rule_actions, + BulkActionEditTypeEnum.add_rule_actions, + ].forEach((ruleAction) => { + casesForNonEmptyActions.forEach(({ payloadThrottle, expectedThrottle }) => { + it(`throttle is updated correctly for rule action "${ruleAction}", if payload throttle="${payloadThrottle}" and actions non empty`, async () => { + // create a new connector + const webHookConnector = await createWebHookConnector(); + + const ruleId = 'ruleId'; + const createdRule = await createRule(supertest, log, getSimpleRule(ruleId)); + + const { body } = await postBulkAction() + .send({ + ids: [createdRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.set_rule_actions, + value: { + throttle: payloadThrottle, + actions: [ + { + id: webHookConnector.id, + group: 'default', + params: { body: '{}' }, + }, + ], }, - ], - }) - .expect(200); - - // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].throttle).to.eql(expectedThrottle); - - const expectedActions = body.attributes.results.updated[0].actions.map( - (action: any) => ({ - ...action, - frequency: { - summary: true, - throttle: payloadThrottle !== 'rule' ? payloadThrottle : null, - notifyWhen: - payloadThrottle !== 'rule' ? 'onThrottleInterval' : 'onActiveAlert', }, - }) - ); + ], + }) + .expect(200); + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].throttle).to.eql(expectedThrottle); + + const expectedActions = body.attributes.results.updated[0].actions.map( + (action: any) => ({ + ...action, + frequency: { + summary: true, + throttle: payloadThrottle !== 'rule' ? payloadThrottle : null, + notifyWhen: + payloadThrottle !== 'rule' ? 'onThrottleInterval' : 'onActiveAlert', + }, + }) + ); - // Check that the updates have been persisted - const { body: rule } = await fetchRule(ruleId).expect(200); + // Check that the updates have been persisted + const { body: rule } = await fetchRule(ruleId).expect(200); - expect(rule.throttle).to.eql(expectedThrottle); - expect(rule.actions).to.eql(expectedActions); - }); + expect(rule.throttle).to.eql(expectedThrottle); + expect(rule.actions).to.eql(expectedActions); }); - } - ); + }); + }); }); describe('notifyWhen', () => { @@ -2409,10 +2410,10 @@ export default ({ getService }: FtrProviderContext): void => { await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: payload.throttle, actions: [], @@ -2443,10 +2444,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval, lookback, @@ -2458,8 +2459,8 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.statusCode).to.eql(400); expect(body.error).to.eql('Bad Request'); - expect(body.message).to.contain('Invalid value "0m" supplied to "edit,value,interval"'); - expect(body.message).to.contain('Invalid value "-1m" supplied to "edit,value,lookback"'); + expect(body.message).to.contain('edit.0.value.interval: Invalid'); + expect(body.message).to.contain('edit.0.value.lookback: Invalid'); }); it('should update schedule values in rules with a valid payload', async () => { @@ -2473,10 +2474,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval, lookback, @@ -2511,10 +2512,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['initial-index-*'], overwrite_data_views: true, }, @@ -2552,10 +2553,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['initial-index-*'], overwrite_data_views: false, }, @@ -2597,10 +2598,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['initial-index-*'], overwrite_data_views: true, }, @@ -2638,10 +2639,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: [], overwrite_data_views: true, }, @@ -2674,10 +2675,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['initial-index-*'], overwrite_data_views: false, }, @@ -2720,10 +2721,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['simple-index-*'], overwrite_data_views: true, }, @@ -2761,10 +2762,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['simple-index-*'], overwrite_data_views: true, }, @@ -2797,10 +2798,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['simple-index-*'], overwrite_data_views: false, }, @@ -2830,14 +2831,14 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['initial-index-*'], }, { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: ['tag3'], }, ], @@ -2868,16 +2869,16 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ // Valid operation { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['initial-index-*'], }, // Operation to be skipped { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: ['tag1'], }, ], @@ -2908,16 +2909,16 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ // Operation to be skipped { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index1-*'], }, // Operation to be skipped { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: ['tag1'], }, ], @@ -2949,10 +2950,10 @@ export default ({ getService }: FtrProviderContext): void => { Array.from({ length: 10 }).map(() => postBulkAction().send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: timelineId, timeline_title: timelineTitle, @@ -2978,10 +2979,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: timelineId, timeline_title: timelineTitle, @@ -3033,7 +3034,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should export rules with legacy investigation_fields and transform legacy field in response', async () => { const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.export }) + .send({ query: '', action: BulkActionTypeEnum.export }) .expect(200) .expect('Content-Type', 'application/ndjson') .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') @@ -3094,7 +3095,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should delete rules with investigation fields and transform legacy field in response', async () => { const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.delete }) + .send({ query: '', action: BulkActionTypeEnum.delete }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); @@ -3124,7 +3125,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should enable rules with legacy investigation fields and transform legacy field in response', async () => { const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.enable }) + .send({ query: '', action: BulkActionTypeEnum.enable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); @@ -3188,7 +3189,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should disable rules with legacy investigation fields and transform legacy field in response', async () => { const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.disable }) + .send({ query: '', action: BulkActionTypeEnum.disable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); @@ -3251,7 +3252,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.duplicate, + action: BulkActionTypeEnum.duplicate, duplicate: { include_exceptions: false, include_expired_exceptions: false }, }) .expect(200); @@ -3353,10 +3354,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should edit rules with legacy investigation fields', async () => { const { body } = await postBulkAction().send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['reset-tag'], }, ], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts index a6df465d09f68..f1df03187379c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts @@ -12,8 +12,8 @@ import { getCreateEsqlRulesSchemaMock } from '@kbn/security-solution-plugin/comm import expect from 'expect'; import { - BulkActionType, - BulkActionEditType, + BulkActionTypeEnum, + BulkActionEditTypeEnum, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { @@ -65,7 +65,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule()); const { body } = await postDryRunBulkAction() - .send({ action: BulkActionType.export }) + .send({ action: BulkActionTypeEnum.export }) .expect(400); expect(body).toEqual({ @@ -80,7 +80,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, testRule); const { body } = await postDryRunBulkAction() - .send({ action: BulkActionType.delete }) + .send({ action: BulkActionTypeEnum.delete }) .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -101,7 +101,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId)); const { body } = await postDryRunBulkAction() - .send({ action: BulkActionType.enable }) + .send({ action: BulkActionTypeEnum.enable }) .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -123,7 +123,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId, true)); const { body } = await postDryRunBulkAction() - .send({ action: BulkActionType.disable }) + .send({ action: BulkActionTypeEnum.disable }) .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -146,7 +146,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, ruleToDuplicate); const { body } = await postDryRunBulkAction() - .send({ action: BulkActionType.disable }) + .send({ action: BulkActionTypeEnum.disable }) .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -172,10 +172,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['reset-tag'], }, ], @@ -208,10 +208,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ ids: [immutableRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['reset-tag'], }, ], @@ -242,9 +242,9 @@ export default ({ getService }: FtrProviderContext): void => { describe('validate updating index pattern for machine learning rule', () => { const actions = [ - BulkActionEditType.add_index_patterns, - BulkActionEditType.set_index_patterns, - BulkActionEditType.delete_index_patterns, + BulkActionEditTypeEnum.add_index_patterns, + BulkActionEditTypeEnum.set_index_patterns, + BulkActionEditTypeEnum.delete_index_patterns, ]; actions.forEach((editAction) => { @@ -254,8 +254,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ ids: [mlRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type: editAction, value: [], @@ -295,9 +295,9 @@ export default ({ getService }: FtrProviderContext): void => { describe('validate updating index pattern for ES|QL rule', () => { const actions = [ - BulkActionEditType.add_index_patterns, - BulkActionEditType.set_index_patterns, - BulkActionEditType.delete_index_patterns, + BulkActionEditTypeEnum.add_index_patterns, + BulkActionEditTypeEnum.set_index_patterns, + BulkActionEditTypeEnum.delete_index_patterns, ]; actions.forEach((editAction) => { @@ -307,8 +307,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ ids: [esqlRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type: editAction, value: [], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts index 7c2d88620924f..d88ed8a898f90 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts @@ -566,7 +566,8 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql({ error: 'Bad Request', - message: '[request body]: Invalid input', + message: + '[request body]: type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "query", type: Invalid literal value, expected "saved_query", saved_id: Required, and 14 more', statusCode: 400, }); }); @@ -955,7 +956,9 @@ export default ({ getService }: FtrProviderContext) => { .send(updatedRule) .expect(400); - expect(body.message).to.eql('[request body]: Invalid input'); + expect(body.message).to.eql( + '[request body]: investigation_fields: Expected object, received array, type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, and 22 more' + ); }); it('unsets legacy investigation fields when field not specified for update', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts index b5dbf7fca40f2..a3defbb6d1c82 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts @@ -853,7 +853,9 @@ export default ({ getService }: FtrProviderContext) => { ]) .expect(400); - expect(body.message).to.eql('[request body]: 0: Invalid input'); + expect(body.message).to.eql( + '[request body]: 0.investigation_fields: Expected object, received array, 0.type: Invalid literal value, expected "eql", 0.language: Invalid literal value, expected "eql", 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, and 22 more' + ); }); it('updates a rule with legacy investigation fields and transforms field in response', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts index 69425ac7fa4fc..4c38edaf0cd28 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts @@ -28,7 +28,7 @@ import { v4 as uuidv4 } from 'uuid'; import { QueryRuleCreateProps, - BulkActionType, + BulkActionTypeEnum, AlertSuppressionMissingFieldsStrategyEnum, } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; @@ -2296,7 +2296,7 @@ export default ({ getService }: FtrProviderContext) => { .post(DETECTION_ENGINE_RULES_BULK_ACTION) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') - .send({ query: '', action: BulkActionType.enable }) + .send({ query: '', action: BulkActionTypeEnum.enable }) .expect(200); // Confirming that enabling did not migrate rule, so rule diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts index 97b602c4db617..5ace976f1f4fc 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts @@ -467,7 +467,8 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql({ error: 'Bad Request', - message: '[request body]: Invalid input', + message: + '[request body]: type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "query", type: Invalid literal value, expected "saved_query", saved_id: Required, and 14 more', statusCode: 400, }); }); @@ -574,7 +575,9 @@ export default ({ getService }: FtrProviderContext) => { .send(rule) .expect(400); - expect(body.message).to.eql('[request body]: Invalid input'); + expect(body.message).to.eql( + '[request body]: investigation_fields: Expected object, received array, type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, and 22 more' + ); }); }); });