mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Migrate remaining public Detection Engine APIs to OpenAPI and code generation (#170330)
**Related to: https://github.com/elastic/security-team/issues/7491** ## Summary Migrated remaining public Detection Engine endpoints to OpenAPI schema and code generation: - `POST /api/detection_engine/rules/_bulk_action` - `GET /api/detection_engine/rules/_find` Also completed the migration of internal APIs: - `GET /internal/detection_engine/rules/{ruleId}/execution/events` - `GET /internal/detection_engine/rules/{ruleId}/execution/results` ### Other notable changes - Changed how we compose Zod error messages for unions, see `packages/kbn-zod-helpers/src/stringify_zod_error.ts`. Now we are trying to list the validation errors of all union members but limiting the total number of validation errors displayed to users. - Addressed some remaining `TODO https://github.com/elastic/security-team/issues/7491` - Removed dependencies of the risk engine and timelines on detection engine schemas - Removed outdated legacy rule schemas that are no longer in use - Added new schema helpers that work with query params: `BooleanFromString` and `ArrayFromString`  
This commit is contained in:
parent
0063691ad5
commit
e00566fa98
164 changed files with 2567 additions and 2583 deletions
|
@ -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}}
|
||||
|
||||
|
|
|
@ -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")}}
|
||||
|
|
|
@ -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';
|
||||
|
|
34
packages/kbn-zod-helpers/src/array_from_string.test.ts
Normal file
34
packages/kbn-zod-helpers/src/array_from_string.test.ts
Normal file
|
@ -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();
|
||||
});
|
||||
});
|
24
packages/kbn-zod-helpers/src/array_from_string.ts
Normal file
24
packages/kbn-zod-helpers/src/array_from_string.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 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<T extends z.ZodTypeAny>(schema: T) {
|
||||
return z.preprocess(
|
||||
(value: unknown) =>
|
||||
typeof value === 'string' ? (value === '' ? [] : value.split(',')) : value,
|
||||
z.array(schema)
|
||||
);
|
||||
}
|
32
packages/kbn-zod-helpers/src/boolean_from_string.test.ts
Normal file
32
packages/kbn-zod-helpers/src/boolean_from_string.test.ts
Normal file
|
@ -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();
|
||||
});
|
||||
});
|
24
packages/kbn-zod-helpers/src/boolean_from_string.ts
Normal file
24
packages/kbn-zod-helpers/src/boolean_from_string.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 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';
|
||||
});
|
|
@ -7,9 +7,14 @@
|
|||
*/
|
||||
|
||||
import type { SafeParseReturnType, SafeParseSuccess } from 'zod';
|
||||
import { stringifyZodError } from './stringify_zod_error';
|
||||
|
||||
export function expectParseSuccess<Input, Output>(
|
||||
result: SafeParseReturnType<Input, Output>
|
||||
): asserts result is SafeParseSuccess<Output> {
|
||||
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)}`);
|
||||
}
|
||||
}
|
||||
|
|
28
packages/kbn-zod-helpers/src/safe_parse_result.ts
Normal file
28
packages/kbn-zod-helpers/src/safe_parse_result.ts
Normal file
|
@ -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<T extends z.ZodTypeAny>(
|
||||
payload: unknown,
|
||||
schema: T
|
||||
): T['_output'] | undefined {
|
||||
const result = schema.safeParse(payload);
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
}
|
||||
}
|
|
@ -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<any>) {
|
||||
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}`;
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<typeof Page>;
|
||||
export const Page = z.number().int().min(1);
|
||||
|
||||
/**
|
||||
* Number of items per page
|
||||
*/
|
||||
export type PerPage = z.infer<typeof PerPage>;
|
||||
export const PerPage = z.number().int().min(0);
|
||||
|
||||
export type PaginationResult = z.infer<typeof PaginationResult>;
|
||||
export const PaginationResult = z.object({
|
||||
page: Page,
|
||||
per_page: PerPage,
|
||||
/**
|
||||
* Total number of items
|
||||
*/
|
||||
total: z.number().int().min(0),
|
||||
});
|
|
@ -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
|
|
@ -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<typeof Page>;
|
||||
export const Page = PositiveIntegerGreaterThanZero;
|
||||
|
||||
export type PageOrUndefined = t.TypeOf<typeof PageOrUndefined>;
|
||||
export const PageOrUndefined = t.union([Page, t.undefined]);
|
||||
|
||||
export type PerPage = t.TypeOf<typeof PerPage>;
|
||||
export const PerPage = PositiveInteger;
|
||||
|
||||
export type PerPageOrUndefined = t.TypeOf<typeof PerPageOrUndefined>;
|
||||
export const PerPageOrUndefined = t.union([PerPage, t.undefined]);
|
||||
|
||||
export type PaginationResult = t.TypeOf<typeof PaginationResult>;
|
||||
export const PaginationResult = t.type({
|
||||
page: Page,
|
||||
per_page: PerPage,
|
||||
total: PositiveInteger,
|
||||
});
|
|
@ -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';
|
||||
|
|
|
@ -367,26 +367,38 @@ export const RuleActionFrequency = z.object({
|
|||
throttle: RuleActionThrottle.nullable(),
|
||||
});
|
||||
|
||||
export type RuleActionAlertsFilter = z.infer<typeof RuleActionAlertsFilter>;
|
||||
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<typeof RuleActionParams>;
|
||||
export const RuleActionParams = z.object({}).catchall(z.unknown());
|
||||
|
||||
/**
|
||||
* Optionally groups actions by use cases. Use `default` for alert notifications.
|
||||
*/
|
||||
export type RuleActionGroup = z.infer<typeof RuleActionGroup>;
|
||||
export const RuleActionGroup = z.string();
|
||||
|
||||
/**
|
||||
* The connector ID.
|
||||
*/
|
||||
export type RuleActionId = z.infer<typeof RuleActionId>;
|
||||
export const RuleActionId = z.string();
|
||||
|
||||
export type RuleAction = z.infer<typeof RuleAction>;
|
||||
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(),
|
||||
});
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<T extends readonly string[]>(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<typeof RuleResponseOsqueryAction>;
|
||||
export const RuleResponseOsqueryAction = t.strict({
|
||||
actionTypeId: t.literal(RESPONSE_ACTION_TYPES.OSQUERY),
|
||||
actionTypeId: t.literal('.osquery'),
|
||||
params: OsqueryParamsCamelCase,
|
||||
});
|
||||
|
||||
export type RuleResponseEndpointAction = t.TypeOf<typeof RuleResponseEndpointAction>;
|
||||
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,
|
||||
});
|
||||
|
|
@ -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,
|
||||
export type DurationMetric = t.TypeOf<typeof DurationMetric>;
|
||||
export const DurationMetric = PositiveInteger;
|
||||
|
||||
// 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,
|
||||
};
|
||||
export type RuleExecutionMetrics = t.TypeOf<typeof RuleExecutionMetrics>;
|
||||
|
||||
const responseOptionalFields = {
|
||||
execution_summary: RuleExecutionSummary,
|
||||
};
|
||||
/**
|
||||
@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<typeof BaseCreateProps>;
|
||||
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<typeof SharedUpdateProps>;
|
||||
const SharedUpdateProps = t.intersection([
|
||||
baseSchema.create,
|
||||
t.exact(t.partial({ rule_id: RuleSignatureId })),
|
||||
t.exact(t.partial({ id: RuleObjectId })),
|
||||
]);
|
||||
|
||||
type SharedPatchProps = t.TypeOf<typeof SharedPatchProps>;
|
||||
const SharedPatchProps = t.intersection([
|
||||
baseSchema.patch,
|
||||
t.exact(t.partial({ rule_id: RuleSignatureId, id: RuleObjectId })),
|
||||
]);
|
||||
|
||||
export type SharedResponseProps = t.TypeOf<typeof SharedResponseProps>;
|
||||
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<typeof KqlQueryLanguage>;
|
||||
export const KqlQueryLanguage = t.keyof({ kuery: null, lucene: null });
|
||||
|
||||
|
@ -278,21 +232,6 @@ const eqlSchema = buildRuleSchemas({
|
|||
defaultable: {},
|
||||
});
|
||||
|
||||
export type EqlRule = t.TypeOf<typeof EqlRule>;
|
||||
export const EqlRule = t.intersection([SharedResponseProps, eqlSchema.response]);
|
||||
|
||||
export type EqlRuleCreateProps = t.TypeOf<typeof EqlRuleCreateProps>;
|
||||
export const EqlRuleCreateProps = t.intersection([SharedCreateProps, eqlSchema.create]);
|
||||
|
||||
export type EqlRuleUpdateProps = t.TypeOf<typeof EqlRuleUpdateProps>;
|
||||
export const EqlRuleUpdateProps = t.intersection([SharedUpdateProps, eqlSchema.create]);
|
||||
|
||||
export type EqlRulePatchProps = t.TypeOf<typeof EqlRulePatchProps>;
|
||||
export const EqlRulePatchProps = t.intersection([SharedPatchProps, eqlSchema.patch]);
|
||||
|
||||
export type EqlPatchParams = t.TypeOf<typeof EqlPatchParams>;
|
||||
export const EqlPatchParams = eqlSchema.patch;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// ES|QL rule schema
|
||||
|
||||
|
@ -309,21 +248,6 @@ const esqlSchema = buildRuleSchemas({
|
|||
defaultable: {},
|
||||
});
|
||||
|
||||
export type EsqlRule = t.TypeOf<typeof EsqlRule>;
|
||||
export const EsqlRule = t.intersection([SharedResponseProps, esqlSchema.response]);
|
||||
|
||||
export type EsqlRuleCreateProps = t.TypeOf<typeof EsqlRuleCreateProps>;
|
||||
export const EsqlRuleCreateProps = t.intersection([SharedCreateProps, esqlSchema.create]);
|
||||
|
||||
export type EsqlRuleUpdateProps = t.TypeOf<typeof EsqlRuleUpdateProps>;
|
||||
export const EsqlRuleUpdateProps = t.intersection([SharedUpdateProps, esqlSchema.create]);
|
||||
|
||||
export type EsqlRulePatchProps = t.TypeOf<typeof EsqlRulePatchProps>;
|
||||
export const EsqlRulePatchProps = t.intersection([SharedPatchProps, esqlSchema.patch]);
|
||||
|
||||
export type EsqlPatchParams = t.TypeOf<typeof EsqlPatchParams>;
|
||||
export const EsqlPatchParams = esqlSchema.patch;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Indicator Match rule schema
|
||||
|
||||
|
@ -351,30 +275,6 @@ const threatMatchSchema = buildRuleSchemas({
|
|||
},
|
||||
});
|
||||
|
||||
export type ThreatMatchRule = t.TypeOf<typeof ThreatMatchRule>;
|
||||
export const ThreatMatchRule = t.intersection([SharedResponseProps, threatMatchSchema.response]);
|
||||
|
||||
export type ThreatMatchRuleCreateProps = t.TypeOf<typeof ThreatMatchRuleCreateProps>;
|
||||
export const ThreatMatchRuleCreateProps = t.intersection([
|
||||
SharedCreateProps,
|
||||
threatMatchSchema.create,
|
||||
]);
|
||||
|
||||
export type ThreatMatchRuleUpdateProps = t.TypeOf<typeof ThreatMatchRuleUpdateProps>;
|
||||
export const ThreatMatchRuleUpdateProps = t.intersection([
|
||||
SharedUpdateProps,
|
||||
threatMatchSchema.create,
|
||||
]);
|
||||
|
||||
export type ThreatMatchRulePatchProps = t.TypeOf<typeof ThreatMatchRulePatchProps>;
|
||||
export const ThreatMatchRulePatchProps = t.intersection([
|
||||
SharedPatchProps,
|
||||
threatMatchSchema.patch,
|
||||
]);
|
||||
|
||||
export type ThreatMatchPatchParams = t.TypeOf<typeof ThreatMatchPatchParams>;
|
||||
export const ThreatMatchPatchParams = threatMatchSchema.patch;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Custom Query rule schema
|
||||
|
||||
|
@ -396,21 +296,6 @@ const querySchema = buildRuleSchemas({
|
|||
},
|
||||
});
|
||||
|
||||
export type QueryRule = t.TypeOf<typeof QueryRule>;
|
||||
export const QueryRule = t.intersection([SharedResponseProps, querySchema.response]);
|
||||
|
||||
export type QueryRuleCreateProps = t.TypeOf<typeof QueryRuleCreateProps>;
|
||||
export const QueryRuleCreateProps = t.intersection([SharedCreateProps, querySchema.create]);
|
||||
|
||||
export type QueryRuleUpdateProps = t.TypeOf<typeof QueryRuleUpdateProps>;
|
||||
export const QueryRuleUpdateProps = t.intersection([SharedUpdateProps, querySchema.create]);
|
||||
|
||||
export type QueryRulePatchProps = t.TypeOf<typeof QueryRulePatchProps>;
|
||||
export const QueryRulePatchProps = t.intersection([SharedPatchProps, querySchema.patch]);
|
||||
|
||||
export type QueryPatchParams = t.TypeOf<typeof QueryPatchParams>;
|
||||
export const QueryPatchParams = querySchema.patch;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Saved Query rule schema
|
||||
|
||||
|
@ -434,27 +319,6 @@ const savedQuerySchema = buildRuleSchemas({
|
|||
},
|
||||
});
|
||||
|
||||
export type SavedQueryRule = t.TypeOf<typeof SavedQueryRule>;
|
||||
export const SavedQueryRule = t.intersection([SharedResponseProps, savedQuerySchema.response]);
|
||||
|
||||
export type SavedQueryRuleCreateProps = t.TypeOf<typeof SavedQueryRuleCreateProps>;
|
||||
export const SavedQueryRuleCreateProps = t.intersection([
|
||||
SharedCreateProps,
|
||||
savedQuerySchema.create,
|
||||
]);
|
||||
|
||||
export type SavedQueryRuleUpdateProps = t.TypeOf<typeof SavedQueryRuleUpdateProps>;
|
||||
export const SavedQueryRuleUpdateProps = t.intersection([
|
||||
SharedUpdateProps,
|
||||
savedQuerySchema.create,
|
||||
]);
|
||||
|
||||
export type SavedQueryRulePatchProps = t.TypeOf<typeof SavedQueryRulePatchProps>;
|
||||
export const SavedQueryRulePatchProps = t.intersection([SharedPatchProps, savedQuerySchema.patch]);
|
||||
|
||||
export type SavedQueryPatchParams = t.TypeOf<typeof SavedQueryPatchParams>;
|
||||
export const SavedQueryPatchParams = savedQuerySchema.patch;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Threshold rule schema
|
||||
|
||||
|
@ -475,21 +339,6 @@ const thresholdSchema = buildRuleSchemas({
|
|||
},
|
||||
});
|
||||
|
||||
export type ThresholdRule = t.TypeOf<typeof ThresholdRule>;
|
||||
export const ThresholdRule = t.intersection([SharedResponseProps, thresholdSchema.response]);
|
||||
|
||||
export type ThresholdRuleCreateProps = t.TypeOf<typeof ThresholdRuleCreateProps>;
|
||||
export const ThresholdRuleCreateProps = t.intersection([SharedCreateProps, thresholdSchema.create]);
|
||||
|
||||
export type ThresholdRuleUpdateProps = t.TypeOf<typeof ThresholdRuleUpdateProps>;
|
||||
export const ThresholdRuleUpdateProps = t.intersection([SharedUpdateProps, thresholdSchema.create]);
|
||||
|
||||
export type ThresholdRulePatchProps = t.TypeOf<typeof ThresholdRulePatchProps>;
|
||||
export const ThresholdRulePatchProps = t.intersection([SharedPatchProps, thresholdSchema.patch]);
|
||||
|
||||
export type ThresholdPatchParams = t.TypeOf<typeof ThresholdPatchParams>;
|
||||
export const ThresholdPatchParams = thresholdSchema.patch;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Machine Learning rule schema
|
||||
|
||||
|
@ -503,33 +352,6 @@ const machineLearningSchema = buildRuleSchemas({
|
|||
defaultable: {},
|
||||
});
|
||||
|
||||
export type MachineLearningRule = t.TypeOf<typeof MachineLearningRule>;
|
||||
export const MachineLearningRule = t.intersection([
|
||||
SharedResponseProps,
|
||||
machineLearningSchema.response,
|
||||
]);
|
||||
|
||||
export type MachineLearningRuleCreateProps = t.TypeOf<typeof MachineLearningRuleCreateProps>;
|
||||
export const MachineLearningRuleCreateProps = t.intersection([
|
||||
SharedCreateProps,
|
||||
machineLearningSchema.create,
|
||||
]);
|
||||
|
||||
export type MachineLearningRuleUpdateProps = t.TypeOf<typeof MachineLearningRuleUpdateProps>;
|
||||
export const MachineLearningRuleUpdateProps = t.intersection([
|
||||
SharedUpdateProps,
|
||||
machineLearningSchema.create,
|
||||
]);
|
||||
|
||||
export type MachineLearningRulePatchProps = t.TypeOf<typeof MachineLearningRulePatchProps>;
|
||||
export const MachineLearningRulePatchProps = t.intersection([
|
||||
SharedPatchProps,
|
||||
machineLearningSchema.patch,
|
||||
]);
|
||||
|
||||
export type MachineLearningPatchParams = t.TypeOf<typeof MachineLearningPatchParams>;
|
||||
export const MachineLearningPatchParams = machineLearningSchema.patch;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// New Terms rule schema
|
||||
|
||||
|
@ -550,21 +372,6 @@ const newTermsSchema = buildRuleSchemas({
|
|||
},
|
||||
});
|
||||
|
||||
export type NewTermsRule = t.TypeOf<typeof NewTermsRule>;
|
||||
export const NewTermsRule = t.intersection([SharedResponseProps, newTermsSchema.response]);
|
||||
|
||||
export type NewTermsRuleCreateProps = t.TypeOf<typeof NewTermsRuleCreateProps>;
|
||||
export const NewTermsRuleCreateProps = t.intersection([SharedCreateProps, newTermsSchema.create]);
|
||||
|
||||
export type NewTermsRuleUpdateProps = t.TypeOf<typeof NewTermsRuleUpdateProps>;
|
||||
export const NewTermsRuleUpdateProps = t.intersection([SharedUpdateProps, newTermsSchema.create]);
|
||||
|
||||
export type NewTermsRulePatchProps = t.TypeOf<typeof NewTermsRulePatchProps>;
|
||||
export const NewTermsRulePatchProps = t.intersection([SharedPatchProps, newTermsSchema.patch]);
|
||||
|
||||
export type NewTermsPatchParams = t.TypeOf<typeof NewTermsPatchParams>;
|
||||
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<typeof TypeSpecificPatchProps>;
|
||||
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<typeof TypeSpecificResponse>;
|
||||
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<typeof RuleCreateProps>;
|
||||
export const RuleCreateProps = t.intersection([TypeSpecificCreateProps, SharedCreateProps]);
|
||||
|
||||
export type RuleUpdateProps = t.TypeOf<typeof RuleUpdateProps>;
|
||||
export const RuleUpdateProps = t.intersection([TypeSpecificCreateProps, SharedUpdateProps]);
|
||||
|
||||
export type RulePatchProps = t.TypeOf<typeof RulePatchProps>;
|
||||
export const RulePatchProps = t.intersection([TypeSpecificPatchProps, SharedPatchProps]);
|
||||
|
||||
export type RuleResponse = t.TypeOf<typeof RuleResponse>;
|
||||
export const RuleResponse = t.intersection([TypeSpecificResponse, SharedResponseProps]);
|
||||
|
|
|
@ -33,12 +33,6 @@ export type Status = t.TypeOf<typeof status>;
|
|||
|
||||
export const conflicts = t.keyof({ abort: null, proceed: null });
|
||||
|
||||
export const queryFilter = t.string;
|
||||
export type QueryFilter = t.TypeOf<typeof queryFilter>;
|
||||
|
||||
export const queryFilterOrUndefined = t.union([queryFilter, t.undefined]);
|
||||
export type QueryFilterOrUndefined = t.TypeOf<typeof queryFilterOrUndefined>;
|
||||
|
||||
export const signal_ids = t.array(t.string);
|
||||
export type SignalIds = t.TypeOf<typeof signal_ids>;
|
||||
|
||||
|
@ -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<typeof alert_tag_ids>;
|
||||
|
||||
export const fields = t.array(t.string);
|
||||
export type Fields = t.TypeOf<typeof fields>;
|
||||
export const fieldsOrUndefined = t.union([fields, t.undefined]);
|
||||
export type FieldsOrUndefined = t.TypeOf<typeof fieldsOrUndefined>;
|
||||
|
||||
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,
|
||||
|
|
|
@ -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';
|
||||
import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers';
|
||||
import { SortOrder } from './sorting.gen';
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
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);
|
||||
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(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);
|
||||
|
||||
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'"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<typeof SortOrder>;
|
||||
export const SortOrder = t.keyof({ asc: null, desc: null });
|
||||
|
||||
export type SortOrderOrUndefined = t.TypeOf<typeof SortOrderOrUndefined>;
|
||||
export const SortOrderOrUndefined = t.union([SortOrder, t.undefined]);
|
||||
|
||||
const defaultSortOrder = (order: SortOrder): t.Type<SortOrder, SortOrder, unknown> => {
|
||||
return new t.Type<SortOrder, SortOrder, unknown>(
|
||||
`DefaultSortOrder${capitalize(order)}`,
|
||||
SortOrder.is,
|
||||
(input, context): Either<t.Errors, SortOrder> =>
|
||||
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');
|
|
@ -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<typeof CreateRuleExceptionsRequestParams>;
|
||||
export const CreateRuleExceptionsRequestParams = t.exact(
|
||||
t.type({
|
||||
id: RuleObjectId,
|
||||
id: UUID,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -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<typeof BulkEditSkipReason>;
|
||||
export const BulkEditSkipReason = z.literal('RULE_NOT_MODIFIED');
|
||||
|
||||
export type BulkActionSkipResult = z.infer<typeof BulkActionSkipResult>;
|
||||
export const BulkActionSkipResult = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().optional(),
|
||||
skip_reason: BulkEditSkipReason,
|
||||
});
|
||||
|
||||
export type RuleDetailsInError = z.infer<typeof RuleDetailsInError>;
|
||||
export const RuleDetailsInError = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().optional(),
|
||||
});
|
||||
|
||||
export type BulkActionsDryRunErrCode = z.infer<typeof BulkActionsDryRunErrCode>;
|
||||
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<typeof NormalizedRuleError>;
|
||||
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<typeof BulkEditActionResults>;
|
||||
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<typeof BulkEditActionSummary>;
|
||||
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<typeof BulkEditActionResponse>;
|
||||
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<typeof BulkExportActionResponse>;
|
||||
export const BulkExportActionResponse = z.string();
|
||||
|
||||
export type BulkActionBase = z.infer<typeof BulkActionBase>;
|
||||
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<typeof BulkDeleteRules>;
|
||||
export const BulkDeleteRules = BulkActionBase.and(
|
||||
z.object({
|
||||
action: z.literal('delete'),
|
||||
})
|
||||
);
|
||||
|
||||
export type BulkDisableRules = z.infer<typeof BulkDisableRules>;
|
||||
export const BulkDisableRules = BulkActionBase.and(
|
||||
z.object({
|
||||
action: z.literal('disable'),
|
||||
})
|
||||
);
|
||||
|
||||
export type BulkEnableRules = z.infer<typeof BulkEnableRules>;
|
||||
export const BulkEnableRules = BulkActionBase.and(
|
||||
z.object({
|
||||
action: z.literal('enable'),
|
||||
})
|
||||
);
|
||||
|
||||
export type BulkExportRules = z.infer<typeof BulkExportRules>;
|
||||
export const BulkExportRules = BulkActionBase.and(
|
||||
z.object({
|
||||
action: z.literal('export'),
|
||||
})
|
||||
);
|
||||
|
||||
export type BulkDuplicateRules = z.infer<typeof BulkDuplicateRules>;
|
||||
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<typeof ThrottleForBulkActions>;
|
||||
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<typeof BulkActionType>;
|
||||
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<typeof BulkActionEditType>;
|
||||
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<typeof NormalizedRuleAction>;
|
||||
export const NormalizedRuleAction = z
|
||||
.object({
|
||||
group: RuleActionGroup,
|
||||
id: RuleActionId,
|
||||
params: RuleActionParams,
|
||||
frequency: RuleActionFrequency.optional(),
|
||||
alerts_filter: RuleActionAlertsFilter.optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type BulkActionEditPayloadRuleActions = z.infer<typeof BulkActionEditPayloadRuleActions>;
|
||||
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<typeof BulkActionEditPayloadSchedule>;
|
||||
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<typeof BulkActionEditPayloadIndexPatterns>;
|
||||
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<typeof BulkActionEditPayloadTags>;
|
||||
export const BulkActionEditPayloadTags = z.object({
|
||||
type: z.enum(['add_tags', 'delete_tags', 'set_tags']),
|
||||
value: RuleTagArray,
|
||||
});
|
||||
|
||||
export type BulkActionEditPayloadTimeline = z.infer<typeof BulkActionEditPayloadTimeline>;
|
||||
export const BulkActionEditPayloadTimeline = z.object({
|
||||
type: z.literal('set_timeline'),
|
||||
value: z.object({
|
||||
timeline_id: TimelineTemplateId,
|
||||
timeline_title: TimelineTemplateTitle,
|
||||
}),
|
||||
});
|
||||
|
||||
export type BulkActionEditPayload = z.infer<typeof BulkActionEditPayload>;
|
||||
export const BulkActionEditPayload = z.union([
|
||||
BulkActionEditPayloadTags,
|
||||
BulkActionEditPayloadIndexPatterns,
|
||||
BulkActionEditPayloadTimeline,
|
||||
BulkActionEditPayloadRuleActions,
|
||||
BulkActionEditPayloadSchedule,
|
||||
]);
|
||||
|
||||
export type BulkEditRules = z.infer<typeof BulkEditRules>;
|
||||
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<typeof PerformBulkActionRequestQuery>;
|
||||
export const PerformBulkActionRequestQuery = z.object({
|
||||
/**
|
||||
* Enables dry run mode for the request call.
|
||||
*/
|
||||
dry_run: BooleanFromString.optional(),
|
||||
});
|
||||
export type PerformBulkActionRequestQueryInput = z.input<typeof PerformBulkActionRequestQuery>;
|
||||
|
||||
export type PerformBulkActionRequestBody = z.infer<typeof PerformBulkActionRequestBody>;
|
||||
export const PerformBulkActionRequestBody = z.union([
|
||||
BulkDeleteRules,
|
||||
BulkDisableRules,
|
||||
BulkEnableRules,
|
||||
BulkExportRules,
|
||||
BulkDuplicateRules,
|
||||
BulkEditRules,
|
||||
]);
|
||||
export type PerformBulkActionRequestBodyInput = z.input<typeof PerformBulkActionRequestBody>;
|
||||
|
||||
export type PerformBulkActionResponse = z.infer<typeof PerformBulkActionResponse>;
|
||||
export const PerformBulkActionResponse = z.union([
|
||||
BulkEditActionResponse,
|
||||
BulkExportActionResponse,
|
||||
]);
|
|
@ -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'] }],
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<PerformBulkActionRequestBody, 'action'> = {
|
||||
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<PerformBulkActionRequestBody, 'action'> & { 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,
|
||||
action: BulkActionTypeEnum.edit,
|
||||
};
|
||||
|
||||
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 "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'] }],
|
||||
};
|
||||
|
||||
const message = retrieveValidationMessage(payload);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<typeof ThrottleForBulkActions>;
|
||||
export const ThrottleForBulkActions = t.union([
|
||||
t.literal('rule'),
|
||||
t.literal('1h'),
|
||||
t.literal('1d'),
|
||||
t.literal('7d'),
|
||||
]);
|
||||
|
||||
type BulkActionEditPayloadTags = t.TypeOf<typeof BulkActionEditPayloadTags>;
|
||||
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<typeof BulkActionEditPayloadTimeline>;
|
||||
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<typeof NormalizedRuleAction>;
|
||||
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<typeof BulkActionEditPayloadRuleActions>;
|
||||
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<typeof BulkActionEditPayloadSchedule>;
|
||||
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<typeof BulkActionEditPayload>;
|
||||
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<typeof bulkActionDuplicatePayload>;
|
||||
|
||||
/**
|
||||
* 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<typeof PerformBulkActionRequestBody>;
|
||||
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<typeof PerformBulkActionRequestQuery>;
|
||||
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;
|
|
@ -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;
|
|
@ -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"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<typeof FindRulesSortField>;
|
||||
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<typeof FindRulesRequestQuery>;
|
||||
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<typeof FindRulesRequestQuery>;
|
||||
|
||||
export type FindRulesResponse = z.infer<typeof FindRulesResponse>;
|
||||
export const FindRulesResponse = z.object({
|
||||
page: z.number().int(),
|
||||
perPage: z.number().int(),
|
||||
total: z.number().int(),
|
||||
data: z.array(RuleResponse),
|
||||
});
|
|
@ -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
|
|
@ -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<FindRulesRequestQuery> & { madeUp: string } = {
|
||||
test('made up parameters are ignored', () => {
|
||||
const payload: Partial<FindRulesRequestQueryInput> & { 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<FindRulesRequestQuery, 'fields'> & { fields: number } = {
|
||||
const payload: Omit<FindRulesRequestQueryInput, 'fields'> & { 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<FindRulesRequestQuery, 'filter'> & { filter: number } = {
|
||||
const payload: Omit<FindRulesRequestQueryInput, 'filter'> & { 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<FindRulesRequestQuery, 'sort_order'> & { sort_order: string } = {
|
||||
const payload: Omit<FindRulesRequestQueryInput, 'sort_order'> & { 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'"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<typeof FindRulesSortField>;
|
||||
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<typeof FindRulesSortFieldOrUndefined>;
|
||||
export const FindRulesSortFieldOrUndefined = t.union([FindRulesSortField, t.undefined]);
|
||||
|
||||
/**
|
||||
* Query string parameters of the API route.
|
||||
*/
|
||||
export type FindRulesRequestQuery = t.TypeOf<typeof FindRulesRequestQuery>;
|
||||
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[];
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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 [];
|
||||
};
|
||||
|
|
|
@ -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<typeof ImportRulesRequestQuery>;
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<typeof LogLevel>;
|
||||
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<typeof RuleExecutionEventType>;
|
||||
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<typeof RuleExecutionEvent>;
|
||||
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(),
|
||||
});
|
|
@ -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<RuleExecutionEvent> = {}): 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<RuleExecutionEvent> = {}): 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<RuleExecutionEvent> = {}): 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<RuleExecutionEvent> = {}): 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<RuleExecutionEvent> = {}): 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',
|
||||
}),
|
||||
];
|
||||
|
|
|
@ -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.
|
|
@ -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<typeof RuleExecutionEvent>;
|
||||
export const RuleExecutionEvent = t.type({
|
||||
timestamp: IsoDateString,
|
||||
sequence: t.number,
|
||||
level: TLogLevel,
|
||||
type: TRuleExecutionEventType,
|
||||
execution_id: NonEmptyString,
|
||||
message: t.string,
|
||||
});
|
|
@ -15,16 +15,19 @@ import { z } from 'zod';
|
|||
export type RuleExecutionMetrics = z.infer<typeof RuleExecutionMetrics>;
|
||||
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(),
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<typeof DurationMetric>;
|
||||
export const DurationMetric = PositiveInteger;
|
||||
|
||||
export type RuleExecutionMetrics = t.TypeOf<typeof RuleExecutionMetrics>;
|
||||
|
||||
/**
|
||||
@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,
|
||||
});
|
|
@ -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 => {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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<typeof RuleExecutionSummary>;
|
||||
export const RuleExecutionSummary = t.type({
|
||||
last_execution: t.type({
|
||||
date: IsoDateString,
|
||||
status: TRuleExecutionStatus,
|
||||
status_order: t.number,
|
||||
message: t.string,
|
||||
metrics: RuleExecutionMetrics,
|
||||
}),
|
||||
});
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<typeof GetRuleExecutionEventsRequestQuery>;
|
||||
export const GetRuleExecutionEventsRequestQuery = z.object({
|
||||
/**
|
||||
* Include events of matching the search term. If omitted, all events will be included.
|
||||
*/
|
||||
search_term: z.string().optional(),
|
||||
/**
|
||||
* Include events of the specified types. If omitted, all types of events will be included.
|
||||
*/
|
||||
event_types: ArrayFromString(RuleExecutionEventType).optional().default([]),
|
||||
/**
|
||||
* Include events having these log levels. If omitted, events of all levels will be included.
|
||||
*/
|
||||
log_levels: ArrayFromString(LogLevel).optional().default([]),
|
||||
/**
|
||||
* Start date of the time range to query
|
||||
*/
|
||||
start: z.string().datetime(),
|
||||
date_start: z.string().datetime().optional(),
|
||||
/**
|
||||
* End date of the time range to query
|
||||
*/
|
||||
end: z.string().datetime(),
|
||||
/**
|
||||
* Query text to filter results by
|
||||
*/
|
||||
query_text: z.string().optional().default(''),
|
||||
/**
|
||||
* 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([]),
|
||||
/**
|
||||
* Field to sort results by
|
||||
*/
|
||||
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<typeof GetRuleExecutionEventsResponse>;
|
||||
export const GetRuleExecutionEventsResponse = z.object({
|
||||
events: z.array(RuleExecutionResult).optional(),
|
||||
total: z.number().int().optional(),
|
||||
events: z.array(RuleExecutionEvent),
|
||||
pagination: PaginationResult,
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
required: false
|
||||
description: Include events of matching the search term. If omitted, all events will be included.
|
||||
schema:
|
||||
type: string
|
||||
- name: event_types
|
||||
in: query
|
||||
required: false
|
||||
description: Include events of the specified types. If omitted, all types of events will be included.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '../../model/execution_event.schema.yaml#/components/schemas/RuleExecutionEventType'
|
||||
default: []
|
||||
- name: log_levels
|
||||
in: query
|
||||
required: false
|
||||
description: Include events having these log levels. If omitted, events of all levels will be included.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '../../model/execution_event.schema.yaml#/components/schemas/LogLevel'
|
||||
default: []
|
||||
- name: date_start
|
||||
in: query
|
||||
required: false
|
||||
description: Start date of the time range to query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: end
|
||||
- name: date_end
|
||||
in: query
|
||||
required: true
|
||||
required: false
|
||||
description: End date of the time range to query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: query_text
|
||||
in: query
|
||||
required: false
|
||||
description: Query text to filter results by
|
||||
schema:
|
||||
type: string
|
||||
default: ''
|
||||
- name: status_filters
|
||||
in: query
|
||||
required: false
|
||||
description: Comma-separated list of rule execution statuses to filter results by
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '../../model/execution_status.schema.yaml#/components/schemas/RuleExecutionStatus'
|
||||
default: []
|
||||
- name: sort_field
|
||||
in: query
|
||||
required: false
|
||||
description: Field to sort results by
|
||||
schema:
|
||||
$ref: '../../model/execution_result.schema.yaml#/components/schemas/SortFieldOfRuleExecutionResult'
|
||||
default: timestamp
|
||||
- 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
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<typeof GetRuleExecutionEventsResponse>;
|
||||
export const GetRuleExecutionEventsResponse = t.exact(
|
||||
t.type({
|
||||
events: t.array(RuleExecutionEvent),
|
||||
pagination: PaginationResult,
|
||||
})
|
||||
);
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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<typeof signalsReindexOptions>;
|
|||
export const createSignalsMigrationSchema = t.intersection([
|
||||
t.exact(
|
||||
t.type({
|
||||
index: IndexPatternArray,
|
||||
index: t.array(t.string),
|
||||
})
|
||||
),
|
||||
t.exact(signalsReindexOptions),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
});
|
|
@ -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', () => {
|
|
@ -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,
|
||||
}),
|
||||
})
|
||||
);
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<string, unknown>;
|
||||
}>;
|
||||
}>;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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<BulkActionResponse>(DETECTION_ENGINE_RULES_BULK_ACTION, {
|
||||
|
@ -392,7 +391,7 @@ export type BulkExportResponse = Blob;
|
|||
*/
|
||||
export async function bulkExportRules(queryOrIds: QueryOrIds): Promise<BulkExportResponse> {
|
||||
const params = {
|
||||
action: BulkActionType.export,
|
||||
action: BulkActionTypeEnum.export,
|
||||
query: queryOrIds.query,
|
||||
ids: queryOrIds.ids,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<BulkActionType, BulkActionType.edit>,
|
||||
action: Exclude<BulkActionType, BulkActionTypeEnum['edit']>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<typeof PaginationOptions>;
|
||||
export const PaginationOptions = t.type({
|
||||
page: PositiveInteger,
|
||||
perPage: PositiveInteger,
|
||||
total: PositiveInteger,
|
||||
export type PaginationOptions = z.infer<typeof PaginationOptions>;
|
||||
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<typeof SortingOptions>;
|
||||
export const SortingOptions = t.type({
|
||||
export type SortingOptions = z.infer<typeof SortingOptions>;
|
||||
export const SortingOptions = z.object({
|
||||
field: FindRulesSortField,
|
||||
order: SortOrder,
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
<BulkActionRuleErrorsList bulkAction={BulkActionType.edit} ruleErrors={[]} />,
|
||||
<BulkActionRuleErrorsList bulkAction={BulkActionTypeEnum.edit} ruleErrors={[]} />,
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
|
@ -46,9 +46,12 @@ describe('Component BulkEditRuleErrorsList', () => {
|
|||
ruleIds: ['rule:1'],
|
||||
},
|
||||
];
|
||||
render(<BulkActionRuleErrorsList bulkAction={BulkActionType.edit} ruleErrors={ruleErrors} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
render(
|
||||
<BulkActionRuleErrorsList bulkAction={BulkActionTypeEnum.edit} ruleErrors={ruleErrors} />,
|
||||
{
|
||||
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(<BulkActionRuleErrorsList bulkAction={BulkActionType.edit} ruleErrors={ruleErrors} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
render(
|
||||
<BulkActionRuleErrorsList bulkAction={BulkActionTypeEnum.edit} ruleErrors={ruleErrors} />,
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
expect(screen.getByText(value)).toBeInTheDocument();
|
||||
});
|
||||
|
|
|
@ -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 (
|
||||
<BulkEditRuleErrorItem
|
||||
message={message}
|
||||
|
@ -141,7 +141,7 @@ const BulkActionRuleErrorsListComponent = ({
|
|||
/>
|
||||
);
|
||||
|
||||
case BulkActionType.export:
|
||||
case BulkActionTypeEnum.export:
|
||||
return (
|
||||
<BulkExportRuleErrorItem
|
||||
message={message}
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
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 { IndexPatternsForm } from './forms/index_patterns_form';
|
||||
import { TagsForm } from './forms/tags_form';
|
||||
|
@ -25,23 +28,23 @@ interface BulkEditFlyoutProps {
|
|||
|
||||
const BulkEditFlyoutComponent = ({ editAction, ...props }: BulkEditFlyoutProps) => {
|
||||
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 <IndexPatternsForm {...props} editAction={editAction} />;
|
||||
|
||||
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 <TagsForm {...props} editAction={editAction} />;
|
||||
|
||||
case BulkActionEditType.set_timeline:
|
||||
case BulkActionEditTypeEnum.set_timeline:
|
||||
return <TimelineTemplateForm {...props} />;
|
||||
|
||||
case BulkActionEditType.add_rule_actions:
|
||||
case BulkActionEditType.set_rule_actions:
|
||||
case BulkActionEditTypeEnum.add_rule_actions:
|
||||
case BulkActionEditTypeEnum.set_rule_actions:
|
||||
return <RuleActionsForm {...props} />;
|
||||
case BulkActionEditType.set_schedule:
|
||||
case BulkActionEditTypeEnum.set_schedule:
|
||||
return <ScheduleForm {...props} />;
|
||||
|
||||
default:
|
||||
|
|
|
@ -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 && (
|
||||
<CommonUseField
|
||||
path="overwrite"
|
||||
componentProps={{
|
||||
|
@ -161,7 +159,7 @@ const IndexPatternsFormComponent = ({
|
|||
</EuiCallOut>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
{editAction === BulkActionEditType.add_index_patterns && (
|
||||
{editAction === BulkActionEditTypeEnum.add_index_patterns && (
|
||||
<CommonUseField
|
||||
path="overwriteDataViews"
|
||||
componentProps={{
|
||||
|
@ -180,7 +178,7 @@ const IndexPatternsFormComponent = ({
|
|||
</EuiCallOut>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
{editAction === BulkActionEditType.delete_index_patterns && (
|
||||
{editAction === BulkActionEditTypeEnum.delete_index_patterns && (
|
||||
<EuiFormRow fullWidth>
|
||||
<EuiCallOut color="warning" size="s" data-test-subj="bulkEditRulesDataViewsWarning">
|
||||
<FormattedMessage
|
||||
|
|
|
@ -23,8 +23,8 @@ import {
|
|||
getUseField,
|
||||
Field,
|
||||
} from '../../../../../../shared_imports';
|
||||
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 { BulkEditFormWrapper } from './bulk_edit_form_wrapper';
|
||||
import { bulkAddRuleActions as i18n } from '../translations';
|
||||
|
@ -99,8 +99,8 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction
|
|||
|
||||
const { actions = [], overwrite: overwriteValue } = data;
|
||||
const editAction = overwriteValue
|
||||
? BulkActionEditType.set_rule_actions
|
||||
: BulkActionEditType.add_rule_actions;
|
||||
? BulkActionEditTypeEnum.set_rule_actions
|
||||
: BulkActionEditTypeEnum.add_rule_actions;
|
||||
|
||||
onConfirm({
|
||||
type: editAction,
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
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 { ScheduleItem } from '../../../../../../detections/components/rules/schedule_item_form';
|
||||
import type { FormSchema } from '../../../../../../shared_imports';
|
||||
import { UseField, useForm } from '../../../../../../shared_imports';
|
||||
|
@ -55,7 +55,7 @@ export const ScheduleForm = ({ rulesCount, onClose, onConfirm }: ScheduleFormCom
|
|||
}
|
||||
|
||||
onConfirm({
|
||||
type: BulkActionEditType.set_schedule,
|
||||
type: BulkActionEditTypeEnum.set_schedule,
|
||||
value: {
|
||||
interval: data.interval,
|
||||
lookback: data.lookback,
|
||||
|
|
|
@ -10,8 +10,8 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import React, { useMemo } from 'react';
|
||||
|
||||
import { useRuleManagementFilters } from '../../../../../rule_management/logic/use_rule_management_filters';
|
||||
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 * as i18n from '../../../../../../detections/pages/detection_engine/rules/translations';
|
||||
import { caseInsensitiveSort } from '../../helpers';
|
||||
|
||||
|
@ -28,9 +28,9 @@ import {
|
|||
import { BulkEditFormWrapper } from './bulk_edit_form_wrapper';
|
||||
|
||||
type TagsEditActions =
|
||||
| BulkActionEditType.add_tags
|
||||
| BulkActionEditType.delete_tags
|
||||
| BulkActionEditType.set_tags;
|
||||
| BulkActionEditTypeEnum['add_tags']
|
||||
| BulkActionEditTypeEnum['delete_tags']
|
||||
| BulkActionEditTypeEnum['set_tags'];
|
||||
|
||||
const CommonUseField = getUseField({ component: Field });
|
||||
|
||||
|
@ -58,7 +58,7 @@ const schema: FormSchema<TagsFormData> = {
|
|||
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 ? (
|
||||
<CommonUseField
|
||||
path="overwrite"
|
||||
componentProps={{
|
||||
|
|
|
@ -11,8 +11,8 @@ import { EuiCallOut } from '@elastic/eui';
|
|||
import type { FormSchema } from '../../../../../../shared_imports';
|
||||
import { useForm, UseField } from '../../../../../../shared_imports';
|
||||
import { PickTimeline } from '../../../../../../detections/components/rules/pick_timeline';
|
||||
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 { BulkEditFormWrapper } from './bulk_edit_form_wrapper';
|
||||
import { bulkApplyTimelineTemplate as i18n } from '../translations';
|
||||
|
@ -62,7 +62,7 @@ const TimelineTemplateFormComponent = (props: TimelineTemplateFormProps) => {
|
|||
const timelineTitle = timelineId ? data.timeline.title : '';
|
||||
|
||||
onConfirm({
|
||||
type: BulkActionEditType.set_timeline,
|
||||
type: BulkActionEditTypeEnum.set_timeline,
|
||||
value: {
|
||||
timeline_id: timelineId,
|
||||
timeline_title: timelineTitle,
|
||||
|
|
|
@ -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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue