From 788b71952e54ee7071da0de600568cd13bccb0e3 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 6 Nov 2024 07:39:53 +0100 Subject: [PATCH 01/28] add eql query editable component --- .../public/common/hooks/eql/api.ts | 27 ++-- .../components/eql_query_bar/validators.ts | 112 +---------------- .../components/step_define_rule/schema.tsx | 119 ++++++------------ .../step_define_rule/translations.tsx | 28 ----- .../components/step_define_rule/utils.ts | 25 +--- .../validators/debounce_async.ts | 37 ++++++ .../validators/eql_query_validator_factory.ts | 94 ++++++++++++++ .../validators/query_validator_factory.ts | 74 +++++++++++ .../final_edit/eql_rule_field_edit.tsx | 3 + .../eql_query/eql_query_edit_adapter.tsx | 104 +++++++++++++++ .../fields/eql_query/eql_query_edit_form.tsx | 88 +++++++++++++ .../final_edit/fields/eql_query/index.ts | 8 ++ .../fields/eql_query/translations.ts | 15 +++ .../timeline/query_bar/eql/index.tsx | 17 ++- 14 files changed, 492 insertions(+), 259 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_validator_factory.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/index.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/translations.ts diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts index 569344297f319..f9421f161f040 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts @@ -31,9 +31,9 @@ interface Params { dataViewTitle: string; query: string; data: DataPublicPluginStart; - signal: AbortSignal; runtimeMappings: estypes.MappingRuntimeFields | undefined; - options: Omit | undefined; + eqlOptions: Omit | undefined; + signal?: AbortSignal; } export interface EqlResponseError { @@ -51,9 +51,9 @@ export const validateEql = async ({ data, dataViewTitle, query, - signal, runtimeMappings, - options, + eqlOptions, + signal, }: Params): Promise => { try { const { rawResponse: response } = await firstValueFrom( @@ -62,9 +62,9 @@ export const validateEql = async ({ params: { index: dataViewTitle, body: { query, runtime_mappings: runtimeMappings, size: 0 }, - timestamp_field: options?.timestampField, - tiebreaker_field: options?.tiebreakerField || undefined, - event_category_field: options?.eventCategoryField, + timestamp_field: eqlOptions?.timestampField, + tiebreaker_field: eqlOptions?.tiebreakerField, + event_category_field: eqlOptions?.eventCategoryField, }, options: { ignore: [400] }, }, @@ -79,19 +79,23 @@ export const validateEql = async ({ valid: false, error: { code: EQL_ERROR_CODES.INVALID_SYNTAX, messages: getValidationErrors(response) }, }; - } else if (isVerificationErrorResponse(response) || isMappingErrorResponse(response)) { + } + + if (isVerificationErrorResponse(response) || isMappingErrorResponse(response)) { return { valid: false, error: { code: EQL_ERROR_CODES.INVALID_EQL, messages: getValidationErrors(response) }, }; - } else if (isErrorResponse(response)) { + } + + if (isErrorResponse(response)) { return { valid: false, error: { code: EQL_ERROR_CODES.FAILED_REQUEST, error: new Error(JSON.stringify(response)) }, }; - } else { - return { valid: true }; } + + return { valid: true }; } catch (error) { if (error instanceof Error && error.message.startsWith('index_not_found_exception')) { return { @@ -99,6 +103,7 @@ export const validateEql = async ({ error: { code: EQL_ERROR_CODES.MISSING_DATA_SOURCE, messages: [error.message] }, }; } + return { valid: false, error: { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts index 8cd9a4d60745e..676a780d9daf5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts @@ -5,116 +5,8 @@ * 2.0. */ -import { isEmpty } from 'lodash'; - -import type { FieldHook, ValidationError, ValidationFunc } from '../../../../shared_imports'; -import { isEqlRule } from '../../../../../common/detection_engine/utils'; -import { KibanaServices } from '../../../../common/lib/kibana'; -import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; -import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types'; -import type { EqlResponseError } from '../../../../common/hooks/eql/api'; -import { validateEql, EQL_ERROR_CODES } from '../../../../common/hooks/eql/api'; -import type { FieldValueQueryBar } from '../query_bar'; -import * as i18n from './translations'; - -/** - * Unlike lodash's debounce, which resolves intermediate calls with the most - * recent value, this implementation waits to resolve intermediate calls until - * the next invocation resolves. - * - * @param fn an async function - * - * @returns A debounced async function that resolves on the next invocation - */ -export const debounceAsync = >( - fn: (...args: Args) => Result, - interval: number -): ((...args: Args) => Result) => { - let handle: ReturnType | undefined; - let resolves: Array<(value?: Result) => void> = []; - - return (...args: Args): Result => { - if (handle) { - clearTimeout(handle); - } - - handle = setTimeout(() => { - const result = fn(...args); - resolves.forEach((resolve) => resolve(result)); - resolves = []; - }, interval); - - return new Promise((resolve) => resolves.push(resolve)) as Result; - }; -}; - -export const transformEqlResponseErrorToValidationError = ( - responseError: EqlResponseError -): ValidationError => { - if (responseError.error) { - return { - code: EQL_ERROR_CODES.FAILED_REQUEST, - message: i18n.EQL_VALIDATION_REQUEST_ERROR, - error: responseError.error, - }; - } - return { - code: responseError.code, - message: '', - messages: responseError.messages, - }; -}; - -export const eqlValidator = async ( - ...args: Parameters -): Promise | void | undefined> => { - const [{ value, formData }] = args; - const { query: queryValue } = value as FieldValueQueryBar; - const query = queryValue.query as string; - const { dataViewId, index, ruleType, eqlOptions } = formData as DefineStepRule; - - const needsValidation = - (ruleType === undefined && !isEmpty(query)) || (isEqlRule(ruleType) && !isEmpty(query)); - if (!needsValidation) { - return; - } - - try { - const { data } = KibanaServices.get(); - let dataViewTitle = index?.join(); - let runtimeMappings = {}; - if ( - dataViewId != null && - dataViewId !== '' && - formData.dataSourceType === DataSourceType.DataView - ) { - const dataView = await data.dataViews.get(dataViewId); - - dataViewTitle = dataView.title; - runtimeMappings = dataView.getRuntimeMappings(); - } - - const signal = new AbortController().signal; - const response = await validateEql({ - data, - query, - signal, - dataViewTitle, - runtimeMappings, - options: eqlOptions, - }); - - if (response?.valid === false && response.error) { - return transformEqlResponseErrorToValidationError(response.error); - } - } catch (error) { - return { - code: EQL_ERROR_CODES.FAILED_REQUEST, - message: i18n.EQL_VALIDATION_REQUEST_ERROR, - error, - }; - } -}; +import type { FieldHook } from '../../../../shared_imports'; +import { EQL_ERROR_CODES } from '../../../../common/hooks/eql/api'; export const getValidationResults = ( field: FieldHook diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx index 6563c0d3b175f..530db8797a148 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx @@ -10,7 +10,6 @@ import { i18n } from '@kbn/i18n'; import { EuiText } from '@elastic/eui'; import React from 'react'; -import { fromKueryExpression } from '@kbn/es-query'; import { singleEntryThreat, containsInvalidItems, @@ -27,19 +26,16 @@ import { } from '../../../../../common/detection_engine/utils'; import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../../common/constants'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; -import type { FieldValueQueryBar } from '../query_bar'; import type { ERROR_CODE, FormSchema, ValidationFunc } from '../../../../shared_imports'; import { FIELD_TYPES, fieldValidators } from '../../../../shared_imports'; +import { debounceAsync } from '../../validators/debounce_async'; import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types'; -import { debounceAsync, eqlValidator } from '../eql_query_bar/validators'; import { esqlValidator } from '../../../rule_creation/logic/esql_validator'; import { dataViewIdValidatorFactory } from '../../validators/data_view_id_validator_factory'; import { indexPatternValidatorFactory } from '../../validators/index_pattern_validator_factory'; import { alertSuppressionFieldsValidatorFactory } from '../../validators/alert_suppression_fields_validator_factory'; import { - CUSTOM_QUERY_REQUIRED, - INVALID_CUSTOM_QUERY, INDEX_HELPER_TEXT, THREAT_MATCH_INDEX_HELPER_TEXT, THREAT_MATCH_REQUIRED, @@ -53,6 +49,10 @@ import { ALERT_SUPPRESSION_MISSING_FIELDS_FIELD_NAME, } from '../../../rule_creation/components/alert_suppression_edit'; import * as alertSuppressionEditI81n from '../../../rule_creation/components/alert_suppression_edit/components/translations'; +import { dataViewIdValidatorFactory } from '../../validators/data_view_id_validator_factory'; +import { indexPatternValidatorFactory } from '../../validators/index_pattern_validator_factory'; +import { queryValidatorFactory } from '../../validators/query_validator_factory'; +import { eqlQueryValidatorFactory } from '../../validators/eql_query_validator_factory'; export const schema: FormSchema = { index: { @@ -68,7 +68,7 @@ export const schema: FormSchema = { helpText: {INDEX_HELPER_TEXT}, validations: [ { - validator: (...args: Parameters) => { + validator: (...args) => { const [{ formData }] = args; if ( @@ -94,7 +94,7 @@ export const schema: FormSchema = { fieldsToValidateOnChange: ['dataViewId'], validations: [ { - validator: (...args: Parameters) => { + validator: (...args) => { const [{ formData }] = args; if (isMlRule(formData.ruleType) || formData.dataSourceType !== DataSourceType.DataView) { @@ -122,56 +122,44 @@ export const schema: FormSchema = { fieldsToValidateOnChange: ['queryBar', ALERT_SUPPRESSION_FIELDS_FIELD_NAME], validations: [ { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ value, path, formData }] = args; - const { query, filters, saved_id: savedId } = value as FieldValueQueryBar; - const needsValidation = !isMlRule(formData.ruleType); - if (!needsValidation) { - return undefined; - } - const isFieldEmpty = isEmpty(query.query as string) && isEmpty(filters); - if (!isFieldEmpty) { - return undefined; - } - if (savedId) { + validator: (...args) => { + const [{ value, formData }] = args; + + if (isMlRule(formData.ruleType) || value.saved_id) { // Ignore field validation error in this case. // Instead, we show the error toast when saved query object does not exist. // https://github.com/elastic/kibana/issues/159060 - return undefined; + return; } - const message = getQueryRequiredMessage(formData.ruleType); - return { code: 'ERR_FIELD_MISSING', path, message }; + + return queryValidatorFactory(formData.ruleType)(...args); }, }, { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ value, path, formData }] = args; - const { query } = value as FieldValueQueryBar; - const needsValidation = !isMlRule(formData.ruleType); - if (!needsValidation) { + validator: (...args) => { + const [{ formData }] = args; + const { + dataSourceType, + dataViewId = '', + index, + ruleType, + eqlOptions, + } = formData as DefineStepRule; + + if (!isEqlRule(ruleType)) { return; } - if (!isEmpty(query.query as string) && query.language === 'kuery') { - try { - fromKueryExpression(query.query); - } catch (err) { - return { - code: 'ERR_FIELD_FORMAT', - path, - message: INVALID_CUSTOM_QUERY, - }; - } - } + return debounceAsync( + eqlQueryValidatorFactory( + dataSourceType === DataSourceType.DataView + ? { dataViewId, eqlOptions } + : { indexPattern: index, eqlOptions } + ), + 300 + )(...args); }, }, - { - validator: debounceAsync(eqlValidator, 300), - }, { validator: debounceAsync(esqlValidator, 300), }, @@ -509,48 +497,13 @@ export const schema: FormSchema = { ), validations: [ { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { + validator: (...args) => { const [{ value, path, formData }] = args; - const needsValidation = isThreatMatchRule(formData.ruleType); - if (!needsValidation) { + if (!isThreatMatchRule(formData.ruleType)) { return; } - const { query, filters } = value as FieldValueQueryBar; - - return isEmpty(query.query as string) && isEmpty(filters) - ? { - code: 'ERR_FIELD_MISSING', - path, - message: CUSTOM_QUERY_REQUIRED, - } - : undefined; - }, - }, - { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ value, path, formData }] = args; - const needsValidation = isThreatMatchRule(formData.ruleType); - if (!needsValidation) { - return; - } - const { query } = value as FieldValueQueryBar; - - if (!isEmpty(query.query as string) && query.language === 'kuery') { - try { - fromKueryExpression(query.query); - } catch (err) { - return { - code: 'ERR_FIELD_FORMAT', - path, - message: INVALID_CUSTOM_QUERY, - }; - } - } + return queryValidatorFactory(formData.ruleType)(...args); }, }, ], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx index d8b24f978afd0..b2b219ad6a336 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx @@ -9,34 +9,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -export const CUSTOM_QUERY_REQUIRED = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError', - { - defaultMessage: 'A custom query is required.', - } -); - -export const EQL_QUERY_REQUIRED = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlQueryFieldRequiredError', - { - defaultMessage: 'An EQL query is required.', - } -); - -export const ESQL_QUERY_REQUIRED = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryFieldRequiredError', - { - defaultMessage: 'An ES|QL query is required.', - } -); - -export const INVALID_CUSTOM_QUERY = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError', - { - defaultMessage: 'The KQL is invalid', - } -); - export const INDEX_HELPER_TEXT = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesHelperDescription', { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts index 88592da7ecd8d..67d8565947bad 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts @@ -5,38 +5,21 @@ * 2.0. */ -import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import type { FieldSpec } from '@kbn/data-plugin/common'; -import { CUSTOM_QUERY_REQUIRED, EQL_QUERY_REQUIRED, ESQL_QUERY_REQUIRED } from './translations'; - -import { isEqlRule, isEsqlRule } from '../../../../../common/detection_engine/utils'; - /** * Filters out fields, that are not supported in terms aggregation. * Terms aggregation supports limited number of types: * Keyword, Numeric, ip, boolean, or binary. * https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html */ -export const getTermsAggregationFields = (fields: FieldSpec[]): FieldSpec[] => +export const getTermsAggregationFields = (fields: FieldSpec[]): FieldSpec[] => { fields.filter( (field) => field.aggregatable === true && ALLOWED_AGGREGATABLE_FIELD_TYPES_SET.has(field.type) ); + return fields.filter((field) => field.aggregatable === true && allowedTypesSet.has(field.type)); +}; + // binary types is excluded, as binary field has property aggregatable === false const ALLOWED_AGGREGATABLE_FIELD_TYPES_SET = new Set(['string', 'number', 'ip', 'boolean']); - -/** - * return query is required message depends on a rule type - */ -export const getQueryRequiredMessage = (ruleType: Type) => { - if (isEsqlRule(ruleType)) { - return ESQL_QUERY_REQUIRED; - } - - if (isEqlRule(ruleType)) { - return EQL_QUERY_REQUIRED; - } - - return CUSTOM_QUERY_REQUIRED; -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.ts new file mode 100644 index 0000000000000..f9de51e4a2e65 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Unlike lodash's debounce, which resolves intermediate calls with the most + * recent value, this implementation waits to resolve intermediate calls until + * the next invocation resolves. + * + * @param fn an async function + * + * @returns A debounced async function that resolves on the next invocation + */ +export const debounceAsync = ( + fn: (...args: Args) => Result, + interval: number +): ((...args: Args) => Result) => { + let handle: ReturnType | undefined; + let resolves: Array<(value?: Result) => void> = []; + + return (...args: Args): Result => { + if (handle) { + clearTimeout(handle); + } + + handle = setTimeout(() => { + const result = fn(...args); + resolves.forEach((resolve) => resolve(result)); + resolves = []; + }, interval); + + return new Promise((resolve) => resolves.push(resolve)) as Result; + }; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts new file mode 100644 index 0000000000000..9a05dea4e11e8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts @@ -0,0 +1,94 @@ +/* + * 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 { isEmpty } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import type { FormData, ValidationError, ValidationFunc } from '../../../shared_imports'; +import { KibanaServices } from '../../../common/lib/kibana'; +import type { EqlOptionsSelected } from '../../../../common/search_strategy'; +import type { FieldValueQueryBar } from '../components/query_bar'; +import type { EqlResponseError } from '../../../common/hooks/eql/api'; +import { EQL_ERROR_CODES, validateEql } from '../../../common/hooks/eql/api'; +import { isDataViewIdValid } from './data_view_id_validator_factory'; + +type EqlQueryValidatorFactoryParams = + | { + indexPattern: string[]; + dataViewId?: never; + eqlOptions: EqlOptionsSelected; + } + | { + indexPattern?: never; + dataViewId: string; + eqlOptions: EqlOptionsSelected; + }; + +export function eqlQueryValidatorFactory({ + indexPattern, + dataViewId, + eqlOptions, +}: EqlQueryValidatorFactoryParams): ValidationFunc { + return async (...args) => { + const [{ value }] = args; + + if (isEmpty(value.query.query)) { + return; + } + + try { + const { data } = KibanaServices.get(); + const dataView = isDataViewIdValid(dataViewId) + ? await data.dataViews.get(dataViewId) + : undefined; + const dataViewTitle = dataView?.getIndexPattern() ?? indexPattern?.join() ?? ''; + const runtimeMappings = dataView?.getRuntimeMappings() ?? {}; + + const response = await validateEql({ + data, + query: value.query.query as string, + dataViewTitle, + runtimeMappings, + eqlOptions, + }); + + if (response?.valid === false && response.error) { + return transformEqlResponseErrorToValidationError(response.error); + } + } catch (error) { + return { + code: EQL_ERROR_CODES.FAILED_REQUEST, + message: EQL_VALIDATION_REQUEST_ERROR, + error, + }; + } + }; +} + +function transformEqlResponseErrorToValidationError( + responseError: EqlResponseError +): ValidationError { + if (responseError.error) { + return { + code: EQL_ERROR_CODES.FAILED_REQUEST, + message: EQL_VALIDATION_REQUEST_ERROR, + error: responseError.error, + }; + } + + return { + code: responseError.code, + message: '', + messages: responseError.messages, + }; +} + +const EQL_VALIDATION_REQUEST_ERROR = i18n.translate( + 'xpack.securitySolution.ruleManagement.eqlValidation.requestError', + { + defaultMessage: 'An error occurred while validating your EQL query', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_validator_factory.ts new file mode 100644 index 0000000000000..6f527a5a81454 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_validator_factory.ts @@ -0,0 +1,74 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; +import { fromKueryExpression } from '@kbn/es-query'; +import type { RuleType } from '@kbn/securitysolution-rules'; +import type { FormData, ValidationFunc } from '../../../shared_imports'; +import { isEqlRule, isEsqlRule } from '../../../../common/detection_engine/utils'; +import type { FieldValueQueryBar } from '../components/query_bar'; + +export function queryValidatorFactory( + ruleType: RuleType +): ValidationFunc { + return (...args) => { + const [{ path, value }] = args; + + if (isEmpty(value.query.query as string) && isEmpty(value.filters)) { + return { + code: 'ERR_FIELD_MISSING', + path, + message: getErrorMessage(ruleType), + }; + } + + if (!isEmpty(value.query.query) && value.query.language === 'kuery') { + try { + fromKueryExpression(value.query.query); + } catch (err) { + return { + code: 'ERR_FIELD_FORMAT', + path, + message: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError', + { + defaultMessage: 'The KQL is invalid', + } + ), + }; + } + } + }; +} + +function getErrorMessage(ruleType: RuleType): string { + if (isEsqlRule(ruleType)) { + return i18n.translate( + 'xpack.securitySolution.ruleManagement.ruleCreation.validation.query.esqlQueryFieldRequiredError', + { + defaultMessage: 'An ES|QL query is required.', + } + ); + } + + if (isEqlRule(ruleType)) { + return i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlQueryFieldRequiredError', + { + defaultMessage: 'An EQL query is required.', + } + ); + } + + return i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError', + { + defaultMessage: 'A custom query is required.', + } + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx index dba33e57d56a7..2893a61a1f471 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx @@ -9,6 +9,7 @@ import React from 'react'; import type { UpgradeableEqlFields } from '../../../../model/prebuilt_rule_upgrade/fields'; import { DataSourceEditForm } from './fields/data_source'; import { AlertSuppressionEditForm } from './fields/alert_suppression'; +import { EqlQueryEditForm } from './fields/eql_query'; interface EqlRuleFieldEditProps { fieldName: UpgradeableEqlFields; @@ -16,6 +17,8 @@ interface EqlRuleFieldEditProps { export function EqlRuleFieldEdit({ fieldName }: EqlRuleFieldEditProps) { switch (fieldName) { + case 'eql_query': + return ; case 'data_source': return ; case 'alert_suppression': diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx new file mode 100644 index 0000000000000..edc8e79b24445 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx @@ -0,0 +1,104 @@ +/* + * 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 React, { useMemo } from 'react'; +import type { DataViewBase } from '@kbn/es-query'; +import { EqlQueryBar } from '../../../../../../../rule_creation_ui/components/eql_query_bar'; +import { UseField } from '../../../../../../../../shared_imports'; +import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; +import { useDiffableRuleDataView } from '../hooks/use_diffable_rule_data_view'; +import { queryValidatorFactory } from '../../../../../../../rule_creation_ui/validators/query_validator_factory'; +import { debounceAsync } from '../../../../../../../rule_creation_ui/validators/debounce_async'; +import { eqlQueryValidatorFactory } from '../../../../../../../rule_creation_ui/validators/eql_query_validator_factory'; +import * as i18n from './translations'; + +export function EqlQueryEditAdapter({ + finalDiffableRule, +}: RuleFieldEditComponentProps): JSX.Element { + const { dataView, isLoading } = useDiffableRuleDataView(finalDiffableRule); + const optionsData = useMemo( + () => + !dataView + ? { + keywordFields: [], + dateFields: [], + nonDateFields: [], + } + : { + keywordFields: dataView.fields + .filter((f) => f.esTypes?.includes('keyword')) + .map((f) => ({ label: f.name })), + dateFields: dataView.fields + .filter((f) => f.type === 'date') + .map((f) => ({ label: f.name })), + nonDateFields: dataView.fields + .filter((f) => f.type !== 'date') + .map((f) => ({ label: f.name })), + }, + [dataView] + ); + + const componentProps = useMemo( + () => ({ + optionsData, + optionsSelected: {}, + isSizeOptionDisabled: true, + isDisabled: isLoading, + isLoading, + indexPattern: dataView ?? DEFAULT_DATA_VIEW_BASE, + showFilterBar: true, + idAria: 'ruleEqlQueryBar', + dataTestSubj: 'ruleEqlQueryBar', + }), + [optionsData, dataView, isLoading] + ); + const fieldConfig = useMemo(() => { + if (finalDiffableRule.type !== 'eql') { + return { + label: i18n.EQL_QUERY_BAR_LABEL, + }; + } + + const eqlOptions = { + eventCategoryField: finalDiffableRule.event_category_override, + tiebreakerField: finalDiffableRule.tiebreaker_field, + timestampField: finalDiffableRule.timestamp_field, + }; + + return { + label: i18n.EQL_QUERY_BAR_LABEL, + validations: [ + { + validator: queryValidatorFactory('eql'), + }, + { + validator: (...args) => { + return debounceAsync( + eqlQueryValidatorFactory({ dataViewId: dataView?.id ?? '', eqlOptions }), + 300 + )(...args); + }, + }, + ], + }; + }, [dataView, finalDiffableRule]); + + return ( + + ); +} + +const DEFAULT_DATA_VIEW_BASE: DataViewBase = { + title: '', + fields: [], +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx new file mode 100644 index 0000000000000..2296652ac6f2b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx @@ -0,0 +1,88 @@ +/* + * 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 React from 'react'; +import type { Filter } from '@kbn/es-query'; +import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; +import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; +import type { FieldValueQueryBar } from '../../../../../../../rule_creation_ui/components/query_bar'; +import { + type DiffableRule, + RuleEqlQuery, + QueryLanguageEnum, +} from '../../../../../../../../../common/api/detection_engine'; +import { EqlQueryEditAdapter } from './eql_query_edit_adapter'; +// import { +// debounceAsync, +// eqlValidator, +// } from '@kbn/security-solution-plugin/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators'; + +export function EqlQueryEditForm(): JSX.Element { + return ( + + ); +} + +const kqlQuerySchema = { + eqlQuery: { + validations: [ + // { + // validator: debounceAsync(eqlValidator, 300), + // }, + ], + }, +} as FormSchema<{ + eqlQuery: RuleEqlQuery; +}>; + +function deserializer( + fieldValue: FormData, + finalDiffableRule: DiffableRule +): { + eqlQuery: FieldValueQueryBar; +} { + const parsedEqlQuery = + 'eql_query' in finalDiffableRule + ? RuleEqlQuery.parse(fieldValue.eql_query) + : { + query: '', + language: QueryLanguageEnum.eql, + filters: [], + }; + + return { + eqlQuery: { + query: { + query: parsedEqlQuery.query, + language: parsedEqlQuery.language, + }, + // cast to Filter since RuleEqlQuery checks it's an array + // potentially it might be incompatible type + filters: parsedEqlQuery.filters as Filter[], + saved_id: null, + }, + }; +} + +function serializer(formData: FormData): { + eql_query: RuleEqlQuery; +} { + const formValue = formData as { eqlQuery: FieldValueQueryBar }; + + return { + eql_query: { + query: formValue.eqlQuery.query.query as string, + language: QueryLanguageEnum.eql, + filters: formValue.eqlQuery.filters, + }, + }; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/index.ts new file mode 100644 index 0000000000000..c11bd722a717d --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './eql_query_edit_form'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/translations.ts new file mode 100644 index 0000000000000..aba4c787d7bc3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const EQL_QUERY_BAR_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.EqlQueryBarLabel', + { + defaultMessage: 'EQL query', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index 54b5a4a9aae2f..599f03d7657ca 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -18,11 +18,8 @@ import { useSourcererDataView } from '../../../../../sourcerer/containers'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import { SourcererScopeName } from '../../../../../sourcerer/store/model'; import { EqlQueryBar } from '../../../../../detection_engine/rule_creation_ui/components/eql_query_bar'; - -import { - debounceAsync, - eqlValidator, -} from '../../../../../detection_engine/rule_creation_ui/components/eql_query_bar/validators'; +import { debounceAsync } from '../../../../../detection_engine/rule_creation_ui/validators/debounce_async'; +import { eqlQueryValidatorFactory } from '../../../../../detection_engine/rule_creation_ui/validators/eql_query_validator_factory'; import type { FieldValueQueryBar } from '../../../../../detection_engine/rule_creation_ui/components/query_bar'; import type { FormSchema } from '../../../../../shared_imports'; @@ -58,7 +55,15 @@ const schema: FormSchema = { eqlQueryBar: { validations: [ { - validator: debounceAsync(eqlValidator, 300), + validator: (...args) => { + const [{ formData }] = args; + const { index, eqlOptions } = formData; + + return debounceAsync( + eqlQueryValidatorFactory({ indexPattern: index, eqlOptions }), + 300 + )(...args); + }, }, ], }, From f1f4e54307b3336f79185a8cc6749602ed524b71 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 6 Nov 2024 11:35:07 +0100 Subject: [PATCH 02/28] wrap EqlQueryBar into EqlQueryEdit component with reasonable defaults --- .../timeline/events/eql/index.ts | 4 +- .../public/common/hooks/eql/api.test.ts | 2 +- .../public/common/hooks/eql/api.ts | 4 +- .../components/description_step/helpers.tsx | 4 +- .../components/description_step/index.tsx | 4 +- .../eql_overview_link.tsx | 0 .../eql_query_bar.test.tsx | 4 +- .../eql_query_bar.tsx | 22 ++-- .../eql_query_edit/eql_query_edit.tsx | 108 ++++++++++++++++++ .../errors_popover.test.tsx | 0 .../errors_popover.tsx | 0 .../footer.test.tsx | 0 .../footer.tsx | 8 +- .../index.ts | 2 +- .../translations.ts | 7 ++ .../validators.mock.ts | 0 .../validators.ts | 0 .../components/step_define_rule/index.tsx | 38 +++--- .../components/step_define_rule/schema.tsx | 37 ++---- .../step_define_rule/translations.tsx | 7 -- .../rule_creation_ui/pages/form.test.ts | 67 ++++++----- .../rule_creation_ui/pages/form.tsx | 4 +- .../debounce_async.test.ts} | 2 +- .../validators/eql_query_validator_factory.ts | 6 +- .../validators/kuery_validator_factory.ts | 37 ++++++ ...ts => query_required_validator_factory.ts} | 20 +--- .../eql_query/eql_query_edit_adapter.tsx | 68 ++--------- .../fields/eql_query/translations.ts | 15 --- .../rules/use_rule_from_timeline.tsx | 4 +- .../pages/detection_engine/rules/types.ts | 4 +- .../timeline/query_bar/eql/index.tsx | 45 +++----- .../public/timelines/containers/index.tsx | 6 +- .../public/timelines/store/model.ts | 7 +- x-pack/plugins/timelines/common/index.ts | 4 +- .../timeline/events/eql/index.ts | 6 +- 35 files changed, 292 insertions(+), 254 deletions(-) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/eql_overview_link.tsx (100%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/eql_query_bar.test.tsx (97%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/eql_query_bar.tsx (93%) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/errors_popover.test.tsx (100%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/errors_popover.tsx (100%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/footer.test.tsx (100%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/footer.tsx (98%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/index.ts (84%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/translations.ts (94%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/validators.mock.ts (100%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{eql_query_bar => eql_query_edit}/validators.ts (100%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/{components/eql_query_bar/validators.test.ts => validators/debounce_async.test.ts} (96%) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/kuery_validator_factory.ts rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/{query_validator_factory.ts => query_required_validator_factory.ts} (74%) delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/translations.ts diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts index 10f993b468189..ce7805f89db1a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts @@ -7,7 +7,7 @@ export type { TimelineEqlResponse, - EqlOptionsData, - EqlOptionsSelected, + EqlFieldsComboBoxOptions, + EqlOptions, FieldsEqlOptions, } from '@kbn/timelines-plugin/common'; diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/api.test.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/api.test.ts index ee7c9a1515f9e..a0e47595c5da7 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/api.test.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/api.test.ts @@ -34,7 +34,7 @@ const triggerValidateEql = () => { query: 'any where true', signal, runtimeMappings: undefined, - options: undefined, + eqlOptions: undefined, }); }; diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts index f9421f161f040..6e730570faafd 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts @@ -11,7 +11,7 @@ import type { EqlSearchStrategyRequest, EqlSearchStrategyResponse } from '@kbn/d import { EQL_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { EqlOptionsSelected } from '../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../common/search_strategy'; import { getValidationErrors, isErrorResponse, @@ -32,7 +32,7 @@ interface Params { query: string; data: DataPublicPluginStart; runtimeMappings: estypes.MappingRuntimeFields | undefined; - eqlOptions: Omit | undefined; + eqlOptions: Omit | undefined; signal?: AbortSignal; } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/helpers.tsx index 6740e9fc8d014..57292d91953d8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/helpers.tsx @@ -32,7 +32,7 @@ import type { } from '../../../../../common/api/detection_engine/model/rule_schema'; import { AlertSuppressionMissingFieldsStrategyEnum } from '../../../../../common/api/detection_engine/model/rule_schema'; import { MATCHES, AND, OR } from '../../../../common/components/threat_match/translations'; -import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../../common/search_strategy'; import { assertUnreachable } from '../../../../../common/utility_types'; import * as i18nSeverity from '../severity_mapping/translations'; import * as i18nRiskScore from '../risk_score_mapping/translations'; @@ -147,7 +147,7 @@ export const buildQueryBarDescription = ({ return items; }; -export const buildEqlOptionsDescription = (eqlOptions: EqlOptionsSelected): ListItems[] => { +export const buildEqlOptionsDescription = (eqlOptions: EqlOptions): ListItems[] => { let items: ListItems[] = []; if (!isEmpty(eqlOptions.eventCategoryField)) { items = [ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx index 657f592fe47c4..24ad5f4135a14 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx @@ -19,7 +19,7 @@ import type { } from '../../../../../common/api/detection_engine/model/rule_schema'; import { buildRelatedIntegrationsDescription } from '../../../../detections/components/rules/related_integrations/integrations_description'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; -import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../../common/search_strategy'; import { useKibana } from '../../../../common/lib/kibana'; import type { AboutStepRiskScore, @@ -275,7 +275,7 @@ export const getDescriptionItem = ( return []; } } else if (field === 'eqlOptions') { - const eqlOptions: EqlOptionsSelected = get(field, data); + const eqlOptions: EqlOptions = get(field, data); return buildEqlOptionsDescription(eqlOptions); } else if (field === 'threat') { const threats: Threats = get(field, data); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_overview_link.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_overview_link.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_overview_link.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_overview_link.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.test.tsx index 67e0e516e22e3..bfa723ad8b3d9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.test.tsx @@ -130,9 +130,9 @@ describe('EqlQueryBar', () => { dataTestSubj="myQueryBar" field={mockField} isLoading={false} - optionsData={mockOptionsData} + eqlFieldsComboBoxOptions={mockOptionsData} indexPattern={mockIndexPattern} - onOptionsChange={onOptionsChangeMock} + onEqlOptionsChange={onOptionsChangeMock} /> ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx index 607b7a3ef2bb2..d99c639ec5b6f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx @@ -22,8 +22,8 @@ import * as i18n from './translations'; import { EqlQueryBarFooter } from './footer'; import { getValidationResults } from './validators'; import type { - EqlOptionsData, - EqlOptionsSelected, + EqlFieldsComboBoxOptions, + EqlOptions, FieldsEqlOptions, } from '../../../../../common/search_strategy'; import { useKibana } from '../../../../common/lib/kibana'; @@ -65,10 +65,10 @@ export interface EqlQueryBarProps { indexPattern: DataViewBase; showFilterBar?: boolean; idAria?: string; - optionsData?: EqlOptionsData; - optionsSelected?: EqlOptionsSelected; + eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions; + eqlOptions?: EqlOptions; isSizeOptionDisabled?: boolean; - onOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; + onEqlOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; onValidityChange?: (arg: boolean) => void; onValiditingChange?: (arg: boolean) => void; } @@ -76,14 +76,14 @@ export interface EqlQueryBarProps { export const EqlQueryBar: FC = ({ dataTestSubj, field, - isLoading = false, + isLoading, indexPattern, showFilterBar, idAria, - optionsData, - optionsSelected, + eqlFieldsComboBoxOptions, + eqlOptions: optionsSelected, isSizeOptionDisabled, - onOptionsChange, + onEqlOptionsChange, onValidityChange, onValiditingChange, }) => { @@ -195,9 +195,9 @@ export const EqlQueryBar: FC = ({ errors={errorMessages} isLoading={isValidating} isSizeOptionDisabled={isSizeOptionDisabled} - optionsData={optionsData} + optionsData={eqlFieldsComboBoxOptions} optionsSelected={optionsSelected} - onOptionsChange={onOptionsChange} + onOptionsChange={onEqlOptionsChange} /> {showFilterBar && ( <> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx new file mode 100644 index 0000000000000..644d69484d775 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx @@ -0,0 +1,108 @@ +/* + * 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 React, { useMemo } from 'react'; +import type { DataViewBase } from '@kbn/es-query'; +import type { FieldConfig } from '../../../../shared_imports'; +import { UseField } from '../../../../shared_imports'; +import type { + EqlFieldsComboBoxOptions, + EqlOptions, + FieldsEqlOptions, +} from '../../../../../common/search_strategy'; +import { queryRequiredValidatorFactory } from '../../validators/query_required_validator_factory'; +import { debounceAsync } from '../../validators/debounce_async'; +import { eqlQueryValidatorFactory } from '../../validators/eql_query_validator_factory'; +import { EqlQueryBar } from './eql_query_bar'; +import * as i18n from './translations'; +import type { FieldValueQueryBar } from '../query_bar'; + +interface EqlQueryEditProps { + path: string; + eqlFieldsComboBoxOptions: EqlFieldsComboBoxOptions; + eqlOptions: EqlOptions; + dataView: DataViewBase; + required?: boolean; + loading?: boolean; + disabled?: boolean; + onEqlOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; + onValidityChange?: (arg: boolean) => void; + onValiditingChange?: (arg: boolean) => void; +} + +export function EqlQueryEdit({ + path, + eqlFieldsComboBoxOptions, + eqlOptions, + dataView, + required, + loading, + disabled, + onEqlOptionsChange, + onValidityChange, + onValiditingChange, +}: EqlQueryEditProps): JSX.Element { + const componentProps = useMemo( + () => ({ + eqlFieldsComboBoxOptions, + eqlOptions, + onEqlOptionsChange, + isSizeOptionDisabled: true, + isDisabled: disabled, + isLoading: loading, + indexPattern: dataView, + showFilterBar: true, + idAria: 'ruleEqlQueryBar', + dataTestSubj: 'ruleEqlQueryBar', + onValidityChange, + onValiditingChange, + }), + [ + eqlFieldsComboBoxOptions, + eqlOptions, + onEqlOptionsChange, + onValidityChange, + onValiditingChange, + dataView, + loading, + disabled, + ] + ); + const fieldConfig: FieldConfig = useMemo( + () => ({ + label: i18n.EQL_QUERY_BAR_LABEL, + validations: [ + ...(required + ? [ + { + validator: queryRequiredValidatorFactory('eql'), + }, + ] + : []), + { + validator: debounceAsync( + eqlQueryValidatorFactory({ + dataViewId: dataView.id ?? '', + eqlOptions, + }), + 300 + ), + }, + ], + }), + [required, dataView.id, eqlOptions] + ); + + return ( + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/errors_popover.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/errors_popover.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/errors_popover.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/errors_popover.test.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/errors_popover.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/errors_popover.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/errors_popover.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/errors_popover.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.test.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.tsx index 45ab4a1c969c5..0c0b0984ce279 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.tsx @@ -24,8 +24,8 @@ import styled from 'styled-components'; import type { DebouncedFunc } from 'lodash'; import { debounce } from 'lodash'; import type { - EqlOptionsData, - EqlOptionsSelected, + EqlFieldsComboBoxOptions, + EqlOptions, FieldsEqlOptions, } from '../../../../../common/search_strategy'; import * as i18n from './translations'; @@ -36,8 +36,8 @@ export interface Props { errors: string[]; isLoading?: boolean; isSizeOptionDisabled?: boolean; - optionsData?: EqlOptionsData; - optionsSelected?: EqlOptionsSelected; + optionsData?: EqlFieldsComboBoxOptions; + optionsSelected?: EqlOptions; onOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/index.ts similarity index 84% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/index.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/index.ts index 0ad62099e2bce..6044ff5a12e00 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/index.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { EqlQueryBar } from './eql_query_bar'; +export * from './eql_query_edit'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/translations.ts similarity index 94% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/translations.ts index 092c9b4bc1a11..1a2d6cb7c09aa 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/translations.ts @@ -7,6 +7,13 @@ import { i18n } from '@kbn/i18n'; +export const EQL_QUERY_BAR_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.EqlQueryBarLabel', + { + defaultMessage: 'EQL query', + } +); + export const EQL_VALIDATION_REQUEST_ERROR = i18n.translate( 'xpack.securitySolution.detectionEngine.eqlValidation.requestError', { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/validators.mock.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.mock.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/validators.mock.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/validators.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/validators.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx index 7085371eea276..0eee4e7198265 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx @@ -33,7 +33,7 @@ import { useSetFieldValueWithCallback } from '../../../../common/utils/use_set_f import type { SetRuleQuery } from '../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; import { useRuleFromTimeline } from '../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; -import type { EqlOptionsSelected, FieldsEqlOptions } from '../../../../../common/search_strategy'; +import type { EqlOptions, FieldsEqlOptions } from '../../../../../common/search_strategy'; import { filterRuleFieldsForType, getStepDataDataSource } from '../../pages/rule_creation/helpers'; import type { DefineStepRule, @@ -74,7 +74,7 @@ import { isEqlSequenceQuery, isSuppressionRuleInGA, } from '../../../../../common/detection_engine/utils'; -import { EqlQueryBar } from '../eql_query_bar'; +import { EqlQueryEdit } from '../eql_query_edit'; import { DataViewSelectorField } from '../data_view_selector_field'; import { ThreatMatchInput } from '../threatmatch_input'; import { useFetchIndex } from '../../../../common/containers/source'; @@ -105,8 +105,8 @@ export interface StepDefineRuleProps extends RuleStepProps { threatIndicesConfig: string[]; defaultSavedQuery?: SavedQuery; form: FormHook; - optionsSelected: EqlOptionsSelected; - setOptionsSelected: React.Dispatch>; + optionsSelected: EqlOptions; + setOptionsSelected: React.Dispatch>; indexPattern: DataViewBase; isIndexPatternLoading: boolean; isQueryBarValid: boolean; @@ -794,27 +794,17 @@ const StepDefineRuleComponent: FC = ({ {isEqlRule(ruleType) ? ( <> - diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx index 530db8797a148..58cbd6cbe7091 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx @@ -51,8 +51,8 @@ import { import * as alertSuppressionEditI81n from '../../../rule_creation/components/alert_suppression_edit/components/translations'; import { dataViewIdValidatorFactory } from '../../validators/data_view_id_validator_factory'; import { indexPatternValidatorFactory } from '../../validators/index_pattern_validator_factory'; -import { queryValidatorFactory } from '../../validators/query_validator_factory'; -import { eqlQueryValidatorFactory } from '../../validators/eql_query_validator_factory'; +import { queryRequiredValidatorFactory } from '../../validators/query_required_validator_factory'; +import { kueryValidatorFactory } from '../../validators/kuery_validator_factory'; export const schema: FormSchema = { index: { @@ -132,33 +132,11 @@ export const schema: FormSchema = { return; } - return queryValidatorFactory(formData.ruleType)(...args); + return queryRequiredValidatorFactory(formData.ruleType)(...args); }, }, { - validator: (...args) => { - const [{ formData }] = args; - const { - dataSourceType, - dataViewId = '', - index, - ruleType, - eqlOptions, - } = formData as DefineStepRule; - - if (!isEqlRule(ruleType)) { - return; - } - - return debounceAsync( - eqlQueryValidatorFactory( - dataSourceType === DataSourceType.DataView - ? { dataViewId, eqlOptions } - : { indexPattern: index, eqlOptions } - ), - 300 - )(...args); - }, + validator: kueryValidatorFactory(), }, { validator: debounceAsync(esqlValidator, 300), @@ -498,14 +476,17 @@ export const schema: FormSchema = { validations: [ { validator: (...args) => { - const [{ value, path, formData }] = args; + const [{ formData }] = args; if (!isThreatMatchRule(formData.ruleType)) { return; } - return queryValidatorFactory(formData.ruleType)(...args); + return queryRequiredValidatorFactory(formData.ruleType)(...args); }, }, + { + validator: kueryValidatorFactory(), + }, ], }, newTermsFields: { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx index b2b219ad6a336..7b8063b23e306 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx @@ -38,13 +38,6 @@ export const QUERY_BAR_LABEL = i18n.translate( } ); -export const EQL_QUERY_BAR_LABEL = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.EqlQueryBarLabel', - { - defaultMessage: 'EQL query', - } -); - export const SAVED_QUERY_FORM_ROW_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.SavedQueryFormRowLabel', { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts index 84d10bae1c5d5..d2bbe9edb1160 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts @@ -17,7 +17,6 @@ import type { } from '../../../detections/pages/detection_engine/rules/types'; import { useRuleFormsErrors } from './form'; -import { transformEqlResponseErrorToValidationError } from '../components/eql_query_bar/validators'; import { ALERT_SUPPRESSION_FIELDS_FIELD_NAME } from '../../rule_creation/components/alert_suppression_edit'; const getFormWithErrorsMock = (fields: { @@ -33,13 +32,15 @@ describe('useRuleFormsErrors', () => { it('should return blocking error in case of syntax validation error', async () => { const { result } = renderHook(() => useRuleFormsErrors()); - const validationError = transformEqlResponseErrorToValidationError({ - code: EQL_ERROR_CODES.INVALID_SYNTAX, - messages: ["line 1:5: missing 'where' at 'demo'"], - }); const defineStepForm = getFormWithErrorsMock({ queryBar: { - errors: [validationError], + errors: [ + { + code: EQL_ERROR_CODES.INVALID_SYNTAX, + message: '', + messages: ["line 1:5: missing 'where' at 'demo'"], + }, + ], }, }); @@ -53,13 +54,17 @@ describe('useRuleFormsErrors', () => { it('should return non-blocking error in case of missing data source validation error', async () => { const { result } = renderHook(() => useRuleFormsErrors()); - const validationError = transformEqlResponseErrorToValidationError({ - code: EQL_ERROR_CODES.MISSING_DATA_SOURCE, - messages: ['index_not_found_exception Found 1 problem line -1:-1: Unknown index [*,-*]'], - }); const defineStepForm = getFormWithErrorsMock({ queryBar: { - errors: [validationError], + errors: [ + { + code: EQL_ERROR_CODES.MISSING_DATA_SOURCE, + message: '', + messages: [ + 'index_not_found_exception Found 1 problem line -1:-1: Unknown index [*,-*]', + ], + }, + ], }, }); @@ -75,15 +80,17 @@ describe('useRuleFormsErrors', () => { it('should return non-blocking error in case of missing data field validation error', async () => { const { result } = renderHook(() => useRuleFormsErrors()); - const validationError = transformEqlResponseErrorToValidationError({ - code: EQL_ERROR_CODES.INVALID_EQL, - messages: [ - 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]', - ], - }); const defineStepForm = getFormWithErrorsMock({ queryBar: { - errors: [validationError], + errors: [ + { + code: EQL_ERROR_CODES.INVALID_EQL, + message: '', + messages: [ + 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]', + ], + }, + ], }, }); @@ -99,13 +106,15 @@ describe('useRuleFormsErrors', () => { it('should return non-blocking error in case of failed request error', async () => { const { result } = renderHook(() => useRuleFormsErrors()); - const validationError = transformEqlResponseErrorToValidationError({ - code: EQL_ERROR_CODES.FAILED_REQUEST, - error: new Error('Some internal error'), - }); const defineStepForm = getFormWithErrorsMock({ queryBar: { - errors: [validationError], + errors: [ + { + code: EQL_ERROR_CODES.FAILED_REQUEST, + message: 'An error occurred while validating your EQL query', + error: new Error('Some internal error'), + }, + ], }, }); @@ -121,13 +130,15 @@ describe('useRuleFormsErrors', () => { it('should return blocking and non-blocking errors', async () => { const { result } = renderHook(() => useRuleFormsErrors()); - const validationError = transformEqlResponseErrorToValidationError({ - code: EQL_ERROR_CODES.MISSING_DATA_SOURCE, - messages: ['Missing data source'], - }); const defineStepForm = getFormWithErrorsMock({ queryBar: { - errors: [validationError], + errors: [ + { + code: EQL_ERROR_CODES.MISSING_DATA_SOURCE, + message: '', + messages: ['Missing data source'], + }, + ], }, }); const aboutStepForm = getFormWithErrorsMock({ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx index 9e232e4bff2be..02bc813589d39 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx @@ -19,7 +19,7 @@ import { useKibana } from '../../../common/lib/kibana'; import type { FormHook, ValidationError } from '../../../shared_imports'; import { useForm, useFormData } from '../../../shared_imports'; import { schema as defineRuleSchema } from '../components/step_define_rule/schema'; -import type { EqlOptionsSelected } from '../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../common/search_strategy'; import { schema as aboutRuleSchema, threatMatchAboutSchema, @@ -53,7 +53,7 @@ export const useRuleForms = ({ options: { stripEmptyFields: false }, schema: defineRuleSchema, }); - const [eqlOptionsSelected, setEqlOptionsSelected] = useState( + const [eqlOptionsSelected, setEqlOptionsSelected] = useState( defineStepDefault.eqlOptions ); const [defineStepFormData] = useFormData({ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.test.ts similarity index 96% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.test.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.test.ts index 6c5cadf41a7a6..aa75c6fddc23e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { debounceAsync } from './validators'; +import { debounceAsync } from './debounce_async'; jest.useFakeTimers({ legacyFakeTimers: true }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts index 9a05dea4e11e8..b08461dca3ef0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts @@ -9,7 +9,7 @@ import { isEmpty } from 'lodash'; import { i18n } from '@kbn/i18n'; import type { FormData, ValidationError, ValidationFunc } from '../../../shared_imports'; import { KibanaServices } from '../../../common/lib/kibana'; -import type { EqlOptionsSelected } from '../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../common/search_strategy'; import type { FieldValueQueryBar } from '../components/query_bar'; import type { EqlResponseError } from '../../../common/hooks/eql/api'; import { EQL_ERROR_CODES, validateEql } from '../../../common/hooks/eql/api'; @@ -19,12 +19,12 @@ type EqlQueryValidatorFactoryParams = | { indexPattern: string[]; dataViewId?: never; - eqlOptions: EqlOptionsSelected; + eqlOptions: EqlOptions; } | { indexPattern?: never; dataViewId: string; - eqlOptions: EqlOptionsSelected; + eqlOptions: EqlOptions; }; export function eqlQueryValidatorFactory({ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/kuery_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/kuery_validator_factory.ts new file mode 100644 index 0000000000000..f362acffd3bcf --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/kuery_validator_factory.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; +import { fromKueryExpression } from '@kbn/es-query'; +import type { FormData, ValidationFunc } from '../../../shared_imports'; +import type { FieldValueQueryBar } from '../components/query_bar'; + +export function kueryValidatorFactory(): ValidationFunc { + return (...args) => { + const [{ path, value }] = args; + + if (isEmpty(value.query.query) || value.query.language !== 'kuery') { + return; + } + + try { + fromKueryExpression(value.query.query); + } catch (err) { + return { + code: 'ERR_FIELD_FORMAT', + path, + message: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError', + { + defaultMessage: 'The KQL is invalid', + } + ), + }; + } + }; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_required_validator_factory.ts similarity index 74% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_validator_factory.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_required_validator_factory.ts index 6f527a5a81454..f06aaa6b312f8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_validator_factory.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_required_validator_factory.ts @@ -7,13 +7,12 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; -import { fromKueryExpression } from '@kbn/es-query'; import type { RuleType } from '@kbn/securitysolution-rules'; import type { FormData, ValidationFunc } from '../../../shared_imports'; import { isEqlRule, isEsqlRule } from '../../../../common/detection_engine/utils'; import type { FieldValueQueryBar } from '../components/query_bar'; -export function queryValidatorFactory( +export function queryRequiredValidatorFactory( ruleType: RuleType ): ValidationFunc { return (...args) => { @@ -26,23 +25,6 @@ export function queryValidatorFactory( message: getErrorMessage(ruleType), }; } - - if (!isEmpty(value.query.query) && value.query.language === 'kuery') { - try { - fromKueryExpression(value.query.query); - } catch (err) { - return { - code: 'ERR_FIELD_FORMAT', - path, - message: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError', - { - defaultMessage: 'The KQL is invalid', - } - ), - }; - } - } }; } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx index edc8e79b24445..d2e564e956aea 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx @@ -7,20 +7,15 @@ import React, { useMemo } from 'react'; import type { DataViewBase } from '@kbn/es-query'; -import { EqlQueryBar } from '../../../../../../../rule_creation_ui/components/eql_query_bar'; -import { UseField } from '../../../../../../../../shared_imports'; +import { EqlQueryEdit } from '../../../../../../../rule_creation_ui/components/eql_query_edit'; import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; import { useDiffableRuleDataView } from '../hooks/use_diffable_rule_data_view'; -import { queryValidatorFactory } from '../../../../../../../rule_creation_ui/validators/query_validator_factory'; -import { debounceAsync } from '../../../../../../../rule_creation_ui/validators/debounce_async'; -import { eqlQueryValidatorFactory } from '../../../../../../../rule_creation_ui/validators/eql_query_validator_factory'; -import * as i18n from './translations'; export function EqlQueryEditAdapter({ finalDiffableRule, }: RuleFieldEditComponentProps): JSX.Element { const { dataView, isLoading } = useDiffableRuleDataView(finalDiffableRule); - const optionsData = useMemo( + const eqlFieldsComboBoxOptions = useMemo( () => !dataView ? { @@ -42,62 +37,21 @@ export function EqlQueryEditAdapter({ [dataView] ); - const componentProps = useMemo( - () => ({ - optionsData, - optionsSelected: {}, - isSizeOptionDisabled: true, - isDisabled: isLoading, - isLoading, - indexPattern: dataView ?? DEFAULT_DATA_VIEW_BASE, - showFilterBar: true, - idAria: 'ruleEqlQueryBar', - dataTestSubj: 'ruleEqlQueryBar', - }), - [optionsData, dataView, isLoading] - ); - const fieldConfig = useMemo(() => { - if (finalDiffableRule.type !== 'eql') { - return { - label: i18n.EQL_QUERY_BAR_LABEL, - }; - } - - const eqlOptions = { - eventCategoryField: finalDiffableRule.event_category_override, - tiebreakerField: finalDiffableRule.tiebreaker_field, - timestampField: finalDiffableRule.timestamp_field, - }; - - return { - label: i18n.EQL_QUERY_BAR_LABEL, - validations: [ - { - validator: queryValidatorFactory('eql'), - }, - { - validator: (...args) => { - return debounceAsync( - eqlQueryValidatorFactory({ dataViewId: dataView?.id ?? '', eqlOptions }), - 300 - )(...args); - }, - }, - ], - }; - }, [dataView, finalDiffableRule]); - return ( - ); } +const EQL_OPTIONS = {}; + const DEFAULT_DATA_VIEW_BASE: DataViewBase = { title: '', fields: [], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/translations.ts deleted file mode 100644 index aba4c787d7bc3..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/translations.ts +++ /dev/null @@ -1,15 +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 { i18n } from '@kbn/i18n'; - -export const EQL_QUERY_BAR_LABEL = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.EqlQueryBarLabel', - { - defaultMessage: 'EQL query', - } -); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx index 27c2699cdf195..b72c043ffa408 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx @@ -9,7 +9,7 @@ import { isEmpty } from 'lodash/fp'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { i18n } from '@kbn/i18n'; -import type { EqlOptionsSelected } from '@kbn/timelines-plugin/common'; +import type { EqlOptions } from '@kbn/timelines-plugin/common'; import { convertKueryToElasticSearchQuery } from '../../../../common/lib/kuery'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useSourcererDataView } from '../../../../sourcerer/containers'; @@ -37,7 +37,7 @@ export type SetRuleQuery = ({ }: { index: string[]; queryBar: FieldValueQueryBar; - eqlOptions?: EqlOptionsSelected; + eqlOptions?: EqlOptions; }) => void; export const useRuleFromTimeline = (setRuleQuery: SetRuleQuery): RuleFromTimeline => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index cf2c3264f3150..2f0cf92737f4a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -50,7 +50,7 @@ import type { RequiredFieldInput, } from '../../../../../common/api/detection_engine/model/rule_schema'; import type { SortOrder } from '../../../../../common/api/detection_engine'; -import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../../common/search_strategy'; import type { RuleResponseAction, ResponseAction, @@ -164,7 +164,7 @@ export interface DefineStepRule { threatIndex: ThreatIndex; threatQueryBar: FieldValueQueryBar; threatMapping: ThreatMapping; - eqlOptions: EqlOptionsSelected; + eqlOptions: EqlOptions; dataSourceType: DataSourceType; newTermsFields: string[]; historyWindowSize: string; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index 599f03d7657ca..2e9ca918833ec 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -9,15 +9,13 @@ import { isEmpty, isEqual } from 'lodash'; import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; +import type { DataViewBase } from '@kbn/es-query'; -import type { - EqlOptionsSelected, - FieldsEqlOptions, -} from '../../../../../../common/search_strategy'; +import type { EqlOptions, FieldsEqlOptions } from '../../../../../../common/search_strategy'; import { useSourcererDataView } from '../../../../../sourcerer/containers'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import { SourcererScopeName } from '../../../../../sourcerer/store/model'; -import { EqlQueryBar } from '../../../../../detection_engine/rule_creation_ui/components/eql_query_bar'; +import { EqlQueryEdit } from '../../../../../detection_engine/rule_creation_ui/components/eql_query_edit'; import { debounceAsync } from '../../../../../detection_engine/rule_creation_ui/validators/debounce_async'; import { eqlQueryValidatorFactory } from '../../../../../detection_engine/rule_creation_ui/validators/eql_query_validator_factory'; import type { FieldValueQueryBar } from '../../../../../detection_engine/rule_creation_ui/components/query_bar'; @@ -25,13 +23,12 @@ import type { FieldValueQueryBar } from '../../../../../detection_engine/rule_cr import type { FormSchema } from '../../../../../shared_imports'; import { Form, UseField, useForm, useFormData } from '../../../../../shared_imports'; import { timelineActions } from '../../../../store'; -import * as i18n from '../translations'; import { getEqlOptions } from './selectors'; interface TimelineEqlQueryBar { index: string[]; eqlQueryBar: FieldValueQueryBar; - eqlOptions: EqlOptionsSelected; + eqlOptions: EqlOptions; } const defaultValues = { @@ -128,7 +125,7 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) const prevEqlQuery = useRef(''); - const optionsData = useMemo(() => { + const eqlFieldsComboBoxOptions = useMemo(() => { const fields = Object.values(sourcererDataView.fields || {}); return isEmpty(fields) @@ -190,30 +187,26 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) } }, [dispatch, formEqlQueryBar, isQueryBarValid, isQueryBarValidating, timelineId]); + /* Force casting `sourcererDataView` to `DataViewBase` is required since EqlQueryEdit + accepts DataViewBase but `useSourcererDataView()` returns `DataViewSpec`. + + When using `UseField` with `EqlQueryBar` such casting isn't required by TS since + `UseField` component props are types as `Record`. */ return (
- ); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 6c40ca1b7dfd1..0301f0123c30f 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -41,7 +41,7 @@ import { TimelineId } from '../../../common/types/timeline'; import { useRouteSpy } from '../../common/utils/route/use_route_spy'; import { activeTimeline } from './active_timeline_context'; import type { - EqlOptionsSelected, + EqlOptions, TimelineEqlResponse, } from '../../../common/search_strategy/timeline/events/eql'; import { useTrackHttpRequest } from '../../common/lib/apm/use_track_http_request'; @@ -84,7 +84,7 @@ type TimelineResponse = T extends 'kuery' export interface UseTimelineEventsProps { dataViewId: string | null; endDate?: string; - eqlOptions?: EqlOptionsSelected; + eqlOptions?: EqlOptions; fields: string[]; filterQuery?: ESQuery | string; id: string; @@ -112,7 +112,7 @@ export const initSortDefault: TimelineRequestSortField[] = [ }, ]; -const deStructureEqlOptions = (eqlOptions?: EqlOptionsSelected) => ({ +const deStructureEqlOptions = (eqlOptions?: EqlOptions) => ({ ...(!isEmpty(eqlOptions?.eventCategoryField) ? { eventCategoryField: eqlOptions?.eventCategoryField, diff --git a/x-pack/plugins/security_solution/public/timelines/store/model.ts b/x-pack/plugins/security_solution/public/timelines/store/model.ts index 92c435f93cb43..f93d6b3f6b649 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/model.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/model.ts @@ -8,10 +8,7 @@ import type { Filter } from '@kbn/es-query'; import type { SavedSearch } from '@kbn/saved-search-plugin/common'; import type { SessionViewConfig } from '../../../common/types'; -import type { - EqlOptionsSelected, - TimelineNonEcsData, -} from '../../../common/search_strategy/timeline'; +import type { EqlOptions, TimelineNonEcsData } from '../../../common/search_strategy/timeline'; import type { TimelineTabs, ScrollToTopEvent, @@ -42,7 +39,7 @@ export interface TimelineModel { createdBy?: string; /** A summary of the events and notes in this timeline */ description: string; - eqlOptions: EqlOptionsSelected; + eqlOptions: EqlOptions; /** Type of event you want to see in this timeline */ eventType?: TimelineEventsType; /** A map of events in this timeline to the chronologically ordered notes (in this timeline) associated with the event */ diff --git a/x-pack/plugins/timelines/common/index.ts b/x-pack/plugins/timelines/common/index.ts index 9b289f481a2d5..0a96a22fb2679 100644 --- a/x-pack/plugins/timelines/common/index.ts +++ b/x-pack/plugins/timelines/common/index.ts @@ -42,8 +42,8 @@ export type { BeatFields, BrowserFields, CursorType, - EqlOptionsData, - EqlOptionsSelected, + EqlFieldsComboBoxOptions, + EqlOptions, FieldsEqlOptions, FieldInfo, IndexField, diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts index 66758bbcb94d7..8636a55941042 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts @@ -22,13 +22,13 @@ export interface TimelineEqlResponse extends EqlSearchStrategyResponse; } -export interface EqlOptionsData { +export interface EqlFieldsComboBoxOptions { keywordFields: EuiComboBoxOptionOption[]; dateFields: EuiComboBoxOptionOption[]; nonDateFields: EuiComboBoxOptionOption[]; } -export interface EqlOptionsSelected { +export interface EqlOptions { eventCategoryField?: string; tiebreakerField?: string; timestampField?: string; @@ -36,4 +36,4 @@ export interface EqlOptionsSelected { size?: number; } -export type FieldsEqlOptions = keyof EqlOptionsSelected; +export type FieldsEqlOptions = keyof EqlOptions; From ad8acbdd7a63ab9fa084be679bf51f9e528f90ea Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 6 Nov 2024 13:16:11 +0100 Subject: [PATCH 03/28] add eql options fields --- .../eql_query_edit/eql_query_edit.tsx | 6 +- .../final_edit/eql_rule_field_edit.tsx | 9 +++ .../eql_query/eql_query_edit_adapter.tsx | 24 +------- .../event_category_override_edit_adapter.tsx | 42 +++++++++++++ .../event_category_override_edit_form.tsx | 61 +++++++++++++++++++ .../fields/event_category_override/index.ts | 8 +++ .../event_category_override/translations.ts | 23 +++++++ ...fable_rule_eql_fields_combo_box_options.ts | 39 ++++++++++++ .../fields/tiebreaker_field/index.ts | 8 +++ .../tiebreaker_field_edit_adapter.tsx | 42 +++++++++++++ .../tiebreaker_field_edit_form.tsx | 59 ++++++++++++++++++ .../fields/tiebreaker_field/translations.ts | 23 +++++++ .../fields/timestamp_field/index.ts | 8 +++ .../timestamp_field_edit_adapter.tsx | 38 ++++++++++++ .../timestamp_field_edit_form.tsx | 59 ++++++++++++++++++ .../fields/timestamp_field/translations.ts | 22 +++++++ 16 files changed, 445 insertions(+), 26 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/event_category_override_edit_adapter.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/event_category_override_edit_form.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/translations.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/hooks/use_diffable_rule_eql_fields_combo_box_options.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/index.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_adapter.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_form.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/translations.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/index.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_adapter.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_form.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/translations.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx index 644d69484d775..bcaefbd60d7ad 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx @@ -23,8 +23,8 @@ import type { FieldValueQueryBar } from '../query_bar'; interface EqlQueryEditProps { path: string; - eqlFieldsComboBoxOptions: EqlFieldsComboBoxOptions; - eqlOptions: EqlOptions; + eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions; + eqlOptions?: EqlOptions; dataView: DataViewBase; required?: boolean; loading?: boolean; @@ -87,7 +87,7 @@ export function EqlQueryEdit({ validator: debounceAsync( eqlQueryValidatorFactory({ dataViewId: dataView.id ?? '', - eqlOptions, + eqlOptions: eqlOptions ?? {}, }), 300 ), diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx index 2893a61a1f471..0dad1bf131f8b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx @@ -10,6 +10,9 @@ import type { UpgradeableEqlFields } from '../../../../model/prebuilt_rule_upgra import { DataSourceEditForm } from './fields/data_source'; import { AlertSuppressionEditForm } from './fields/alert_suppression'; import { EqlQueryEditForm } from './fields/eql_query'; +import { EventCategoryOverrideEditForm } from './fields/event_category_override'; +import { TimestampFieldEditForm } from './fields/timestamp_field'; +import { TiebreakerFieldEditForm } from './fields/tiebreaker_field'; interface EqlRuleFieldEditProps { fieldName: UpgradeableEqlFields; @@ -23,6 +26,12 @@ export function EqlRuleFieldEdit({ fieldName }: EqlRuleFieldEditProps) { return ; case 'alert_suppression': return ; + case 'event_category_override': + return ; + case 'timestamp_field': + return ; + case 'tiebreaker_field': + return ; default: return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx index d2e564e956aea..fe3e40c71af77 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import type { DataViewBase } from '@kbn/es-query'; import { EqlQueryEdit } from '../../../../../../../rule_creation_ui/components/eql_query_edit'; import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; @@ -15,33 +15,11 @@ export function EqlQueryEditAdapter({ finalDiffableRule, }: RuleFieldEditComponentProps): JSX.Element { const { dataView, isLoading } = useDiffableRuleDataView(finalDiffableRule); - const eqlFieldsComboBoxOptions = useMemo( - () => - !dataView - ? { - keywordFields: [], - dateFields: [], - nonDateFields: [], - } - : { - keywordFields: dataView.fields - .filter((f) => f.esTypes?.includes('keyword')) - .map((f) => ({ label: f.name })), - dateFields: dataView.fields - .filter((f) => f.type === 'date') - .map((f) => ({ label: f.name })), - nonDateFields: dataView.fields - .filter((f) => f.type !== 'date') - .map((f) => ({ label: f.name })), - }, - [dataView] - ); return ( ({ + label: i18n.EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL, + helpText: i18n.EQL_OPTIONS_EVENT_CATEGORY_FIELD_HELPER, + euiFieldProps: { + fullWidth: true, + singleSelection: { asPlainText: true }, + noSuggestions: false, + placeholder: '', + onCreateOption: undefined, + options: keywordFields, + }, + }), + [keywordFields] + ); + + return ( + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/event_category_override_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/event_category_override_edit_form.tsx new file mode 100644 index 0000000000000..89ad22e62bbbc --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/event_category_override_edit_form.tsx @@ -0,0 +1,61 @@ +/* + * 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 React from 'react'; +import type { + DiffableRule, + EventCategoryOverride, +} from '../../../../../../../../../common/api/detection_engine'; +import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; +import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; +import { EventCategoryOverrideEditAdapter } from './event_category_override_edit_adapter'; + +export function EventCategoryOverrideEditForm(): JSX.Element { + return ( + + ); +} + +const schema = { + event_category_override: {}, +} as FormSchema<{ + event_category_override: EventCategoryOverride; +}>; + +function deserializer( + _: FormData, + finalDiffableRule: DiffableRule +): { + eventCategoryOverride: string[]; +} { + if (finalDiffableRule.type !== 'eql') { + return { + eventCategoryOverride: [], + }; + } + + return { + eventCategoryOverride: finalDiffableRule.event_category_override + ? [finalDiffableRule.event_category_override] + : [], + }; +} + +function serializer(formData: FormData): { + event_category_override?: EventCategoryOverride; +} { + const { eventCategoryOverride } = formData as { eventCategoryOverride: string[] }; + + return { + event_category_override: eventCategoryOverride.length ? eventCategoryOverride[0] : undefined, + }; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts new file mode 100644 index 0000000000000..0c7abbd45c79b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './event_category_override_edit_form'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/translations.ts new file mode 100644 index 0000000000000..2f617604e119e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/translations.ts @@ -0,0 +1,23 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL = i18n.translate( + 'xpack.securitySolution.ruleManagement.eqlOptionsEventCategoryField.label', + { + defaultMessage: 'Event category field', + } +); + +export const EQL_OPTIONS_EVENT_CATEGORY_FIELD_HELPER = i18n.translate( + 'xpack.securitySolution.ruleManagement.eqlOptionsEventCategoryField.text', + { + defaultMessage: + 'Field containing the event classification, such as process, file, or network. This field is typically mapped as a field type in the keyword family', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/hooks/use_diffable_rule_eql_fields_combo_box_options.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/hooks/use_diffable_rule_eql_fields_combo_box_options.ts new file mode 100644 index 0000000000000..f6665d51706e3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/hooks/use_diffable_rule_eql_fields_combo_box_options.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMemo } from 'react'; +import type { EqlFieldsComboBoxOptions } from '@kbn/timelines-plugin/common'; +import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; +import { useDiffableRuleDataView } from './use_diffable_rule_data_view'; + +export function useDiffableRuleEqlFieldsComboBoxOptions( + diffableRule: DiffableRule +): EqlFieldsComboBoxOptions { + const { dataView } = useDiffableRuleDataView(diffableRule); + + return useMemo( + () => + !dataView + ? { + keywordFields: [], + dateFields: [], + nonDateFields: [], + } + : { + keywordFields: dataView.fields + .filter((f) => f.esTypes?.includes('keyword')) + .map((f) => ({ label: f.name })), + dateFields: dataView.fields + .filter((f) => f.type === 'date') + .map((f) => ({ label: f.name })), + nonDateFields: dataView.fields + .filter((f) => f.type !== 'date') + .map((f) => ({ label: f.name })), + }, + [dataView] + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/index.ts new file mode 100644 index 0000000000000..7edddde1e52af --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './tiebreaker_field_edit_form'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_adapter.tsx new file mode 100644 index 0000000000000..f983108be1a44 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_adapter.tsx @@ -0,0 +1,42 @@ +/* + * 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 React, { useMemo } from 'react'; +import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { UseField } from '../../../../../../../../shared_imports'; +import { useDiffableRuleEqlFieldsComboBoxOptions } from '../hooks/use_diffable_rule_eql_fields_combo_box_options'; +import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; +import * as i18n from './translations'; + +export function TiebreakerFieldEditAdapter({ + finalDiffableRule, +}: RuleFieldEditComponentProps): JSX.Element { + const { nonDateFields } = useDiffableRuleEqlFieldsComboBoxOptions(finalDiffableRule); + const comboBoxFieldProps = useMemo( + () => ({ + label: i18n.EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL, + helpText: i18n.EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_HELPER, + euiFieldProps: { + fullWidth: true, + singleSelection: { asPlainText: true }, + noSuggestions: false, + placeholder: '', + onCreateOption: undefined, + options: nonDateFields, + }, + }), + [nonDateFields] + ); + + return ( + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_form.tsx new file mode 100644 index 0000000000000..ef5463fa026e9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_form.tsx @@ -0,0 +1,59 @@ +/* + * 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 React from 'react'; +import type { + DiffableRule, + TiebreakerField, +} from '../../../../../../../../../common/api/detection_engine'; +import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; +import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; +import { TiebreakerFieldEditAdapter } from './tiebreaker_field_edit_adapter'; + +export function TiebreakerFieldEditForm(): JSX.Element { + return ( + + ); +} + +const schema = { + tiebreaker_field: {}, +} as FormSchema<{ + tiebreaker_field: TiebreakerField; +}>; + +function deserializer( + _: FormData, + finalDiffableRule: DiffableRule +): { + tiebreakerField: string[]; +} { + if (finalDiffableRule.type !== 'eql') { + return { + tiebreakerField: [], + }; + } + + return { + tiebreakerField: finalDiffableRule.tiebreaker_field ? [finalDiffableRule.tiebreaker_field] : [], + }; +} + +function serializer(formData: FormData): { + tiebreaker_field?: TiebreakerField; +} { + const { tiebreakerField } = formData as { tiebreakerField: string[] }; + + return { + tiebreaker_field: tiebreakerField.length ? tiebreakerField[0] : undefined, + }; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/translations.ts new file mode 100644 index 0000000000000..be67222601bd6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/translations.ts @@ -0,0 +1,23 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL = i18n.translate( + 'xpack.securitySolution.ruleManagement.eqlOptionsEventTiebreakerField.label', + { + defaultMessage: 'Tiebreaker field', + } +); + +export const EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_HELPER = i18n.translate( + 'xpack.securitySolution.ruleManagement.eqlOptionsEventTiebreakerField.text', + { + defaultMessage: + 'Field used to sort hits with the same timestamp in ascending, lexicographic order', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/index.ts new file mode 100644 index 0000000000000..1f2edea910118 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './timestamp_field_edit_form'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_adapter.tsx new file mode 100644 index 0000000000000..8b2264a184515 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_adapter.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { UseField } from '../../../../../../../../shared_imports'; +import { useDiffableRuleEqlFieldsComboBoxOptions } from '../hooks/use_diffable_rule_eql_fields_combo_box_options'; +import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; +import * as i18n from './translations'; + +export function TimestampFieldEditAdapter({ + finalDiffableRule, +}: RuleFieldEditComponentProps): JSX.Element { + const { dateFields } = useDiffableRuleEqlFieldsComboBoxOptions(finalDiffableRule); + const comboBoxFieldProps = useMemo( + () => ({ + label: i18n.EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_LABEL, + helpText: i18n.EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_HELPER, + euiFieldProps: { + fullWidth: true, + singleSelection: { asPlainText: true }, + noSuggestions: false, + placeholder: '', + onCreateOption: undefined, + options: dateFields, + }, + }), + [dateFields] + ); + + return ( + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_form.tsx new file mode 100644 index 0000000000000..edf1d021e5492 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_form.tsx @@ -0,0 +1,59 @@ +/* + * 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 React from 'react'; +import type { + DiffableRule, + TimestampField, +} from '../../../../../../../../../common/api/detection_engine'; +import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; +import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; +import { TimestampFieldEditAdapter } from './timestamp_field_edit_adapter'; + +export function TimestampFieldEditForm(): JSX.Element { + return ( + + ); +} + +const schema = { + timestamp_field: {}, +} as FormSchema<{ + timestamp_field: TimestampField; +}>; + +function deserializer( + _: FormData, + finalDiffableRule: DiffableRule +): { + timestampField: string[]; +} { + if (finalDiffableRule.type !== 'eql') { + return { + timestampField: [], + }; + } + + return { + timestampField: finalDiffableRule.timestamp_field ? [finalDiffableRule.timestamp_field] : [], + }; +} + +function serializer(formData: FormData): { + timestamp_field?: TimestampField; +} { + const { timestampField } = formData as { timestampField: string[] }; + + return { + timestamp_field: timestampField.length ? timestampField[0] : undefined, + }; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/translations.ts new file mode 100644 index 0000000000000..426874a8f291d --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_LABEL = i18n.translate( + 'xpack.securitySolution.ruleManagement.eqlOptionsEventTimestampField.label', + { + defaultMessage: 'Timestamp field', + } +); + +export const EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_HELPER = i18n.translate( + 'xpack.securitySolution.ruleManagement.eqlOptionsEventTimestampField.text', + { + defaultMessage: 'Field containing event timestamp', + } +); From 960e68b68b7c44a309a1dcadf53501179baca4a0 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 6 Nov 2024 13:43:23 +0100 Subject: [PATCH 04/28] remove unused translation keys --- x-pack/plugins/translations/translations/fr-FR.json | 1 - x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 3 files changed, 3 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 018646b09b6b6..b13d75079a8f9 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -38077,7 +38077,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoAriaLabel": "Ouvrir une fenêtre contextuelle d'aide", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipContent": "Consultez {createEsqlRuleTypeLink} pour commencer à utiliser les règles ES|QL.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipLink": "documentation", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryFieldRequiredError": "Une requête ES|QL est requise.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryLabel": "Requête ES|QL", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldAnomalyThresholdLabel": "Seuil de score d'anomalie", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldMachineLearningJobIdLabel": "Tâche de Machine Learning", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2f76f1a88b515..001647ceb5839 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -38044,7 +38044,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoAriaLabel": "ヘルプポップオーバーを開く", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipContent": "ES|QL ルールの使用を開始するには、{createEsqlRuleTypeLink}を確認してください。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipLink": "ドキュメンテーション", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryFieldRequiredError": "ES|QLクエリは必須です。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryLabel": "ES|QLクエリ", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldAnomalyThresholdLabel": "異常スコアしきい値", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldMachineLearningJobIdLabel": "機械学習ジョブ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 9dcbf8eacac51..524680e22e2c9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -37439,7 +37439,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoAriaLabel": "打开帮助弹出框", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipContent": "请访问我们的{createEsqlRuleTypeLink}以开始使用 ES|QL 规则。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipLink": "文档", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryFieldRequiredError": "ES|QL 查询必填。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryLabel": "ES|QL 查询", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldAnomalyThresholdLabel": "异常分数阈值", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldMachineLearningJobIdLabel": "Machine Learning 作业", From f20742aa81c0641a127dddd39906ceaede2cb85c Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 6 Nov 2024 15:24:47 +0100 Subject: [PATCH 05/28] properly run first EQL Query validation in Three Way Diff --- .../eql_query_edit/eql_query_edit.tsx | 17 ++++++++++++----- .../validators/eql_query_validator_factory.ts | 9 +++++---- .../fields/eql_query/eql_query_edit_adapter.tsx | 7 ++++++- .../components/timeline/query_bar/eql/index.tsx | 2 +- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx index bcaefbd60d7ad..a465ca1603df3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx @@ -85,16 +85,23 @@ export function EqlQueryEdit({ : []), { validator: debounceAsync( - eqlQueryValidatorFactory({ - dataViewId: dataView.id ?? '', - eqlOptions: eqlOptions ?? {}, - }), + eqlQueryValidatorFactory( + dataView.id + ? { + dataViewId: dataView.id, + eqlOptions: eqlOptions ?? {}, + } + : { + indexPatterns: dataView.title.split(','), + eqlOptions: eqlOptions ?? {}, + } + ), 300 ), }, ], }), - [required, dataView.id, eqlOptions] + [required, dataView.id, dataView.title, eqlOptions] ); return ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts index b08461dca3ef0..9897517fa8981 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts @@ -17,18 +17,18 @@ import { isDataViewIdValid } from './data_view_id_validator_factory'; type EqlQueryValidatorFactoryParams = | { - indexPattern: string[]; + indexPatterns: string[]; dataViewId?: never; eqlOptions: EqlOptions; } | { - indexPattern?: never; + indexPatterns?: never; dataViewId: string; eqlOptions: EqlOptions; }; export function eqlQueryValidatorFactory({ - indexPattern, + indexPatterns, dataViewId, eqlOptions, }: EqlQueryValidatorFactoryParams): ValidationFunc { @@ -44,7 +44,8 @@ export function eqlQueryValidatorFactory({ const dataView = isDataViewIdValid(dataViewId) ? await data.dataViews.get(dataViewId) : undefined; - const dataViewTitle = dataView?.getIndexPattern() ?? indexPattern?.join() ?? ''; + + const dataViewTitle = dataView?.getIndexPattern() ?? indexPatterns?.join() ?? ''; const runtimeMappings = dataView?.getRuntimeMappings() ?? {}; const response = await validateEql({ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx index fe3e40c71af77..b36e3d9f8abe0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx @@ -13,9 +13,14 @@ import { useDiffableRuleDataView } from '../hooks/use_diffable_rule_data_view'; export function EqlQueryEditAdapter({ finalDiffableRule, -}: RuleFieldEditComponentProps): JSX.Element { +}: RuleFieldEditComponentProps): JSX.Element | null { const { dataView, isLoading } = useDiffableRuleDataView(finalDiffableRule); + // Wait for dataView to be defined to trigger validation with the correct index patterns + if (!dataView) { + return null; + } + return ( = { const { index, eqlOptions } = formData; return debounceAsync( - eqlQueryValidatorFactory({ indexPattern: index, eqlOptions }), + eqlQueryValidatorFactory({ indexPatterns: index, eqlOptions }), 300 )(...args); }, From 57075b04be85044c3d4349eb8dec31a57919f914 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 6 Nov 2024 15:26:45 +0100 Subject: [PATCH 06/28] shorten long paths --- ...override_edit_adapter.tsx => evt_cat_field_edit_adapter.tsx} | 0 ...egory_override_edit_form.tsx => evt_cat_field_edit_form.tsx} | 2 +- .../final_edit/fields/event_category_override/index.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/{event_category_override_edit_adapter.tsx => evt_cat_field_edit_adapter.tsx} (100%) rename x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/{event_category_override_edit_form.tsx => evt_cat_field_edit_form.tsx} (94%) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/event_category_override_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_adapter.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/event_category_override_edit_adapter.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_adapter.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/event_category_override_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_form.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/event_category_override_edit_form.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_form.tsx index 89ad22e62bbbc..351a25c525f22 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/event_category_override_edit_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_form.tsx @@ -12,7 +12,7 @@ import type { } from '../../../../../../../../../common/api/detection_engine'; import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; -import { EventCategoryOverrideEditAdapter } from './event_category_override_edit_adapter'; +import { EventCategoryOverrideEditAdapter } from './evt_cat_field_edit_adapter'; export function EventCategoryOverrideEditForm(): JSX.Element { return ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts index 0c7abbd45c79b..17aeafc97fd20 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './event_category_override_edit_form'; +export * from './evt_cat_field_edit_form'; From a0df4043e7a7000cc044a19ce4480c572b08039f Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 6 Nov 2024 18:44:08 +0100 Subject: [PATCH 07/28] remove empty tiebreakerField's default value --- .../security_solution/public/common/mock/global_state.ts | 1 - .../components/timeline/query_bar/eql/selectors.tsx | 5 ----- .../security_solution/public/timelines/store/defaults.ts | 1 - 3 files changed, 7 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 6ae63769b114d..79bd0eb558683 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -349,7 +349,6 @@ export const mockGlobalState: State = { description: '', eqlOptions: { eventCategoryField: 'event.category', - tiebreakerField: '', timestampField: '@timestamp', }, eventIdToNoteIds: { '1': ['1'] }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx index e533ef9175178..a8f287ccb6a5d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx @@ -14,11 +14,6 @@ export const getEqlOptions = () => (timeline) => timeline?.eqlOptions ?? { eventCategoryField: [{ label: 'event.category' }], - tiebreakerField: [ - { - label: '', - }, - ], timestampField: [ { label: '@timestamp', diff --git a/x-pack/plugins/security_solution/public/timelines/store/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/defaults.ts index 5dbe2d1b9c1e8..69d306be9b85f 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/defaults.ts @@ -32,7 +32,6 @@ export const timelineDefaults: SubsetTimelineModel & description: '', eqlOptions: { eventCategoryField: 'event.category', - tiebreakerField: '', timestampField: '@timestamp', query: '', size: 100, From fe00b578fd5356a4f6a6be05d0d6440559778ba4 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 6 Nov 2024 18:46:17 +0100 Subject: [PATCH 08/28] remove unused EQL validation --- .../timeline/query_bar/eql/index.tsx | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index be717c295b0eb..0c065b45479c8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -16,8 +16,6 @@ import { useSourcererDataView } from '../../../../../sourcerer/containers'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import { SourcererScopeName } from '../../../../../sourcerer/store/model'; import { EqlQueryEdit } from '../../../../../detection_engine/rule_creation_ui/components/eql_query_edit'; -import { debounceAsync } from '../../../../../detection_engine/rule_creation_ui/validators/debounce_async'; -import { eqlQueryValidatorFactory } from '../../../../../detection_engine/rule_creation_ui/validators/eql_query_validator_factory'; import type { FieldValueQueryBar } from '../../../../../detection_engine/rule_creation_ui/components/query_bar'; import type { FormSchema } from '../../../../../shared_imports'; @@ -49,21 +47,6 @@ const schema: FormSchema = { eqlOptions: { fieldsToValidateOnChange: ['eqlOptions', 'eqlQueryBar'], }, - eqlQueryBar: { - validations: [ - { - validator: (...args) => { - const [{ formData }] = args; - const { index, eqlOptions } = formData; - - return debounceAsync( - eqlQueryValidatorFactory({ indexPatterns: index, eqlOptions }), - 300 - )(...args); - }, - }, - ], - }, }; const hiddenUseFieldClassName = css` @@ -201,6 +184,7 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) path="eqlQueryBar" eqlFieldsComboBoxOptions={eqlFieldsComboBoxOptions} eqlOptions={optionsSelected} + showEqlSizeOption dataView={sourcererDataView as unknown as DataViewBase} loading={indexPatternsLoading} disabled={indexPatternsLoading} From 878f1672eb75f58a66b7ce149bf4ca753548fbb3 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 6 Nov 2024 18:46:30 +0100 Subject: [PATCH 09/28] expose showEqlSizeOption property --- .../components/eql_query_edit/eql_query_edit.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx index a465ca1603df3..f0eecf93216e3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx @@ -25,6 +25,7 @@ interface EqlQueryEditProps { path: string; eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions; eqlOptions?: EqlOptions; + showEqlSizeOption?: boolean; dataView: DataViewBase; required?: boolean; loading?: boolean; @@ -38,6 +39,7 @@ export function EqlQueryEdit({ path, eqlFieldsComboBoxOptions, eqlOptions, + showEqlSizeOption = false, dataView, required, loading, @@ -51,7 +53,7 @@ export function EqlQueryEdit({ eqlFieldsComboBoxOptions, eqlOptions, onEqlOptionsChange, - isSizeOptionDisabled: true, + isSizeOptionDisabled: !showEqlSizeOption, isDisabled: disabled, isLoading: loading, indexPattern: dataView, @@ -64,6 +66,7 @@ export function EqlQueryEdit({ [ eqlFieldsComboBoxOptions, eqlOptions, + showEqlSizeOption, onEqlOptionsChange, onValidityChange, onValiditingChange, From aa3360af03d630def7804d55306091d51fd7e261 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 6 Nov 2024 18:51:22 +0100 Subject: [PATCH 10/28] expose showFilterBar prop --- .../components/eql_query_edit/eql_query_edit.tsx | 5 ++++- .../rule_creation_ui/components/step_define_rule/index.tsx | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx index f0eecf93216e3..00144894f1388 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx @@ -26,6 +26,7 @@ interface EqlQueryEditProps { eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions; eqlOptions?: EqlOptions; showEqlSizeOption?: boolean; + showFilterBar?: boolean; dataView: DataViewBase; required?: boolean; loading?: boolean; @@ -40,6 +41,7 @@ export function EqlQueryEdit({ eqlFieldsComboBoxOptions, eqlOptions, showEqlSizeOption = false, + showFilterBar = false, dataView, required, loading, @@ -57,7 +59,7 @@ export function EqlQueryEdit({ isDisabled: disabled, isLoading: loading, indexPattern: dataView, - showFilterBar: true, + showFilterBar, idAria: 'ruleEqlQueryBar', dataTestSubj: 'ruleEqlQueryBar', onValidityChange, @@ -67,6 +69,7 @@ export function EqlQueryEdit({ eqlFieldsComboBoxOptions, eqlOptions, showEqlSizeOption, + showFilterBar, onEqlOptionsChange, onValidityChange, onValiditingChange, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx index 0eee4e7198265..a5636f9bc77f2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx @@ -798,6 +798,7 @@ const StepDefineRuleComponent: FC = ({ key="eqlQueryBar" path="queryBar" required + showFilterBar eqlFieldsComboBoxOptions={optionsData} eqlOptions={optionsSelected} dataView={indexPattern} From 759e0218120d8d43ad841ed152ca7da1038aa5fb Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 7 Nov 2024 08:29:52 +0100 Subject: [PATCH 11/28] fix unit tests --- .../security_solution/public/common/mock/timeline_results.ts | 1 - .../public/detections/components/alerts_table/actions.test.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index 406ff1f4ffe31..2ffd70e531ea4 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -2051,7 +2051,6 @@ export const defaultTimelineProps: CreateTimelineProps = { eventCategoryField: 'event.category', query: '', size: 100, - tiebreakerField: '', timestampField: '@timestamp', }, eventIdToNoteIds: {}, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index b16d59045d835..da17ad285b87c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -355,7 +355,6 @@ describe('alert actions', () => { eventCategoryField: 'event.category', query: '', size: 100, - tiebreakerField: '', timestampField: '@timestamp', }, eventIdToNoteIds: {}, From ebb386c457331963515dfa56ff3922b3c60711f2 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Mon, 11 Nov 2024 17:59:51 +0100 Subject: [PATCH 12/28] remove commented code --- .../fields/eql_query/eql_query_edit_form.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx index 2296652ac6f2b..5d63ddcf735e9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx @@ -16,10 +16,6 @@ import { QueryLanguageEnum, } from '../../../../../../../../../common/api/detection_engine'; import { EqlQueryEditAdapter } from './eql_query_edit_adapter'; -// import { -// debounceAsync, -// eqlValidator, -// } from '@kbn/security-solution-plugin/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators'; export function EqlQueryEditForm(): JSX.Element { return ( @@ -32,15 +28,7 @@ export function EqlQueryEditForm(): JSX.Element { ); } -const kqlQuerySchema = { - eqlQuery: { - validations: [ - // { - // validator: debounceAsync(eqlValidator, 300), - // }, - ], - }, -} as FormSchema<{ +const kqlQuerySchema = {} as FormSchema<{ eqlQuery: RuleEqlQuery; }>; From d2adba950d8b4b1a50159ff67871c3ce27d69244 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Mon, 11 Nov 2024 18:37:35 +0100 Subject: [PATCH 13/28] use comma delimiter while joining index patterns --- .../rule_creation_ui/validators/eql_query_validator_factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts index 9897517fa8981..08a68039e035e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts @@ -45,7 +45,7 @@ export function eqlQueryValidatorFactory({ ? await data.dataViews.get(dataViewId) : undefined; - const dataViewTitle = dataView?.getIndexPattern() ?? indexPatterns?.join() ?? ''; + const dataViewTitle = dataView?.getIndexPattern() ?? indexPatterns?.join(',') ?? ''; const runtimeMappings = dataView?.getRuntimeMappings() ?? {}; const response = await validateEql({ From 635fcc9fa6037e3b38536a48d26c6772cc561639 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Tue, 12 Nov 2024 14:12:55 +0100 Subject: [PATCH 14/28] fix a typo --- .../components/eql_query_edit/eql_query_bar.tsx | 16 ++++++++-------- .../components/eql_query_edit/eql_query_edit.tsx | 8 ++++---- .../components/timeline/query_bar/eql/index.tsx | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx index d99c639ec5b6f..1c443641cc1ae 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx @@ -70,7 +70,7 @@ export interface EqlQueryBarProps { isSizeOptionDisabled?: boolean; onEqlOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; onValidityChange?: (arg: boolean) => void; - onValiditingChange?: (arg: boolean) => void; + onValidatingChange?: (arg: boolean) => void; } export const EqlQueryBar: FC = ({ @@ -85,7 +85,7 @@ export const EqlQueryBar: FC = ({ isSizeOptionDisabled, onEqlOptionsChange, onValidityChange, - onValiditingChange, + onValidatingChange, }) => { const { addError } = useAppToasts(); const [errorMessages, setErrorMessages] = useState([]); @@ -115,10 +115,10 @@ export const EqlQueryBar: FC = ({ }, [error, addError]); useEffect(() => { - if (onValiditingChange) { - onValiditingChange(isValidating); + if (onValidatingChange) { + onValidatingChange(isValidating); } - }, [isValidating, onValiditingChange]); + }, [isValidating, onValidatingChange]); useEffect(() => { let isSubscribed = true; @@ -156,8 +156,8 @@ export const EqlQueryBar: FC = ({ const handleChange = useCallback( (e: ChangeEvent) => { const newQuery = e.target.value; - if (onValiditingChange) { - onValiditingChange(true); + if (onValidatingChange) { + onValidatingChange(true); } setErrorMessages([]); setFieldValue({ @@ -169,7 +169,7 @@ export const EqlQueryBar: FC = ({ saved_id: null, }); }, - [fieldValue, setFieldValue, onValiditingChange] + [fieldValue, setFieldValue, onValidatingChange] ); return ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx index 00144894f1388..2a64f38b51b8f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx @@ -33,7 +33,7 @@ interface EqlQueryEditProps { disabled?: boolean; onEqlOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; onValidityChange?: (arg: boolean) => void; - onValiditingChange?: (arg: boolean) => void; + onValidatingChange?: (arg: boolean) => void; } export function EqlQueryEdit({ @@ -48,7 +48,7 @@ export function EqlQueryEdit({ disabled, onEqlOptionsChange, onValidityChange, - onValiditingChange, + onValidatingChange, }: EqlQueryEditProps): JSX.Element { const componentProps = useMemo( () => ({ @@ -63,7 +63,7 @@ export function EqlQueryEdit({ idAria: 'ruleEqlQueryBar', dataTestSubj: 'ruleEqlQueryBar', onValidityChange, - onValiditingChange, + onValidatingChange, }), [ eqlFieldsComboBoxOptions, @@ -72,7 +72,7 @@ export function EqlQueryEdit({ showFilterBar, onEqlOptionsChange, onValidityChange, - onValiditingChange, + onValidatingChange, dataView, loading, disabled, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index 0c065b45479c8..d611d00d504ca 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -189,7 +189,7 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) loading={indexPatternsLoading} disabled={indexPatternsLoading} onValidityChange={setIsQueryBarValid} - onValiditingChange={setIsQueryBarValidating} + onValidatingChange={setIsQueryBarValidating} onEqlOptionsChange={onOptionsChange} /> From c54ac35f65d6bfa6f88af738fd9bc301a1624044 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Tue, 12 Nov 2024 17:39:55 +0100 Subject: [PATCH 15/28] add assertUnreachable for rules having all editable fields implemented --- .../three_way_diff/final_edit/custom_query_rule_field_edit.tsx | 3 ++- .../three_way_diff/final_edit/eql_rule_field_edit.tsx | 3 ++- .../three_way_diff/final_edit/saved_query_rule_field_edit.tsx | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx index 69e11c85e4d51..843f128dae8a6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { assertUnreachable } from '../../../../../../../common/utility_types'; import type { UpgradeableCustomQueryFields } from '../../../../model/prebuilt_rule_upgrade/fields'; import { KqlQueryEditForm } from './fields/kql_query'; import { DataSourceEditForm } from './fields/data_source'; @@ -24,6 +25,6 @@ export function CustomQueryRuleFieldEdit({ fieldName }: CustomQueryRuleFieldEdit case 'alert_suppression': return ; default: - return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented + return assertUnreachable(fieldName); } } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx index 0dad1bf131f8b..b08c9bd7f5732 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { assertUnreachable } from '../../../../../../../common/utility_types'; import type { UpgradeableEqlFields } from '../../../../model/prebuilt_rule_upgrade/fields'; import { DataSourceEditForm } from './fields/data_source'; import { AlertSuppressionEditForm } from './fields/alert_suppression'; @@ -33,6 +34,6 @@ export function EqlRuleFieldEdit({ fieldName }: EqlRuleFieldEditProps) { case 'tiebreaker_field': return ; default: - return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented + return assertUnreachable(fieldName); } } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx index b18b3203fdfb5..0c1ba5b1b6f61 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { assertUnreachable } from '../../../../../../../common/utility_types'; import type { UpgradeableSavedQueryFields } from '../../../../model/prebuilt_rule_upgrade/fields'; import { KqlQueryEditForm } from './fields/kql_query'; import { DataSourceEditForm } from './fields/data_source'; @@ -24,6 +25,6 @@ export function SavedQueryRuleFieldEdit({ fieldName }: SavedQueryRuleFieldEditPr case 'alert_suppression': return ; default: - return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented + return assertUnreachable(fieldName); } } From ca8b4af73a72b5ff3156b494dce1213eba5e9373 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Tue, 12 Nov 2024 18:04:53 +0100 Subject: [PATCH 16/28] fix post-rebase issues --- .../rule_creation_ui/components/step_define_rule/schema.tsx | 3 --- .../rule_creation_ui/components/step_define_rule/utils.ts | 5 +---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx index 58cbd6cbe7091..17406676e0eda 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx @@ -42,15 +42,12 @@ import { THREAT_MATCH_EMPTIES, EQL_SEQUENCE_SUPPRESSION_GROUPBY_VALIDATION_TEXT, } from './translations'; -import { getQueryRequiredMessage } from './utils'; import { ALERT_SUPPRESSION_DURATION_FIELD_NAME, ALERT_SUPPRESSION_FIELDS_FIELD_NAME, ALERT_SUPPRESSION_MISSING_FIELDS_FIELD_NAME, } from '../../../rule_creation/components/alert_suppression_edit'; import * as alertSuppressionEditI81n from '../../../rule_creation/components/alert_suppression_edit/components/translations'; -import { dataViewIdValidatorFactory } from '../../validators/data_view_id_validator_factory'; -import { indexPatternValidatorFactory } from '../../validators/index_pattern_validator_factory'; import { queryRequiredValidatorFactory } from '../../validators/query_required_validator_factory'; import { kueryValidatorFactory } from '../../validators/kuery_validator_factory'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts index 67d8565947bad..baedda1ae1620 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts @@ -13,13 +13,10 @@ import type { FieldSpec } from '@kbn/data-plugin/common'; * Keyword, Numeric, ip, boolean, or binary. * https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html */ -export const getTermsAggregationFields = (fields: FieldSpec[]): FieldSpec[] => { +export const getTermsAggregationFields = (fields: FieldSpec[]): FieldSpec[] => fields.filter( (field) => field.aggregatable === true && ALLOWED_AGGREGATABLE_FIELD_TYPES_SET.has(field.type) ); - return fields.filter((field) => field.aggregatable === true && allowedTypesSet.has(field.type)); -}; - // binary types is excluded, as binary field has property aggregatable === false const ALLOWED_AGGREGATABLE_FIELD_TYPES_SET = new Set(['string', 'number', 'ip', 'boolean']); From 94a2be936bc5ead3661aa578d5321cdcceb93922 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 13 Nov 2024 09:34:15 +0100 Subject: [PATCH 17/28] add fieldsToValidateOnChange to EqlQueryEdit --- .../components/eql_query_edit/eql_query_edit.tsx | 7 ++++++- .../rule_creation_ui/components/step_define_rule/index.tsx | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx index 2a64f38b51b8f..c444f12915836 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx @@ -23,6 +23,7 @@ import type { FieldValueQueryBar } from '../query_bar'; interface EqlQueryEditProps { path: string; + fieldsToValidateOnChange?: string | string[]; eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions; eqlOptions?: EqlOptions; showEqlSizeOption?: boolean; @@ -38,6 +39,7 @@ interface EqlQueryEditProps { export function EqlQueryEdit({ path, + fieldsToValidateOnChange, eqlFieldsComboBoxOptions, eqlOptions, showEqlSizeOption = false, @@ -81,6 +83,9 @@ export function EqlQueryEdit({ const fieldConfig: FieldConfig = useMemo( () => ({ label: i18n.EQL_QUERY_BAR_LABEL, + fieldsToValidateOnChange: fieldsToValidateOnChange + ? [path, fieldsToValidateOnChange].flat() + : undefined, validations: [ ...(required ? [ @@ -107,7 +112,7 @@ export function EqlQueryEdit({ }, ], }), - [required, dataView.id, dataView.title, eqlOptions] + [required, dataView.id, dataView.title, eqlOptions, path, fieldsToValidateOnChange] ); return ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx index a5636f9bc77f2..e83643384f6a0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx @@ -91,7 +91,10 @@ import { useAlertSuppression } from '../../../rule_management/logic/use_alert_su import { AiAssistant } from '../ai_assistant'; import { RelatedIntegrations } from '../../../rule_creation/components/related_integrations'; import { useMLRuleConfig } from '../../../../common/components/ml/hooks/use_ml_rule_config'; -import { AlertSuppressionEdit } from '../../../rule_creation/components/alert_suppression_edit'; +import { + ALERT_SUPPRESSION_FIELDS_FIELD_NAME, + AlertSuppressionEdit, +} from '../../../rule_creation/components/alert_suppression_edit'; import { ThresholdAlertSuppressionEdit } from '../../../rule_creation/components/threshold_alert_suppression_edit'; import { usePersistentAlertSuppressionState } from './use_persistent_alert_suppression_state'; @@ -797,6 +800,7 @@ const StepDefineRuleComponent: FC = ({ Date: Fri, 15 Nov 2024 05:22:26 +0100 Subject: [PATCH 18/28] use form field for EQL Options --- .../public/common/hooks/eql/api.ts | 9 +- .../eql_query_edit/eql_query_bar.test.tsx | 51 ++++-- .../eql_query_edit/eql_query_bar.tsx | 41 +++-- .../eql_query_edit/eql_query_edit.tsx | 64 ++++---- .../components/eql_query_edit/footer.test.tsx | 4 +- .../components/eql_query_edit/footer.tsx | 79 +++++----- .../step_define_rule/index.test.tsx | 5 +- .../components/step_define_rule/index.tsx | 31 +--- .../rule_creation_ui/pages/form.tsx | 13 +- .../pages/rule_creation/index.tsx | 14 +- .../pages/rule_editing/index.tsx | 6 - .../eql_query/eql_query_edit_adapter.tsx | 3 - .../timeline/query_bar/eql/index.tsx | 147 +++++++++--------- .../timeline/query_bar/eql/selectors.tsx | 2 +- .../public/timelines/store/actions.ts | 2 +- 15 files changed, 230 insertions(+), 241 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts index 6e730570faafd..b586e0593ab6f 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts @@ -62,9 +62,12 @@ export const validateEql = async ({ params: { index: dataViewTitle, body: { query, runtime_mappings: runtimeMappings, size: 0 }, - timestamp_field: eqlOptions?.timestampField, - tiebreaker_field: eqlOptions?.tiebreakerField, - event_category_field: eqlOptions?.eventCategoryField, + // Prevent passing empty string values + timestamp_field: eqlOptions?.timestampField ? eqlOptions.timestampField : undefined, + tiebreaker_field: eqlOptions?.tiebreakerField ? eqlOptions.tiebreakerField : undefined, + event_category_field: eqlOptions?.eventCategoryField + ? eqlOptions.eventCategoryField + : undefined, }, options: { ignore: [400] }, }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.test.tsx index bfa723ad8b3d9..60d0a02b066d0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.test.tsx @@ -11,6 +11,7 @@ import { render, screen, fireEvent, within } from '@testing-library/react'; import { mockIndexPattern, TestProviders, useFormFieldMock } from '../../../../common/mock'; import { mockQueryBar } from '../../../rule_management_ui/components/rules_table/__mocks__/mock'; +import { selectEuiComboBoxOption } from '../../../../common/test/eui/combobox'; import type { EqlQueryBarProps } from './eql_query_bar'; import { EqlQueryBar } from './eql_query_bar'; import { getEqlValidationError } from './validators.mock'; @@ -116,35 +117,63 @@ describe('EqlQueryBar', () => { describe('EQL options interaction', () => { const mockOptionsData = { - keywordFields: [], + keywordFields: [{ label: 'category', value: 'category' }], dateFields: [{ label: 'timestamp', value: 'timestamp' }], - nonDateFields: [], + nonDateFields: [{ label: 'tiebreaker', value: 'tiebreaker' }], }; - it('invokes onOptionsChange when the EQL options change', () => { - const onOptionsChangeMock = jest.fn(); + it('updates EQL options', async () => { + let eqlOptions = {}; - const { getByTestId, getByText } = render( + const mockEqlOptionsField = useFormFieldMock({ + value: {}, + setValue: (updater) => { + if (typeof updater === 'function') { + eqlOptions = updater(eqlOptions); + } + }, + }); + + const { getByTestId } = render( ); // open options popover fireEvent.click(getByTestId('eql-settings-trigger')); - // display combobox options - within(getByTestId(`eql-timestamp-field`)).getByRole('combobox').focus(); - // select timestamp - getByText('timestamp').click(); - expect(onOptionsChangeMock).toHaveBeenCalledWith('timestampField', 'timestamp'); + await selectEuiComboBoxOption({ + comboBoxToggleButton: within(getByTestId('eql-event-category-field')).getByRole('combobox'), + optionText: 'category', + }); + + expect(eqlOptions).toEqual({ eventCategoryField: 'category' }); + + await selectEuiComboBoxOption({ + comboBoxToggleButton: within(getByTestId('eql-tiebreaker-field')).getByRole('combobox'), + optionText: 'tiebreaker', + }); + + expect(eqlOptions).toEqual({ eventCategoryField: 'category', tiebreakerField: 'tiebreaker' }); + + await selectEuiComboBoxOption({ + comboBoxToggleButton: within(getByTestId('eql-timestamp-field')).getByRole('combobox'), + optionText: 'timestamp', + }); + + expect(eqlOptions).toEqual({ + eventCategoryField: 'category', + tiebreakerField: 'tiebreaker', + timestampField: 'timestamp', + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx index 1c443641cc1ae..603224e22a9b3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx @@ -17,16 +17,13 @@ import { FilterManager } from '@kbn/data-plugin/public'; import type { FieldHook } from '../../../../shared_imports'; import { FilterBar } from '../../../../common/components/filter_bar'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; -import * as i18n from './translations'; +import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; +import { useKibana } from '../../../../common/lib/kibana'; +import type { FieldValueQueryBar } from '../query_bar'; +import type { EqlQueryBarFooterProps } from './footer'; import { EqlQueryBarFooter } from './footer'; import { getValidationResults } from './validators'; -import type { - EqlFieldsComboBoxOptions, - EqlOptions, - FieldsEqlOptions, -} from '../../../../../common/search_strategy'; -import { useKibana } from '../../../../common/lib/kibana'; +import * as i18n from './translations'; const TextArea = styled(EuiTextArea)` display: block; @@ -60,15 +57,14 @@ const StyledFormRow = styled(EuiFormRow)` export interface EqlQueryBarProps { dataTestSubj: string; - field: FieldHook; - isLoading: boolean; + field: FieldHook; + eqlOptionsField?: FieldHook; + isLoading?: boolean; indexPattern: DataViewBase; showFilterBar?: boolean; idAria?: string; eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions; - eqlOptions?: EqlOptions; isSizeOptionDisabled?: boolean; - onEqlOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; onValidityChange?: (arg: boolean) => void; onValidatingChange?: (arg: boolean) => void; } @@ -76,14 +72,13 @@ export interface EqlQueryBarProps { export const EqlQueryBar: FC = ({ dataTestSubj, field, - isLoading, + eqlOptionsField, + isLoading = false, indexPattern, showFilterBar, idAria, eqlFieldsComboBoxOptions, - eqlOptions: optionsSelected, isSizeOptionDisabled, - onEqlOptionsChange, onValidityChange, onValidatingChange, }) => { @@ -172,6 +167,18 @@ export const EqlQueryBar: FC = ({ [fieldValue, setFieldValue, onValidatingChange] ); + const handleEqlOptionsChange = useCallback< + NonNullable + >( + (eqlOptionsFieldName, value) => { + eqlOptionsField?.setValue((prevEqlOptions) => ({ + ...prevEqlOptions, + [eqlOptionsFieldName]: value, + })); + }, + [eqlOptionsField] + ); + return ( = ({ isLoading={isValidating} isSizeOptionDisabled={isSizeOptionDisabled} optionsData={eqlFieldsComboBoxOptions} - optionsSelected={optionsSelected} - onOptionsChange={onEqlOptionsChange} + eqlOptions={eqlOptionsField?.value} + onEqlOptionsChange={handleEqlOptionsChange} /> {showFilterBar && ( <> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx index c444f12915836..5bd8d9afbc70b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx @@ -8,12 +8,8 @@ import React, { useMemo } from 'react'; import type { DataViewBase } from '@kbn/es-query'; import type { FieldConfig } from '../../../../shared_imports'; -import { UseField } from '../../../../shared_imports'; -import type { - EqlFieldsComboBoxOptions, - EqlOptions, - FieldsEqlOptions, -} from '../../../../../common/search_strategy'; +import { UseField, UseMultiFields } from '../../../../shared_imports'; +import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; import { queryRequiredValidatorFactory } from '../../validators/query_required_validator_factory'; import { debounceAsync } from '../../validators/debounce_async'; import { eqlQueryValidatorFactory } from '../../validators/eql_query_validator_factory'; @@ -23,40 +19,34 @@ import type { FieldValueQueryBar } from '../query_bar'; interface EqlQueryEditProps { path: string; + eqlOptionsPath?: string; fieldsToValidateOnChange?: string | string[]; eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions; - eqlOptions?: EqlOptions; showEqlSizeOption?: boolean; showFilterBar?: boolean; dataView: DataViewBase; required?: boolean; loading?: boolean; disabled?: boolean; - onEqlOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; onValidityChange?: (arg: boolean) => void; - onValidatingChange?: (arg: boolean) => void; } export function EqlQueryEdit({ path, + eqlOptionsPath, fieldsToValidateOnChange, eqlFieldsComboBoxOptions, - eqlOptions, showEqlSizeOption = false, showFilterBar = false, dataView, required, loading, disabled, - onEqlOptionsChange, onValidityChange, - onValidatingChange, }: EqlQueryEditProps): JSX.Element { const componentProps = useMemo( () => ({ eqlFieldsComboBoxOptions, - eqlOptions, - onEqlOptionsChange, isSizeOptionDisabled: !showEqlSizeOption, isDisabled: disabled, isLoading: loading, @@ -65,16 +55,12 @@ export function EqlQueryEdit({ idAria: 'ruleEqlQueryBar', dataTestSubj: 'ruleEqlQueryBar', onValidityChange, - onValidatingChange, }), [ eqlFieldsComboBoxOptions, - eqlOptions, showEqlSizeOption, showFilterBar, - onEqlOptionsChange, onValidityChange, - onValidatingChange, dataView, loading, disabled, @@ -95,26 +81,52 @@ export function EqlQueryEdit({ ] : []), { - validator: debounceAsync( - eqlQueryValidatorFactory( + validator: debounceAsync((...args) => { + const [{ formData }] = args; + const eqlOptions = + eqlOptionsPath && formData[eqlOptionsPath] ? formData[eqlOptionsPath] : {}; + + return eqlQueryValidatorFactory( dataView.id ? { dataViewId: dataView.id, - eqlOptions: eqlOptions ?? {}, + eqlOptions, } : { indexPatterns: dataView.title.split(','), - eqlOptions: eqlOptions ?? {}, + eqlOptions, } - ), - 300 - ), + )(...args); + }, 300), }, ], }), - [required, dataView.id, dataView.title, eqlOptions, path, fieldsToValidateOnChange] + [eqlOptionsPath, required, dataView.id, dataView.title, path, fieldsToValidateOnChange] ); + if (eqlOptionsPath) { + return ( + + fields={{ + eqlQuery: { + path, + config: fieldConfig, + }, + eqlOptions: { + path: eqlOptionsPath, + }, + }} + > + {({ eqlQuery, eqlOptions }) => ( + + )} + + ); + } + return ( { it('EQL settings button is enable when popover is NOT open', () => { const wrapper = mount( - + ); @@ -44,7 +44,7 @@ describe('EQL footer', () => { it('disable EQL settings button when popover is open', () => { const wrapper = mount( - + ); wrapper.find(`[data-test-subj="eql-settings-trigger"]`).first().simulate('click'); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.tsx index 0c0b0984ce279..688ceb03ddbe4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.tsx @@ -23,25 +23,24 @@ import styled from 'styled-components'; import type { DebouncedFunc } from 'lodash'; import { debounce } from 'lodash'; -import type { - EqlFieldsComboBoxOptions, - EqlOptions, - FieldsEqlOptions, -} from '../../../../../common/search_strategy'; +import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; import * as i18n from './translations'; import { ErrorsPopover } from './errors_popover'; import { EqlOverviewLink } from './eql_overview_link'; -export interface Props { +export interface EqlQueryBarFooterProps { errors: string[]; isLoading?: boolean; isSizeOptionDisabled?: boolean; optionsData?: EqlFieldsComboBoxOptions; - optionsSelected?: EqlOptions; - onOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; + eqlOptions?: EqlOptions; + onEqlOptionsChange?: ( + field: Field, + newValue: EqlOptions[Field] + ) => void; } -type SizeVoidFunc = (newSize: string) => void; +type SizeVoidFunc = (newSize: number) => void; const Container = styled(EuiFlexGroup)` border-radius: 0; @@ -69,16 +68,16 @@ const Spinner = styled(EuiLoadingSpinner)` const singleSelection = { asPlainText: true }; -export const EqlQueryBarFooter: FC = ({ +export const EqlQueryBarFooter: FC = ({ errors, isLoading, isSizeOptionDisabled, optionsData, - optionsSelected, - onOptionsChange, + eqlOptions, + onEqlOptionsChange, }) => { const [openEqlSettings, setIsOpenEqlSettings] = useState(false); - const [localSize, setLocalSize] = useState(optionsSelected?.size ?? 100); + const [localSize, setLocalSize] = useState(eqlOptions?.size ?? 100); const debounceSize = useRef>(); const openEqlSettingsHandler = useCallback(() => { @@ -90,74 +89,70 @@ export const EqlQueryBarFooter: FC = ({ const handleEventCategoryField = useCallback( (opt: EuiComboBoxOptionOption[]) => { - if (onOptionsChange) { + if (onEqlOptionsChange) { if (opt.length > 0) { - onOptionsChange('eventCategoryField', opt[0].label); + onEqlOptionsChange('eventCategoryField', opt[0].label); } else { - onOptionsChange('eventCategoryField', undefined); + onEqlOptionsChange('eventCategoryField', undefined); } } }, - [onOptionsChange] + [onEqlOptionsChange] ); const handleTiebreakerField = useCallback( (opt: EuiComboBoxOptionOption[]) => { - if (onOptionsChange) { + if (onEqlOptionsChange) { if (opt.length > 0) { - onOptionsChange('tiebreakerField', opt[0].label); + onEqlOptionsChange('tiebreakerField', opt[0].label); } else { - onOptionsChange('tiebreakerField', undefined); + onEqlOptionsChange('tiebreakerField', undefined); } } }, - [onOptionsChange] + [onEqlOptionsChange] ); const handleTimestampField = useCallback( (opt: EuiComboBoxOptionOption[]) => { - if (onOptionsChange) { + if (onEqlOptionsChange) { if (opt.length > 0) { - onOptionsChange('timestampField', opt[0].label); + onEqlOptionsChange('timestampField', opt[0].label); } else { - onOptionsChange('timestampField', undefined); + onEqlOptionsChange('timestampField', undefined); } } }, - [onOptionsChange] + [onEqlOptionsChange] ); const handleSizeField = useCallback>( (evt) => { - if (onOptionsChange) { + if (onEqlOptionsChange) { setLocalSize(evt?.target?.valueAsNumber); if (debounceSize.current?.cancel) { debounceSize.current?.cancel(); } - debounceSize.current = debounce((newSize) => onOptionsChange('size', newSize), 800); - debounceSize.current(evt?.target?.value); + debounceSize.current = debounce((newSize) => onEqlOptionsChange('size', newSize), 800); + debounceSize.current(evt?.target?.valueAsNumber); } }, - [onOptionsChange] + [onEqlOptionsChange] ); const eventCategoryField = useMemo( () => - optionsSelected?.eventCategoryField != null - ? [{ label: optionsSelected?.eventCategoryField }] + eqlOptions?.eventCategoryField != null + ? [{ label: eqlOptions?.eventCategoryField }] : undefined, - [optionsSelected?.eventCategoryField] + [eqlOptions?.eventCategoryField] ); const tiebreakerField = useMemo( () => - optionsSelected?.tiebreakerField != null - ? [{ label: optionsSelected?.tiebreakerField }] - : undefined, - [optionsSelected?.tiebreakerField] + eqlOptions?.tiebreakerField != null ? [{ label: eqlOptions?.tiebreakerField }] : undefined, + [eqlOptions?.tiebreakerField] ); const timestampField = useMemo( () => - optionsSelected?.timestampField != null - ? [{ label: optionsSelected?.timestampField }] - : undefined, - [optionsSelected?.timestampField] + eqlOptions?.timestampField != null ? [{ label: eqlOptions?.timestampField }] : undefined, + [eqlOptions?.timestampField] ); return ( @@ -183,13 +178,13 @@ export const EqlQueryBarFooter: FC = ({ - {!onOptionsChange && ( + {!onEqlOptionsChange && ( )} - {onOptionsChange && ( + {onEqlOptionsChange && ( <> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx index 50264fffabfb8..364f1b7705732 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { screen, fireEvent, render, within, act, waitFor } from '@testing-library/react'; import type { Type as RuleType } from '@kbn/securitysolution-io-ts-alerting-types'; import type { DataViewBase } from '@kbn/es-query'; @@ -638,7 +638,6 @@ function TestForm({ onSubmit, formProps, }: TestFormProps): JSX.Element { - const [selectedEqlOptions, setSelectedEqlOptions] = useState(stepDefineDefaultValue.eqlOptions); const { form } = useForm({ options: { stripEmptyFields: false }, schema: defineRuleSchema, @@ -653,8 +652,6 @@ function TestForm({ form={form} indicesConfig={[]} threatIndicesConfig={[]} - optionsSelected={selectedEqlOptions} - setOptionsSelected={setSelectedEqlOptions} indexPattern={indexPattern} isIndexPatternLoading={false} isQueryBarValid={true} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx index e83643384f6a0..52259ef5e3d80 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx @@ -33,7 +33,6 @@ import { useSetFieldValueWithCallback } from '../../../../common/utils/use_set_f import type { SetRuleQuery } from '../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; import { useRuleFromTimeline } from '../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; -import type { EqlOptions, FieldsEqlOptions } from '../../../../../common/search_strategy'; import { filterRuleFieldsForType, getStepDataDataSource } from '../../pages/rule_creation/helpers'; import type { DefineStepRule, @@ -108,8 +107,6 @@ export interface StepDefineRuleProps extends RuleStepProps { threatIndicesConfig: string[]; defaultSavedQuery?: SavedQuery; form: FormHook; - optionsSelected: EqlOptions; - setOptionsSelected: React.Dispatch>; indexPattern: DataViewBase; isIndexPatternLoading: boolean; isQueryBarValid: boolean; @@ -166,13 +163,11 @@ const StepDefineRuleComponent: FC = ({ isLoading, isQueryBarValid, isUpdateView = false, - optionsSelected, queryBarSavedId, queryBarTitle, ruleType, setIsQueryBarValid, setIsThreatQueryBarValid, - setOptionsSelected, shouldLoadQueryDynamically, threatIndex, threatIndicesConfig, @@ -223,15 +218,12 @@ const StepDefineRuleComponent: FC = ({ }; if (timelineQueryBar.query.language === 'eql') { setRuleTypeCallback('eql', setQuery); - setOptionsSelected((prevOptions) => ({ - ...prevOptions, - ...(eqlOptions != null ? eqlOptions : {}), - })); + setFieldValue('eqlOptions', eqlOptions ?? {}); } else { setQuery(); } }, - [setFieldValue, setRuleTypeCallback, setOptionsSelected] + [setFieldValue, setRuleTypeCallback] ); const { onOpenTimeline, loading: timelineQueryLoading } = @@ -722,21 +714,6 @@ const StepDefineRuleComponent: FC = ({ ] ); - const onOptionsChange = useCallback( - (field: FieldsEqlOptions, value: string | undefined) => { - setOptionsSelected((prevOptions) => { - const newOptions = { - ...prevOptions, - [field]: value, - }; - - setFieldValue('eqlOptions', newOptions); - return newOptions; - }); - }, - [setFieldValue, setOptionsSelected] - ); - const optionsData = useMemo( () => isEmpty(indexPattern.fields) @@ -800,18 +777,16 @@ const StepDefineRuleComponent: FC = ({ - ) : isEsqlRule(ruleType) ? ( EsqlQueryBarMemo diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx index 02bc813589d39..64230fd3a8a23 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx @@ -19,7 +19,6 @@ import { useKibana } from '../../../common/lib/kibana'; import type { FormHook, ValidationError } from '../../../shared_imports'; import { useForm, useFormData } from '../../../shared_imports'; import { schema as defineRuleSchema } from '../components/step_define_rule/schema'; -import type { EqlOptions } from '../../../../common/search_strategy'; import { schema as aboutRuleSchema, threatMatchAboutSchema, @@ -53,20 +52,14 @@ export const useRuleForms = ({ options: { stripEmptyFields: false }, schema: defineRuleSchema, }); - const [eqlOptionsSelected, setEqlOptionsSelected] = useState( - defineStepDefault.eqlOptions - ); const [defineStepFormData] = useFormData({ form: defineStepForm, }); // FormData doesn't populate on the first render, so we use the defaultValue if the formData // doesn't have what we wanted const defineStepData = useMemo( - () => - 'index' in defineStepFormData - ? { ...defineStepFormData, eqlOptions: eqlOptionsSelected } - : defineStepDefault, - [defineStepDefault, defineStepFormData, eqlOptionsSelected] + () => ('index' in defineStepFormData ? defineStepFormData : defineStepDefault), + [defineStepDefault, defineStepFormData] ); // ABOUT STEP FORM @@ -118,8 +111,6 @@ export const useRuleForms = ({ scheduleStepData, actionsStepForm, actionsStepData, - eqlOptionsSelected, - setEqlOptionsSelected, }; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx index d19c9c7c89d0c..01435a2f7c654 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx @@ -171,8 +171,6 @@ const CreateRulePageComponent: React.FC = () => { scheduleStepData, actionsStepForm, actionsStepData, - eqlOptionsSelected, - setEqlOptionsSelected, } = useRuleForms({ defineStepDefault, aboutStepDefault: stepAboutDefaultValue, @@ -392,10 +390,9 @@ const CreateRulePageComponent: React.FC = () => { const createRuleFromFormData = useCallback( async (enabled: boolean) => { - const localDefineStepData: DefineStepRule = defineFieldsTransform({ - ...defineStepForm.getFormData(), - eqlOptions: eqlOptionsSelected, - }); + const localDefineStepData: DefineStepRule = defineFieldsTransform( + defineStepForm.getFormData() + ); const localAboutStepData = aboutStepForm.getFormData(); const localScheduleStepData = scheduleStepForm.getFormData(); const localActionsStepData = actionsStepForm.getFormData(); @@ -435,7 +432,6 @@ const CreateRulePageComponent: React.FC = () => { createRule, defineFieldsTransform, defineStepForm, - eqlOptionsSelected, navigateToApp, ruleType, scheduleStepForm, @@ -556,8 +552,6 @@ const CreateRulePageComponent: React.FC = () => { indicesConfig={indicesConfig} threatIndicesConfig={threatIndicesConfig} form={defineStepForm} - optionsSelected={eqlOptionsSelected} - setOptionsSelected={setEqlOptionsSelected} indexPattern={indexPattern} isIndexPatternLoading={isIndexPatternLoading} isQueryBarValid={isQueryBarValid} @@ -588,7 +582,6 @@ const CreateRulePageComponent: React.FC = () => { defineStepData, memoizedIndex, defineStepForm, - eqlOptionsSelected, indexPattern, indicesConfig, isCreateRuleLoading, @@ -596,7 +589,6 @@ const CreateRulePageComponent: React.FC = () => { isQueryBarValid, loading, memoDefineStepReadOnly, - setEqlOptionsSelected, threatIndicesConfig, ] ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx index 0a1733bd831fd..b09ee5f4e3f43 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx @@ -131,8 +131,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { scheduleStepData, actionsStepForm, actionsStepData, - eqlOptionsSelected, - setEqlOptionsSelected, } = useRuleForms({ defineStepDefault: defineRuleData, aboutStepDefault: aboutRuleData, @@ -232,8 +230,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { threatIndicesConfig={threatIndicesConfig} defaultSavedQuery={savedQuery} form={defineStepForm} - optionsSelected={eqlOptionsSelected} - setOptionsSelected={setEqlOptionsSelected} key="defineStep" indexPattern={indexPattern} isIndexPatternLoading={isIndexPatternLoading} @@ -355,8 +351,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { threatIndicesConfig, savedQuery, defineStepForm, - eqlOptionsSelected, - setEqlOptionsSelected, indexPattern, isIndexPatternLoading, isQueryBarValid, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx index b36e3d9f8abe0..9b4139fd1a00c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx @@ -25,7 +25,6 @@ export function EqlQueryEditAdapter({ { const dispatch = useDispatch(); - const isInit = useRef(true); - const [isQueryBarValid, setIsQueryBarValid] = useState(false); - const [isQueryBarValidating, setIsQueryBarValidating] = useState(false); const getOptionsSelected = useMemo(() => getEqlOptions(), []); - const optionsSelected = useDeepEqualSelector((state) => getOptionsSelected(state, timelineId)); + const eqlOptions = useDeepEqualSelector((state) => getOptionsSelected(state, timelineId)); const { loading: indexPatternsLoading, @@ -74,39 +72,74 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) index: [...selectedPatterns].sort(), eqlQueryBar: { ...defaultValues.eqlQueryBar, - query: { query: optionsSelected.query ?? '', language: 'eql' }, + query: { query: eqlOptions.query ?? '', language: 'eql' }, }, + eqlOptions, }), - [optionsSelected.query, selectedPatterns] + [eqlOptions, selectedPatterns] ); - const { form } = useForm({ - defaultValue: initialState, - options: { stripEmptyFields: false }, - schema, - }); - const { getFields, setFieldValue } = form; + const handleSubmit = useCallback>( + async (formData, isValid) => { + if (!isValid) { + return; + } - const onOptionsChange = useCallback( - (field: FieldsEqlOptions, value: string | undefined) => { dispatch( timelineActions.updateEqlOptions({ id: timelineId, - field, - value, + field: 'query', + value: `${formData.eqlQueryBar.query.query}`, }) ); - setFieldValue('eqlOptions', { ...optionsSelected, [field]: value }); + + for (const fieldName of Object.keys(formData.eqlOptions) as Array< + keyof typeof formData.eqlOptions + >) { + dispatch( + timelineActions.updateEqlOptions({ + id: timelineId, + field: fieldName, + value: formData.eqlOptions[fieldName], + }) + ); + } }, - [dispatch, optionsSelected, setFieldValue, timelineId] + [dispatch, timelineId] ); - const [{ eqlQueryBar: formEqlQueryBar }] = useFormData({ - form, - watch: ['eqlQueryBar'], + const { form } = useForm({ + defaultValue: initialState, + options: { stripEmptyFields: false }, + schema, + onSubmit: handleSubmit, }); + const { getFields } = form; + const handleOutsideEqlQueryEditClick = useCallback(() => form.submit(), [form]); - const prevEqlQuery = useRef(''); + // Reset the form when new EQL Query came from the state + useEffect(() => { + getFields().eqlQueryBar.setValue({ + ...defaultValues.eqlQueryBar, + query: { query: eqlOptions.query ?? '', language: 'eql' }, + }); + }, [getFields, eqlOptions.query]); + + // Reset the form when new EQL Options came from the state + useEffect(() => { + getFields().eqlOptions.setValue({ + eventCategoryField: eqlOptions.eventCategoryField, + tiebreakerField: eqlOptions.tiebreakerField, + timestampField: eqlOptions.timestampField, + size: eqlOptions.size, + }); + }, [ + getFields, + eqlOptions.eventCategoryField, + eqlOptions.tiebreakerField, + eqlOptions.timestampField, + eqlOptions.size, + ]); const eqlFieldsComboBoxOptions = useMemo(() => { const fields = Object.values(sourcererDataView.fields || {}); @@ -130,46 +163,12 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) const { index: indexField } = getFields(); const newIndexValue = [...selectedPatterns].sort(); const indexFieldValue = (indexField.value as string[]).sort(); + if (!isEqual(indexFieldValue, newIndexValue)) { indexField.setValue(newIndexValue); } }, [getFields, selectedPatterns]); - useEffect(() => { - const { eqlQueryBar } = getFields(); - if (isInit.current) { - isInit.current = false; - setIsQueryBarValidating(true); - eqlQueryBar.setValue({ - ...defaultValues.eqlQueryBar, - query: { query: optionsSelected.query ?? '', language: 'eql' }, - }); - } - return () => { - isInit.current = true; - }; - }, [getFields, optionsSelected.query]); - - useEffect(() => { - if ( - formEqlQueryBar != null && - prevEqlQuery.current !== formEqlQueryBar.query.query && - isQueryBarValid && - !isQueryBarValidating - ) { - prevEqlQuery.current = formEqlQueryBar.query.query; - dispatch( - timelineActions.updateEqlOptions({ - id: timelineId, - field: 'query', - value: `${formEqlQueryBar.query.query}`, - }) - ); - setIsQueryBarValid(false); - setIsQueryBarValidating(false); - } - }, [dispatch, formEqlQueryBar, isQueryBarValid, isQueryBarValidating, timelineId]); - /* Force casting `sourcererDataView` to `DataViewBase` is required since EqlQueryEdit accepts DataViewBase but `useSourcererDataView()` returns `DataViewSpec`. @@ -178,20 +177,18 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) return (
- - + + + ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx index a8f287ccb6a5d..eccaf88591983 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx @@ -13,7 +13,7 @@ export const getEqlOptions = () => selectTimeline, (timeline) => timeline?.eqlOptions ?? { - eventCategoryField: [{ label: 'event.category' }], + eventCategoryField: 'event.category', timestampField: [ { label: '@timestamp', diff --git a/x-pack/plugins/security_solution/public/timelines/store/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/actions.ts index 976d35c030651..78f74ef4670e7 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/actions.ts @@ -188,7 +188,7 @@ export const toggleModalSaveTimeline = actionCreator<{ export const updateEqlOptions = actionCreator<{ id: string; field: FieldsEqlOptions; - value: string | undefined; + value: string | number | undefined; }>('UPDATE_EQL_OPTIONS_TIMELINE'); export const setEventsLoading = actionCreator<{ From dd8dd2b98d264e9a5ed72726a907c6a695fb80d2 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Mon, 18 Nov 2024 14:34:12 +0100 Subject: [PATCH 19/28] prevent dispatching unchanged EQL Options --- .../timeline/query_bar/eql/index.tsx | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index 9bf3e4cb9fd20..51e2dd2d368d6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -85,27 +85,31 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) return; } - dispatch( - timelineActions.updateEqlOptions({ - id: timelineId, - field: 'query', - value: `${formData.eqlQueryBar.query.query}`, - }) - ); - - for (const fieldName of Object.keys(formData.eqlOptions) as Array< - keyof typeof formData.eqlOptions - >) { + if (eqlOptions.query !== `${formData.eqlQueryBar.query.query}`) { dispatch( timelineActions.updateEqlOptions({ id: timelineId, - field: fieldName, - value: formData.eqlOptions[fieldName], + field: 'query', + value: `${formData.eqlQueryBar.query.query}`, }) ); } + + for (const fieldName of Object.keys(formData.eqlOptions) as Array< + keyof typeof formData.eqlOptions + >) { + if (formData.eqlOptions[fieldName] !== eqlOptions[fieldName]) { + dispatch( + timelineActions.updateEqlOptions({ + id: timelineId, + field: fieldName, + value: formData.eqlOptions[fieldName], + }) + ); + } + } }, - [dispatch, timelineId] + [dispatch, timelineId, eqlOptions] ); const { form } = useForm({ From 2499e50d1c1c839956858e48a454a176f482d85a Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 21 Nov 2024 16:15:04 +0100 Subject: [PATCH 20/28] skip EQL validation for prebuilt rules customization workflow --- .../eql_query_edit/eql_query_edit.tsx | 57 ++++++++++++------- .../eql_query_validator_factory.ts | 24 ++++---- .../eql_query/eql_query_edit_adapter.tsx | 2 + 3 files changed, 48 insertions(+), 35 deletions(-) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/{validators => components/eql_query_edit}/eql_query_validator_factory.ts (75%) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx index 5bd8d9afbc70b..994c9d0f6b007 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx @@ -7,12 +7,12 @@ import React, { useMemo } from 'react'; import type { DataViewBase } from '@kbn/es-query'; -import type { FieldConfig } from '../../../../shared_imports'; +import type { FormData, FieldConfig, ValidationFuncArg } from '../../../../shared_imports'; import { UseField, UseMultiFields } from '../../../../shared_imports'; import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; import { queryRequiredValidatorFactory } from '../../validators/query_required_validator_factory'; import { debounceAsync } from '../../validators/debounce_async'; -import { eqlQueryValidatorFactory } from '../../validators/eql_query_validator_factory'; +import { eqlQueryValidatorFactory } from './eql_query_validator_factory'; import { EqlQueryBar } from './eql_query_bar'; import * as i18n from './translations'; import type { FieldValueQueryBar } from '../query_bar'; @@ -28,6 +28,8 @@ interface EqlQueryEditProps { required?: boolean; loading?: boolean; disabled?: boolean; + // This is a temporal solution for Prebuilt Customization workflow + skipEqlValidation?: boolean; onValidityChange?: (arg: boolean) => void; } @@ -42,6 +44,7 @@ export function EqlQueryEdit({ required, loading, disabled, + skipEqlValidation, onValidityChange, }: EqlQueryEditProps): JSX.Element { const componentProps = useMemo( @@ -80,28 +83,40 @@ export function EqlQueryEdit({ }, ] : []), - { - validator: debounceAsync((...args) => { - const [{ formData }] = args; - const eqlOptions = - eqlOptionsPath && formData[eqlOptionsPath] ? formData[eqlOptionsPath] : {}; + ...(!skipEqlValidation + ? [ + { + validator: debounceAsync((...args) => { + const [{ formData }] = args as [ValidationFuncArg]; + const eqlOptions = + eqlOptionsPath && formData[eqlOptionsPath] ? formData[eqlOptionsPath] : {}; - return eqlQueryValidatorFactory( - dataView.id - ? { - dataViewId: dataView.id, - eqlOptions, - } - : { - indexPatterns: dataView.title.split(','), - eqlOptions, - } - )(...args); - }, 300), - }, + return eqlQueryValidatorFactory( + dataView.id + ? { + dataViewId: dataView.id, + eqlOptions, + } + : { + indexPatterns: dataView.title.split(','), + eqlOptions, + } + )(...args); + }, 300), + }, + ] + : []), ], }), - [eqlOptionsPath, required, dataView.id, dataView.title, path, fieldsToValidateOnChange] + [ + skipEqlValidation, + eqlOptionsPath, + required, + dataView.id, + dataView.title, + path, + fieldsToValidateOnChange, + ] ); if (eqlOptionsPath) { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_validator_factory.ts similarity index 75% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_validator_factory.ts index 08a68039e035e..a8e2521b28c4b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/eql_query_validator_factory.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_validator_factory.ts @@ -6,14 +6,13 @@ */ import { isEmpty } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import type { FormData, ValidationError, ValidationFunc } from '../../../shared_imports'; -import { KibanaServices } from '../../../common/lib/kibana'; -import type { EqlOptions } from '../../../../common/search_strategy'; -import type { FieldValueQueryBar } from '../components/query_bar'; -import type { EqlResponseError } from '../../../common/hooks/eql/api'; -import { EQL_ERROR_CODES, validateEql } from '../../../common/hooks/eql/api'; -import { isDataViewIdValid } from './data_view_id_validator_factory'; +import type { FormData, ValidationError, ValidationFunc } from '../../../../shared_imports'; +import { KibanaServices } from '../../../../common/lib/kibana'; +import type { EqlOptions } from '../../../../../common/search_strategy'; +import type { FieldValueQueryBar } from '../query_bar'; +import type { EqlResponseError } from '../../../../common/hooks/eql/api'; +import { EQL_ERROR_CODES, validateEql } from '../../../../common/hooks/eql/api'; +import { EQL_VALIDATION_REQUEST_ERROR } from './translations'; type EqlQueryValidatorFactoryParams = | { @@ -87,9 +86,6 @@ function transformEqlResponseErrorToValidationError( }; } -const EQL_VALIDATION_REQUEST_ERROR = i18n.translate( - 'xpack.securitySolution.ruleManagement.eqlValidation.requestError', - { - defaultMessage: 'An error occurred while validating your EQL query', - } -); +function isDataViewIdValid(dataViewId: unknown): dataViewId is string { + return typeof dataViewId === 'string' && dataViewId !== ''; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx index 9b4139fd1a00c..59d2dc40707d4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx @@ -24,10 +24,12 @@ export function EqlQueryEditAdapter({ return ( ); } From 557782abfccb823926bcc349f9cb2fad5380e105 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 21 Nov 2024 16:16:12 +0100 Subject: [PATCH 21/28] combine EQL query and EQL options --- .../diffable_rule/diffable_field_types.ts | 6 ++ .../model/diff/diffable_rule/diffable_rule.ts | 6 -- .../diff/convert_rule_to_diffable.ts | 12 ++-- .../diff/extract_rule_data_query.ts | 27 +++++--- .../components/rule_details/constants.ts | 3 - .../per_field_rule_diff_tab.test.tsx | 37 +++++------ .../final_edit/eql_rule_field_edit.tsx | 9 --- .../fields/eql_query/eql_query_edit_form.tsx | 12 +++- .../evt_cat_field_edit_adapter.tsx | 42 ------------- .../evt_cat_field_edit_form.tsx | 61 ------------------- .../fields/event_category_override/index.ts | 8 --- .../event_category_override/translations.ts | 23 ------- .../fields/tiebreaker_field/index.ts | 8 --- .../tiebreaker_field_edit_adapter.tsx | 42 ------------- .../tiebreaker_field_edit_form.tsx | 59 ------------------ .../fields/tiebreaker_field/translations.ts | 23 ------- .../fields/timestamp_field/index.ts | 8 --- .../timestamp_field_edit_adapter.tsx | 38 ------------ .../timestamp_field_edit_form.tsx | 59 ------------------ .../fields/timestamp_field/translations.ts | 22 ------- .../eql_rule_field_readonly.tsx | 13 ---- .../fields/eql_query/eql_query.tsx | 29 +++++++-- .../event_category_override.stories.tsx | 40 ------------ .../event_category_override.tsx | 42 ------------- .../tiebreaker_field.stories.tsx | 40 ------------ .../tiebreaker_field/tiebreaker_field.tsx | 40 ------------ .../timestamp_field.stories.tsx | 39 ------------ .../timestamp_field/timestamp_field.tsx | 40 ------------ .../use_prebuilt_rules_upgrade_state.ts | 3 +- .../calculation/calculate_rule_fields_diff.ts | 3 - 30 files changed, 86 insertions(+), 708 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_adapter.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_form.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/index.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_adapter.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_form.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/index.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_adapter.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_form.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.stories.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.stories.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.stories.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.tsx diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_field_types.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_field_types.ts index 5138e94b78db2..6262fd0e579e7 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_field_types.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_field_types.ts @@ -9,14 +9,17 @@ import { z } from '@kbn/zod'; import { BuildingBlockType, DataViewId, + EventCategoryOverride, IndexPatternArray, KqlQueryLanguage, RuleFilterArray, RuleNameOverride, RuleQuery, SavedQueryId, + TiebreakerField, TimelineTemplateId, TimelineTemplateTitle, + TimestampField, TimestampOverride, TimestampOverrideFallbackDisabled, } from '../../../../model/rule_schema'; @@ -78,6 +81,9 @@ export const RuleEqlQuery = z.object({ query: RuleQuery, language: z.literal('eql'), filters: RuleFilterArray, + event_category_override: EventCategoryOverride.optional(), + timestamp_field: TimestampField.optional(), + tiebreaker_field: TiebreakerField.optional(), }); export type RuleEsqlQuery = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts index 374c6ff492e8d..38331d3a01c62 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts @@ -9,7 +9,6 @@ import { z } from '@kbn/zod'; import { AlertSuppression, AnomalyThreshold, - EventCategoryOverride, HistoryWindowStart, InvestigationFields, InvestigationGuide, @@ -37,8 +36,6 @@ import { ThreatMapping, Threshold, ThresholdAlertSuppression, - TiebreakerField, - TimestampField, } from '../../../../model/rule_schema'; import { @@ -113,9 +110,6 @@ export const DiffableEqlFields = z.object({ type: z.literal('eql'), eql_query: RuleEqlQuery, // NOTE: new field data_source: RuleDataSource.optional(), // NOTE: new field - event_category_override: EventCategoryOverride.optional(), - timestamp_field: TimestampField.optional(), - tiebreaker_field: TiebreakerField.optional(), alert_suppression: AlertSuppression.optional(), }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts index 882dcae3e36aa..95ceb5c718825 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts @@ -175,11 +175,15 @@ const extractDiffableEqlFieldsFromRuleObject = ( ): RequiredOptional => { return { type: rule.type, - eql_query: extractRuleEqlQuery(rule.query, rule.language, rule.filters), + eql_query: extractRuleEqlQuery({ + query: rule.query, + language: rule.language, + filters: rule.filters, + eventCategoryOverride: rule.event_category_override, + timestampField: rule.timestamp_field, + tiebreakerField: rule.tiebreaker_field, + }), data_source: extractRuleDataSource(rule.index, rule.data_view_id), - event_category_override: rule.event_category_override, - timestamp_field: rule.timestamp_field, - tiebreaker_field: rule.tiebreaker_field, alert_suppression: rule.alert_suppression, }; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts index 4f4164c6a0086..99bb27b99357f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts @@ -8,9 +8,12 @@ import type { EqlQueryLanguage, EsqlQueryLanguage, + EventCategoryOverride, KqlQueryLanguage, RuleFilterArray, RuleQuery, + TiebreakerField, + TimestampField, } from '../../../api/detection_engine/model/rule_schema'; import type { InlineKqlQuery, @@ -49,15 +52,23 @@ export const extractInlineKqlQuery = ( }; }; -export const extractRuleEqlQuery = ( - query: RuleQuery, - language: EqlQueryLanguage, - filters: RuleFilterArray | undefined -): RuleEqlQuery => { +interface ExtractRuleEqlQueryParams { + query: RuleQuery; + language: EqlQueryLanguage; + filters: RuleFilterArray | undefined; + eventCategoryOverride: EventCategoryOverride | undefined; + timestampField: TimestampField | undefined; + tiebreakerField: TiebreakerField | undefined; +} + +export const extractRuleEqlQuery = (params: ExtractRuleEqlQueryParams): RuleEqlQuery => { return { - query, - language, - filters: filters ?? [], + query: params.query, + language: params.language, + filters: params.filters ?? [], + event_category_override: params.eventCategoryOverride, + timestamp_field: params.timestampField, + tiebreaker_field: params.tiebreakerField, }; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts index 04660191c9cbf..8e159c94aca0e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts @@ -34,9 +34,6 @@ export const DEFINITION_UPGRADE_FIELD_ORDER: Array = [ 'type', 'kql_query', 'eql_query', - 'event_category_override', - 'timestamp_field', - 'tiebreaker_field', 'esql_query', 'anomaly_threshold', 'machine_learning_job_id', diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx index 5e417b3d862ed..701ab18e9a0ab 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx @@ -92,16 +92,16 @@ describe('PerFieldRuleDiffTab', () => { }); describe('Undefined values are displayed with empty diffs', () => { - test('Displays only an updated field value when changed from undefined', () => { + test('Displays only an updated field value when changed from an empty value', () => { const mockData: PartialRuleDiff = { ...ruleFieldsDiffMock, fields: { - timestamp_field: { + name: { ...ruleFieldsDiffBaseFieldsMock, base_version: undefined, - current_version: undefined, - merged_version: 'new timestamp field', - target_version: 'new timestamp field', + current_version: '', + merged_version: 'new name', + target_version: 'new name', }, }, }; @@ -109,19 +109,19 @@ describe('PerFieldRuleDiffTab', () => { const diffContent = wrapper.getByTestId('ruleUpgradePerFieldDiffContent').textContent; // Only the new timestamp field should be displayed - expect(diffContent).toEqual('+new timestamp field'); + expect(diffContent).toEqual('+new name'); }); - test('Displays only an outdated field value when incoming update is undefined', () => { + test('Displays only an outdated field value when incoming update is an empty value', () => { const mockData: PartialRuleDiff = { ...ruleFieldsDiffMock, fields: { - timestamp_field: { + name: { ...ruleFieldsDiffBaseFieldsMock, - base_version: 'old timestamp field', - current_version: 'old timestamp field', - merged_version: undefined, - target_version: undefined, + base_version: 'old name', + current_version: 'old name', + merged_version: '', + target_version: '', }, }, }; @@ -129,7 +129,7 @@ describe('PerFieldRuleDiffTab', () => { const diffContent = wrapper.getByTestId('ruleUpgradePerFieldDiffContent').textContent; // Only the old timestamp_field should be displayed - expect(diffContent).toEqual('-old timestamp field'); + expect(diffContent).toEqual('-old name'); }); }); @@ -144,13 +144,6 @@ describe('PerFieldRuleDiffTab', () => { merged_version: 'new setup', target_version: 'new setup', }, - timestamp_field: { - ...ruleFieldsDiffBaseFieldsMock, - base_version: undefined, - current_version: undefined, - merged_version: 'new timestamp', - target_version: 'new timestamp', - }, name: { ...ruleFieldsDiffBaseFieldsMock, base_version: 'old name', @@ -166,11 +159,11 @@ describe('PerFieldRuleDiffTab', () => { const sectionLabels = matchedSectionElements.map((element) => element.textContent); // Schedule doesn't have any fields in the diff and shouldn't be displayed - expect(sectionLabels).toEqual(['About', 'Definition', 'Setup guide']); + expect(sectionLabels).toEqual(['About', 'Setup guide']); const matchedFieldElements = wrapper.queryAllByTestId('ruleUpgradePerFieldDiffLabel'); const fieldLabels = matchedFieldElements.map((element) => element.textContent); - expect(fieldLabels).toEqual(['Name', 'Timestamp Field', 'Setup']); + expect(fieldLabels).toEqual(['Name', 'Setup']); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx index b08c9bd7f5732..29fcfdf7d522e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx @@ -11,9 +11,6 @@ import type { UpgradeableEqlFields } from '../../../../model/prebuilt_rule_upgra import { DataSourceEditForm } from './fields/data_source'; import { AlertSuppressionEditForm } from './fields/alert_suppression'; import { EqlQueryEditForm } from './fields/eql_query'; -import { EventCategoryOverrideEditForm } from './fields/event_category_override'; -import { TimestampFieldEditForm } from './fields/timestamp_field'; -import { TiebreakerFieldEditForm } from './fields/tiebreaker_field'; interface EqlRuleFieldEditProps { fieldName: UpgradeableEqlFields; @@ -27,12 +24,6 @@ export function EqlRuleFieldEdit({ fieldName }: EqlRuleFieldEditProps) { return ; case 'alert_suppression': return ; - case 'event_category_override': - return ; - case 'timestamp_field': - return ; - case 'tiebreaker_field': - return ; default: return assertUnreachable(fieldName); } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx index 5d63ddcf735e9..3a8312897671e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx @@ -7,6 +7,7 @@ import React from 'react'; import type { Filter } from '@kbn/es-query'; +import type { EqlOptions } from '@kbn/timelines-plugin/common'; import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; import type { FieldValueQueryBar } from '../../../../../../../rule_creation_ui/components/query_bar'; @@ -37,6 +38,7 @@ function deserializer( finalDiffableRule: DiffableRule ): { eqlQuery: FieldValueQueryBar; + eqlOptions: EqlOptions; } { const parsedEqlQuery = 'eql_query' in finalDiffableRule @@ -58,19 +60,27 @@ function deserializer( filters: parsedEqlQuery.filters as Filter[], saved_id: null, }, + eqlOptions: { + eventCategoryField: parsedEqlQuery.event_category_override, + timestampField: parsedEqlQuery.timestamp_field, + tiebreakerField: parsedEqlQuery.tiebreaker_field, + }, }; } function serializer(formData: FormData): { eql_query: RuleEqlQuery; } { - const formValue = formData as { eqlQuery: FieldValueQueryBar }; + const formValue = formData as { eqlQuery: FieldValueQueryBar; eqlOptions: EqlOptions }; return { eql_query: { query: formValue.eqlQuery.query.query as string, language: QueryLanguageEnum.eql, filters: formValue.eqlQuery.filters, + event_category_override: formValue.eqlOptions.eventCategoryField, + timestamp_field: formValue.eqlOptions.timestampField, + tiebreaker_field: formValue.eqlOptions.tiebreakerField, }, }; } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_adapter.tsx deleted file mode 100644 index 2b02f5dffb967..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_adapter.tsx +++ /dev/null @@ -1,42 +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 React, { useMemo } from 'react'; -import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import { UseField } from '../../../../../../../../shared_imports'; -import { useDiffableRuleEqlFieldsComboBoxOptions } from '../hooks/use_diffable_rule_eql_fields_combo_box_options'; -import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; -import * as i18n from './translations'; - -export function EventCategoryOverrideEditAdapter({ - finalDiffableRule, -}: RuleFieldEditComponentProps): JSX.Element { - const { keywordFields } = useDiffableRuleEqlFieldsComboBoxOptions(finalDiffableRule); - const comboBoxFieldProps = useMemo( - () => ({ - label: i18n.EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL, - helpText: i18n.EQL_OPTIONS_EVENT_CATEGORY_FIELD_HELPER, - euiFieldProps: { - fullWidth: true, - singleSelection: { asPlainText: true }, - noSuggestions: false, - placeholder: '', - onCreateOption: undefined, - options: keywordFields, - }, - }), - [keywordFields] - ); - - return ( - - ); -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_form.tsx deleted file mode 100644 index 351a25c525f22..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/evt_cat_field_edit_form.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import type { - DiffableRule, - EventCategoryOverride, -} from '../../../../../../../../../common/api/detection_engine'; -import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; -import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; -import { EventCategoryOverrideEditAdapter } from './evt_cat_field_edit_adapter'; - -export function EventCategoryOverrideEditForm(): JSX.Element { - return ( - - ); -} - -const schema = { - event_category_override: {}, -} as FormSchema<{ - event_category_override: EventCategoryOverride; -}>; - -function deserializer( - _: FormData, - finalDiffableRule: DiffableRule -): { - eventCategoryOverride: string[]; -} { - if (finalDiffableRule.type !== 'eql') { - return { - eventCategoryOverride: [], - }; - } - - return { - eventCategoryOverride: finalDiffableRule.event_category_override - ? [finalDiffableRule.event_category_override] - : [], - }; -} - -function serializer(formData: FormData): { - event_category_override?: EventCategoryOverride; -} { - const { eventCategoryOverride } = formData as { eventCategoryOverride: string[] }; - - return { - event_category_override: eventCategoryOverride.length ? eventCategoryOverride[0] : undefined, - }; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts deleted file mode 100644 index 17aeafc97fd20..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export * from './evt_cat_field_edit_form'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/translations.ts deleted file mode 100644 index 2f617604e119e..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/event_category_override/translations.ts +++ /dev/null @@ -1,23 +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 { i18n } from '@kbn/i18n'; - -export const EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL = i18n.translate( - 'xpack.securitySolution.ruleManagement.eqlOptionsEventCategoryField.label', - { - defaultMessage: 'Event category field', - } -); - -export const EQL_OPTIONS_EVENT_CATEGORY_FIELD_HELPER = i18n.translate( - 'xpack.securitySolution.ruleManagement.eqlOptionsEventCategoryField.text', - { - defaultMessage: - 'Field containing the event classification, such as process, file, or network. This field is typically mapped as a field type in the keyword family', - } -); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/index.ts deleted file mode 100644 index 7edddde1e52af..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export * from './tiebreaker_field_edit_form'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_adapter.tsx deleted file mode 100644 index f983108be1a44..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_adapter.tsx +++ /dev/null @@ -1,42 +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 React, { useMemo } from 'react'; -import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import { UseField } from '../../../../../../../../shared_imports'; -import { useDiffableRuleEqlFieldsComboBoxOptions } from '../hooks/use_diffable_rule_eql_fields_combo_box_options'; -import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; -import * as i18n from './translations'; - -export function TiebreakerFieldEditAdapter({ - finalDiffableRule, -}: RuleFieldEditComponentProps): JSX.Element { - const { nonDateFields } = useDiffableRuleEqlFieldsComboBoxOptions(finalDiffableRule); - const comboBoxFieldProps = useMemo( - () => ({ - label: i18n.EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL, - helpText: i18n.EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_HELPER, - euiFieldProps: { - fullWidth: true, - singleSelection: { asPlainText: true }, - noSuggestions: false, - placeholder: '', - onCreateOption: undefined, - options: nonDateFields, - }, - }), - [nonDateFields] - ); - - return ( - - ); -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_form.tsx deleted file mode 100644 index ef5463fa026e9..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/tiebreaker_field_edit_form.tsx +++ /dev/null @@ -1,59 +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 React from 'react'; -import type { - DiffableRule, - TiebreakerField, -} from '../../../../../../../../../common/api/detection_engine'; -import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; -import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; -import { TiebreakerFieldEditAdapter } from './tiebreaker_field_edit_adapter'; - -export function TiebreakerFieldEditForm(): JSX.Element { - return ( - - ); -} - -const schema = { - tiebreaker_field: {}, -} as FormSchema<{ - tiebreaker_field: TiebreakerField; -}>; - -function deserializer( - _: FormData, - finalDiffableRule: DiffableRule -): { - tiebreakerField: string[]; -} { - if (finalDiffableRule.type !== 'eql') { - return { - tiebreakerField: [], - }; - } - - return { - tiebreakerField: finalDiffableRule.tiebreaker_field ? [finalDiffableRule.tiebreaker_field] : [], - }; -} - -function serializer(formData: FormData): { - tiebreaker_field?: TiebreakerField; -} { - const { tiebreakerField } = formData as { tiebreakerField: string[] }; - - return { - tiebreaker_field: tiebreakerField.length ? tiebreakerField[0] : undefined, - }; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/translations.ts deleted file mode 100644 index be67222601bd6..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/tiebreaker_field/translations.ts +++ /dev/null @@ -1,23 +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 { i18n } from '@kbn/i18n'; - -export const EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL = i18n.translate( - 'xpack.securitySolution.ruleManagement.eqlOptionsEventTiebreakerField.label', - { - defaultMessage: 'Tiebreaker field', - } -); - -export const EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_HELPER = i18n.translate( - 'xpack.securitySolution.ruleManagement.eqlOptionsEventTiebreakerField.text', - { - defaultMessage: - 'Field used to sort hits with the same timestamp in ascending, lexicographic order', - } -); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/index.ts deleted file mode 100644 index 1f2edea910118..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export * from './timestamp_field_edit_form'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_adapter.tsx deleted file mode 100644 index 8b2264a184515..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_adapter.tsx +++ /dev/null @@ -1,38 +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 React, { useMemo } from 'react'; -import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import { UseField } from '../../../../../../../../shared_imports'; -import { useDiffableRuleEqlFieldsComboBoxOptions } from '../hooks/use_diffable_rule_eql_fields_combo_box_options'; -import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; -import * as i18n from './translations'; - -export function TimestampFieldEditAdapter({ - finalDiffableRule, -}: RuleFieldEditComponentProps): JSX.Element { - const { dateFields } = useDiffableRuleEqlFieldsComboBoxOptions(finalDiffableRule); - const comboBoxFieldProps = useMemo( - () => ({ - label: i18n.EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_LABEL, - helpText: i18n.EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_HELPER, - euiFieldProps: { - fullWidth: true, - singleSelection: { asPlainText: true }, - noSuggestions: false, - placeholder: '', - onCreateOption: undefined, - options: dateFields, - }, - }), - [dateFields] - ); - - return ( - - ); -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_form.tsx deleted file mode 100644 index edf1d021e5492..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/timestamp_field_edit_form.tsx +++ /dev/null @@ -1,59 +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 React from 'react'; -import type { - DiffableRule, - TimestampField, -} from '../../../../../../../../../common/api/detection_engine'; -import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; -import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; -import { TimestampFieldEditAdapter } from './timestamp_field_edit_adapter'; - -export function TimestampFieldEditForm(): JSX.Element { - return ( - - ); -} - -const schema = { - timestamp_field: {}, -} as FormSchema<{ - timestamp_field: TimestampField; -}>; - -function deserializer( - _: FormData, - finalDiffableRule: DiffableRule -): { - timestampField: string[]; -} { - if (finalDiffableRule.type !== 'eql') { - return { - timestampField: [], - }; - } - - return { - timestampField: finalDiffableRule.timestamp_field ? [finalDiffableRule.timestamp_field] : [], - }; -} - -function serializer(formData: FormData): { - timestamp_field?: TimestampField; -} { - const { timestampField } = formData as { timestampField: string[] }; - - return { - timestamp_field: timestampField.length ? timestampField[0] : undefined, - }; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/translations.ts deleted file mode 100644 index 426874a8f291d..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/timestamp_field/translations.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_LABEL = i18n.translate( - 'xpack.securitySolution.ruleManagement.eqlOptionsEventTimestampField.label', - { - defaultMessage: 'Timestamp field', - } -); - -export const EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_HELPER = i18n.translate( - 'xpack.securitySolution.ruleManagement.eqlOptionsEventTimestampField.text', - { - defaultMessage: 'Field containing event timestamp', - } -); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/eql_rule_field_readonly.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/eql_rule_field_readonly.tsx index b72fce91f198c..5fbd9516e78ba 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/eql_rule_field_readonly.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/eql_rule_field_readonly.tsx @@ -12,9 +12,6 @@ import { EqlQueryReadOnly } from './fields/eql_query/eql_query'; import { TypeReadOnly } from './fields/type/type'; import { AlertSuppressionReadOnly } from './fields/alert_suppression/alert_suppression'; import { assertUnreachable } from '../../../../../../../common/utility_types'; -import { EventCategoryOverrideReadOnly } from './fields/event_category_override/event_category_override'; -import { TimestampFieldReadOnly } from './fields/timestamp_field/timestamp_field'; -import { TiebreakerFieldReadOnly } from './fields/tiebreaker_field/tiebreaker_field'; interface EqlRuleFieldReadOnlyProps { fieldName: keyof DiffableEqlFields; @@ -39,16 +36,6 @@ export function EqlRuleFieldReadOnly({ fieldName, finalDiffableRule }: EqlRuleFi dataSource={finalDiffableRule.data_source} /> ); - case 'event_category_override': - return ( - - ); - case 'tiebreaker_field': - return ; - case 'timestamp_field': - return ; case 'type': return ; default: diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/eql_query/eql_query.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/eql_query/eql_query.tsx index f94f0bbfbe6c8..f2c49507b8ad5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/eql_query/eql_query.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/eql_query/eql_query.tsx @@ -6,11 +6,11 @@ */ import React from 'react'; -import { EuiDescriptionList } from '@elastic/eui'; +import { EuiDescriptionList, EuiText } from '@elastic/eui'; import type { EuiDescriptionListProps } from '@elastic/eui'; -import type { - RuleDataSource, - RuleEqlQuery, +import { + type RuleDataSource, + type RuleEqlQuery, } from '../../../../../../../../../common/api/detection_engine'; import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations'; import { Query, Filters } from '../../../../rule_definition_section'; @@ -38,5 +38,26 @@ export function EqlQueryReadOnly({ eqlQuery, dataSource }: EqlQueryReadOnlyProps }); } + if (eqlQuery.event_category_override) { + listItems.push({ + title: descriptionStepI18n.EQL_EVENT_CATEGORY_FIELD_LABEL, + description: {eqlQuery.event_category_override}, + }); + } + + if (eqlQuery.tiebreaker_field) { + listItems.push({ + title: descriptionStepI18n.EQL_TIEBREAKER_FIELD_LABEL, + description: {eqlQuery.tiebreaker_field}, + }); + } + + if (eqlQuery.timestamp_field) { + listItems.push({ + title: descriptionStepI18n.EQL_TIMESTAMP_FIELD_LABEL, + description: {eqlQuery.timestamp_field}, + }); + } + return ; } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.stories.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.stories.tsx deleted file mode 100644 index 1f5987287f665..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.stories.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import type { Story } from '@storybook/react'; -import { EventCategoryOverrideReadOnly } from './event_category_override'; -import { FieldReadOnly } from '../../field_readonly'; -import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; -import { mockEqlRule } from '../../storybook/mocks'; -import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers'; - -export default { - component: EventCategoryOverrideReadOnly, - title: - 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/event_category_override', -}; - -interface TemplateProps { - finalDiffableRule: DiffableRule; -} - -const Template: Story = (args) => { - return ( - - - - ); -}; - -export const Default = Template.bind({}); - -Default.args = { - finalDiffableRule: mockEqlRule({ - event_category_override: 'event.action', - }), -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.tsx deleted file mode 100644 index 910e639049f96..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.tsx +++ /dev/null @@ -1,42 +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 React from 'react'; -import { EuiDescriptionList, EuiText } from '@elastic/eui'; -import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations'; -import type { EventCategoryOverride as EventCategoryOverrideType } from '../../../../../../../../../common/api/detection_engine'; - -interface EventCategoryOverrideReadOnlyProps { - eventCategoryOverride?: EventCategoryOverrideType; -} - -export function EventCategoryOverrideReadOnly({ - eventCategoryOverride, -}: EventCategoryOverrideReadOnlyProps) { - if (!eventCategoryOverride) { - return null; - } - - return ( - , - }, - ]} - /> - ); -} - -interface EventCategoryOverrideProps { - eventCategoryOverride: EventCategoryOverrideType; -} - -function EventCategoryOverride({ eventCategoryOverride }: EventCategoryOverrideProps) { - return {eventCategoryOverride}; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.stories.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.stories.tsx deleted file mode 100644 index 3e73afda8f4eb..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.stories.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import type { Story } from '@storybook/react'; -import { TiebreakerFieldReadOnly } from './tiebreaker_field'; -import { FieldReadOnly } from '../../field_readonly'; -import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; -import { mockEqlRule } from '../../storybook/mocks'; -import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers'; - -export default { - component: TiebreakerFieldReadOnly, - title: - 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/tiebreaker_field', -}; - -interface TemplateProps { - finalDiffableRule: DiffableRule; -} - -const Template: Story = (args) => { - return ( - - - - ); -}; - -export const Default = Template.bind({}); - -Default.args = { - finalDiffableRule: mockEqlRule({ - tiebreaker_field: 'process.name', - }), -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.tsx deleted file mode 100644 index 10e52240748c7..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiDescriptionList, EuiText } from '@elastic/eui'; -import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations'; -import type { TiebreakerField as TiebreakerFieldType } from '../../../../../../../../../common/api/detection_engine'; - -interface TiebreakerFieldReadOnlyProps { - tiebreakerField?: TiebreakerFieldType; -} - -export function TiebreakerFieldReadOnly({ tiebreakerField }: TiebreakerFieldReadOnlyProps) { - if (!tiebreakerField) { - return null; - } - - return ( - , - }, - ]} - /> - ); -} - -interface TiebreakerFieldProps { - tiebreakerField: TiebreakerFieldType; -} - -function TiebreakerField({ tiebreakerField }: TiebreakerFieldProps) { - return {tiebreakerField}; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.stories.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.stories.tsx deleted file mode 100644 index 9b3977c3deeb2..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.stories.tsx +++ /dev/null @@ -1,39 +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 React from 'react'; -import type { Story } from '@storybook/react'; -import { TimestampFieldReadOnly } from './timestamp_field'; -import { FieldReadOnly } from '../../field_readonly'; -import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; -import { mockEqlRule } from '../../storybook/mocks'; -import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers'; - -export default { - component: TimestampFieldReadOnly, - title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/timestamp_field', -}; - -interface TemplateProps { - finalDiffableRule: DiffableRule; -} - -const Template: Story = (args) => { - return ( - - - - ); -}; - -export const Default = Template.bind({}); - -Default.args = { - finalDiffableRule: mockEqlRule({ - timestamp_field: 'event.created', - }), -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.tsx deleted file mode 100644 index cd27bfde3db60..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiDescriptionList, EuiText } from '@elastic/eui'; -import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations'; -import type { TimestampField as TimestampFieldType } from '../../../../../../../../../common/api/detection_engine'; - -interface TimestampFieldReadOnlyProps { - timestampField?: TimestampFieldType; -} - -export function TimestampFieldReadOnly({ timestampField }: TimestampFieldReadOnlyProps) { - if (!timestampField) { - return null; - } - - return ( - , - }, - ]} - /> - ); -} - -interface TimestampFieldProps { - timestampField: TimestampFieldType; -} - -function TimestampField({ timestampField }: TimestampFieldProps) { - return {timestampField}; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts index b50ca7da1849d..906f08c907510 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts @@ -143,7 +143,8 @@ function getUnacceptedConflictsCount( ): number { const fieldNames = Object.keys(ruleFieldsDiff); const fieldNamesWithConflict = fieldNames.filter( - (fieldName) => ruleFieldsDiff[fieldName].conflict !== ThreeWayDiffConflict.NONE + (fieldName) => + ruleFieldsDiff[fieldName].conflict !== ThreeWayDiffConflict.NONE && fieldName !== 'version' ); const fieldNamesWithConflictSet = new Set(fieldNamesWithConflict); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts index bde52596667d2..5730af03789d1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts @@ -239,9 +239,6 @@ const eqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { type: ruleTypeDiffAlgorithm, eql_query: eqlQueryDiffAlgorithm, data_source: dataSourceDiffAlgorithm, - event_category_override: singleLineStringDiffAlgorithm, - timestamp_field: singleLineStringDiffAlgorithm, - tiebreaker_field: singleLineStringDiffAlgorithm, alert_suppression: simpleDiffAlgorithm, }; From 58b2f35e9413ae73ae501c7310fb7570fd515c9c Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 21 Nov 2024 16:35:33 +0100 Subject: [PATCH 22/28] show EQL options in rule details page --- .../rule_details/rule_definition_section.tsx | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx index 623ae20fa484f..197b59a229271 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx @@ -27,6 +27,7 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import { FilterItems } from '@kbn/unified-search-plugin/public'; import type { AlertSuppressionMissingFieldsStrategy, + EqlOptionalFields, RequiredFieldArray, RuleResponse, Threshold as ThresholdType, @@ -59,6 +60,11 @@ import { } from './rule_definition_section.styles'; import { getQueryLanguageLabel } from './helpers'; import { useDefaultIndexPattern } from '../../hooks/use_default_index_pattern'; +import { + EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL, + EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL, + EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_LABEL, +} from '../../../rule_creation_ui/components/eql_query_edit/translations'; interface SavedQueryNameProps { savedQueryName: string; @@ -562,6 +568,51 @@ const prepareDefinitionSectionListItems = ( } } + if ((rule as EqlOptionalFields).event_category_override) { + definitionSectionListItems.push({ + title: ( + + {EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL} + + ), + description: ( + + {(rule as EqlOptionalFields).event_category_override} + + ), + }); + } + + if ((rule as EqlOptionalFields).tiebreaker_field) { + definitionSectionListItems.push({ + title: ( + + {EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL} + + ), + description: ( + + {(rule as EqlOptionalFields).tiebreaker_field} + + ), + }); + } + + if ((rule as EqlOptionalFields).timestamp_field) { + definitionSectionListItems.push({ + title: ( + + {EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_LABEL} + + ), + description: ( + + {(rule as EqlOptionalFields).timestamp_field} + + ), + }); + } + if (rule.type) { definitionSectionListItems.push({ title: i18n.RULE_TYPE_FIELD_LABEL, From 3f7c96f27f03e80eba08e659f942a56188ee884c Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 21 Nov 2024 16:39:39 +0100 Subject: [PATCH 23/28] move eql edit component in a shared folder --- .../components/eql_query_edit/eql_overview_link.tsx | 0 .../components/eql_query_edit/eql_query_bar.test.tsx | 0 .../components/eql_query_edit/eql_query_bar.tsx | 2 +- .../components/eql_query_edit/eql_query_edit.tsx | 6 +++--- .../eql_query_edit/eql_query_validator_factory.ts | 2 +- .../components/eql_query_edit/errors_popover.test.tsx | 0 .../components/eql_query_edit/errors_popover.tsx | 0 .../components/eql_query_edit/footer.test.tsx | 0 .../components/eql_query_edit/footer.tsx | 0 .../components/eql_query_edit/index.ts | 0 .../components/eql_query_edit/translations.ts | 0 .../components/eql_query_edit/validators.mock.ts | 0 .../components/eql_query_edit/validators.ts | 0 .../rule_creation_ui/components/step_define_rule/index.tsx | 2 +- .../components/rule_details/rule_definition_section.tsx | 2 +- .../final_edit/fields/eql_query/eql_query_edit_adapter.tsx | 2 +- .../timelines/components/timeline/query_bar/eql/index.tsx | 2 +- 17 files changed, 9 insertions(+), 9 deletions(-) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/eql_overview_link.tsx (100%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/eql_query_bar.test.tsx (100%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/eql_query_bar.tsx (98%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/eql_query_edit.tsx (93%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/eql_query_validator_factory.ts (96%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/errors_popover.test.tsx (100%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/errors_popover.tsx (100%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/footer.test.tsx (100%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/footer.tsx (100%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/index.ts (100%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/translations.ts (100%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/validators.mock.ts (100%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui => rule_creation}/components/eql_query_edit/validators.ts (100%) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_overview_link.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_overview_link.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_overview_link.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_overview_link.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.test.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx index 603224e22a9b3..b0963396348bb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_bar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx @@ -19,7 +19,7 @@ import { FilterBar } from '../../../../common/components/filter_bar'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; import { useKibana } from '../../../../common/lib/kibana'; -import type { FieldValueQueryBar } from '../query_bar'; +import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar'; import type { EqlQueryBarFooterProps } from './footer'; import { EqlQueryBarFooter } from './footer'; import { getValidationResults } from './validators'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx index 994c9d0f6b007..1a22131e6a2ae 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx @@ -10,12 +10,12 @@ import type { DataViewBase } from '@kbn/es-query'; import type { FormData, FieldConfig, ValidationFuncArg } from '../../../../shared_imports'; import { UseField, UseMultiFields } from '../../../../shared_imports'; import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; -import { queryRequiredValidatorFactory } from '../../validators/query_required_validator_factory'; -import { debounceAsync } from '../../validators/debounce_async'; +import { queryRequiredValidatorFactory } from '../../../rule_creation_ui/validators/query_required_validator_factory'; +import { debounceAsync } from '../../../rule_creation_ui/validators/debounce_async'; import { eqlQueryValidatorFactory } from './eql_query_validator_factory'; import { EqlQueryBar } from './eql_query_bar'; import * as i18n from './translations'; -import type { FieldValueQueryBar } from '../query_bar'; +import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar'; interface EqlQueryEditProps { path: string; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_validator_factory.ts similarity index 96% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_validator_factory.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_validator_factory.ts index a8e2521b28c4b..54a0b3e3b6a65 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/eql_query_validator_factory.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_validator_factory.ts @@ -9,7 +9,7 @@ import { isEmpty } from 'lodash'; import type { FormData, ValidationError, ValidationFunc } from '../../../../shared_imports'; import { KibanaServices } from '../../../../common/lib/kibana'; import type { EqlOptions } from '../../../../../common/search_strategy'; -import type { FieldValueQueryBar } from '../query_bar'; +import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar'; import type { EqlResponseError } from '../../../../common/hooks/eql/api'; import { EQL_ERROR_CODES, validateEql } from '../../../../common/hooks/eql/api'; import { EQL_VALIDATION_REQUEST_ERROR } from './translations'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/errors_popover.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/errors_popover.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/errors_popover.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/errors_popover.test.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/errors_popover.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/errors_popover.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/errors_popover.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/errors_popover.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.test.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/footer.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/index.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/index.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/index.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/translations.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/validators.mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.mock.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/validators.mock.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.mock.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/validators.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_edit/validators.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx index 52259ef5e3d80..e7c0b1da21ead 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx @@ -73,7 +73,7 @@ import { isEqlSequenceQuery, isSuppressionRuleInGA, } from '../../../../../common/detection_engine/utils'; -import { EqlQueryEdit } from '../eql_query_edit'; +import { EqlQueryEdit } from '../../../rule_creation/components/eql_query_edit'; import { DataViewSelectorField } from '../data_view_selector_field'; import { ThreatMatchInput } from '../threatmatch_input'; import { useFetchIndex } from '../../../../common/containers/source'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx index 197b59a229271..295323017f06a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx @@ -64,7 +64,7 @@ import { EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL, EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL, EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_LABEL, -} from '../../../rule_creation_ui/components/eql_query_edit/translations'; +} from '../../../rule_creation/components/eql_query_edit/translations'; interface SavedQueryNameProps { savedQueryName: string; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx index 59d2dc40707d4..787891452f1d7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx @@ -7,7 +7,7 @@ import React from 'react'; import type { DataViewBase } from '@kbn/es-query'; -import { EqlQueryEdit } from '../../../../../../../rule_creation_ui/components/eql_query_edit'; +import { EqlQueryEdit } from '../../../../../../../rule_creation/components/eql_query_edit'; import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; import { useDiffableRuleDataView } from '../hooks/use_diffable_rule_data_view'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index 51e2dd2d368d6..5306805d78582 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -16,7 +16,7 @@ import type { EqlOptions } from '../../../../../../common/search_strategy'; import { useSourcererDataView } from '../../../../../sourcerer/containers'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import { SourcererScopeName } from '../../../../../sourcerer/store/model'; -import { EqlQueryEdit } from '../../../../../detection_engine/rule_creation_ui/components/eql_query_edit'; +import { EqlQueryEdit } from '../../../../../detection_engine/rule_creation/components/eql_query_edit'; import type { FieldValueQueryBar } from '../../../../../detection_engine/rule_creation_ui/components/query_bar'; import type { FormSchema, FormSubmitHandler } from '../../../../../shared_imports'; From 0b8fd8f440b5b8cade3edb253be589ef23cecbd2 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 21 Nov 2024 16:41:44 +0100 Subject: [PATCH 24/28] make eqlOptionsPath required --- .../eql_query_edit/eql_query_edit.tsx | 51 ++++++++----------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx index 1a22131e6a2ae..8ca29c95ce90e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx @@ -8,7 +8,7 @@ import React, { useMemo } from 'react'; import type { DataViewBase } from '@kbn/es-query'; import type { FormData, FieldConfig, ValidationFuncArg } from '../../../../shared_imports'; -import { UseField, UseMultiFields } from '../../../../shared_imports'; +import { UseMultiFields } from '../../../../shared_imports'; import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; import { queryRequiredValidatorFactory } from '../../../rule_creation_ui/validators/query_required_validator_factory'; import { debounceAsync } from '../../../rule_creation_ui/validators/debounce_async'; @@ -19,7 +19,7 @@ import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/qu interface EqlQueryEditProps { path: string; - eqlOptionsPath?: string; + eqlOptionsPath: string; fieldsToValidateOnChange?: string | string[]; eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions; showEqlSizeOption?: boolean; @@ -119,35 +119,24 @@ export function EqlQueryEdit({ ] ); - if (eqlOptionsPath) { - return ( - - fields={{ - eqlQuery: { - path, - config: fieldConfig, - }, - eqlOptions: { - path: eqlOptionsPath, - }, - }} - > - {({ eqlQuery, eqlOptions }) => ( - - )} - - ); - } - return ( - + + fields={{ + eqlQuery: { + path, + config: fieldConfig, + }, + eqlOptions: { + path: eqlOptionsPath, + }, + }} + > + {({ eqlQuery, eqlOptions }) => ( + + )} + ); } From 1eedbd249fd14b4e8857ba83a650ae32a74bcbd6 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 21 Nov 2024 17:33:26 +0100 Subject: [PATCH 25/28] move EQL options combo box options calc inside EQL Query bar --- .../eql_query_edit/eql_query_bar.test.tsx | 32 ++++++++++--- .../eql_query_edit/eql_query_bar.tsx | 6 +-- .../eql_query_edit/eql_query_edit.tsx | 47 ++++++++----------- .../components/eql_query_edit/footer.test.tsx | 12 ++++- .../components/eql_query_edit/footer.tsx | 38 +++++++++++---- .../components/step_define_rule/index.tsx | 25 +--------- ...fable_rule_eql_fields_combo_box_options.ts | 39 --------------- .../use_prebuilt_rules_upgrade_state.ts | 3 +- .../timeline/query_bar/eql/index.tsx | 33 +++++-------- 9 files changed, 100 insertions(+), 135 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/hooks/use_diffable_rule_eql_fields_combo_box_options.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.test.tsx index 60d0a02b066d0..0496f08dd264c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { render, screen, fireEvent, within } from '@testing-library/react'; - +import type { SecuritySolutionDataViewBase } from '../../../../common/types'; import { mockIndexPattern, TestProviders, useFormFieldMock } from '../../../../common/mock'; import { mockQueryBar } from '../../../rule_management_ui/components/rules_table/__mocks__/mock'; import { selectEuiComboBoxOption } from '../../../../common/test/eui/combobox'; @@ -116,10 +116,29 @@ describe('EqlQueryBar', () => { }); describe('EQL options interaction', () => { - const mockOptionsData = { - keywordFields: [{ label: 'category', value: 'category' }], - dateFields: [{ label: 'timestamp', value: 'timestamp' }], - nonDateFields: [{ label: 'tiebreaker', value: 'tiebreaker' }], + const mockIndexPatternWithEqlOptionsFields: SecuritySolutionDataViewBase = { + fields: [ + { + name: 'category', + searchable: true, + type: 'keyword', + esTypes: ['keyword'], + aggregatable: true, + }, + { + name: 'timestamp', + searchable: true, + type: 'date', + aggregatable: true, + }, + { + name: 'tiebreaker', + searchable: true, + type: 'string', + aggregatable: true, + }, + ], + title: 'test-*', }; it('updates EQL options', async () => { @@ -141,8 +160,7 @@ describe('EqlQueryBar', () => { field={mockField} eqlOptionsField={mockEqlOptionsField} isLoading={false} - eqlFieldsComboBoxOptions={mockOptionsData} - indexPattern={mockIndexPattern} + indexPattern={mockIndexPatternWithEqlOptionsFields} /> ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx index b0963396348bb..079735aab3c48 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx @@ -17,7 +17,7 @@ import { FilterManager } from '@kbn/data-plugin/public'; import type { FieldHook } from '../../../../shared_imports'; import { FilterBar } from '../../../../common/components/filter_bar'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../../common/search_strategy'; import { useKibana } from '../../../../common/lib/kibana'; import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar'; import type { EqlQueryBarFooterProps } from './footer'; @@ -63,7 +63,6 @@ export interface EqlQueryBarProps { indexPattern: DataViewBase; showFilterBar?: boolean; idAria?: string; - eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions; isSizeOptionDisabled?: boolean; onValidityChange?: (arg: boolean) => void; onValidatingChange?: (arg: boolean) => void; @@ -77,7 +76,6 @@ export const EqlQueryBar: FC = ({ indexPattern, showFilterBar, idAria, - eqlFieldsComboBoxOptions, isSizeOptionDisabled, onValidityChange, onValidatingChange, @@ -202,7 +200,7 @@ export const EqlQueryBar: FC = ({ errors={errorMessages} isLoading={isValidating} isSizeOptionDisabled={isSizeOptionDisabled} - optionsData={eqlFieldsComboBoxOptions} + dataView={indexPattern} eqlOptions={eqlOptionsField?.value} onEqlOptionsChange={handleEqlOptionsChange} /> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx index 8ca29c95ce90e..f96ce06b545a2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx @@ -37,7 +37,6 @@ export function EqlQueryEdit({ path, eqlOptionsPath, fieldsToValidateOnChange, - eqlFieldsComboBoxOptions, showEqlSizeOption = false, showFilterBar = false, dataView, @@ -49,7 +48,6 @@ export function EqlQueryEdit({ }: EqlQueryEditProps): JSX.Element { const componentProps = useMemo( () => ({ - eqlFieldsComboBoxOptions, isSizeOptionDisabled: !showEqlSizeOption, isDisabled: disabled, isLoading: loading, @@ -59,15 +57,7 @@ export function EqlQueryEdit({ dataTestSubj: 'ruleEqlQueryBar', onValidityChange, }), - [ - eqlFieldsComboBoxOptions, - showEqlSizeOption, - showFilterBar, - onValidityChange, - dataView, - loading, - disabled, - ] + [showEqlSizeOption, showFilterBar, onValidityChange, dataView, loading, disabled] ); const fieldConfig: FieldConfig = useMemo( () => ({ @@ -86,23 +76,26 @@ export function EqlQueryEdit({ ...(!skipEqlValidation ? [ { - validator: debounceAsync((...args) => { - const [{ formData }] = args as [ValidationFuncArg]; - const eqlOptions = - eqlOptionsPath && formData[eqlOptionsPath] ? formData[eqlOptionsPath] : {}; + validator: debounceAsync( + (data: ValidationFuncArg) => { + const { formData } = data; + const eqlOptions = + eqlOptionsPath && formData[eqlOptionsPath] ? formData[eqlOptionsPath] : {}; - return eqlQueryValidatorFactory( - dataView.id - ? { - dataViewId: dataView.id, - eqlOptions, - } - : { - indexPatterns: dataView.title.split(','), - eqlOptions, - } - )(...args); - }, 300), + return eqlQueryValidatorFactory( + dataView.id + ? { + dataViewId: dataView.id, + eqlOptions, + } + : { + indexPatterns: dataView.title.split(','), + eqlOptions, + } + )(data); + }, + 300 + ), }, ] : []), diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.test.tsx index a1f851600eb71..ded190cc86de5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.test.tsx @@ -32,7 +32,11 @@ describe('EQL footer', () => { it('EQL settings button is enable when popover is NOT open', () => { const wrapper = mount( - + ); @@ -44,7 +48,11 @@ describe('EQL footer', () => { it('disable EQL settings button when popover is open', () => { const wrapper = mount( - + ); wrapper.find(`[data-test-subj="eql-settings-trigger"]`).first().simulate('click'); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.tsx index 688ceb03ddbe4..fd9431c8d20f5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.tsx @@ -20,10 +20,10 @@ import { import type { FC } from 'react'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; - +import type { DataViewBase } from '@kbn/es-query'; import type { DebouncedFunc } from 'lodash'; -import { debounce } from 'lodash'; -import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; +import { debounce, isEmpty } from 'lodash'; +import type { EqlOptions } from '../../../../../common/search_strategy'; import * as i18n from './translations'; import { ErrorsPopover } from './errors_popover'; import { EqlOverviewLink } from './eql_overview_link'; @@ -32,7 +32,7 @@ export interface EqlQueryBarFooterProps { errors: string[]; isLoading?: boolean; isSizeOptionDisabled?: boolean; - optionsData?: EqlFieldsComboBoxOptions; + dataView: DataViewBase; eqlOptions?: EqlOptions; onEqlOptionsChange?: ( field: Field, @@ -72,7 +72,7 @@ export const EqlQueryBarFooter: FC = ({ errors, isLoading, isSizeOptionDisabled, - optionsData, + dataView, eqlOptions, onEqlOptionsChange, }) => { @@ -80,6 +80,28 @@ export const EqlQueryBarFooter: FC = ({ const [localSize, setLocalSize] = useState(eqlOptions?.size ?? 100); const debounceSize = useRef>(); + const { keywordFields, nonDateFields, dateFields } = useMemo( + () => + isEmpty(dataView?.fields) + ? { + keywordFields: [], + dateFields: [], + nonDateFields: [], + } + : { + keywordFields: dataView.fields + .filter((f) => f.esTypes?.includes('keyword')) + .map((f) => ({ label: f.name })), + dateFields: dataView.fields + .filter((f) => f.type === 'date') + .map((f) => ({ label: f.name })), + nonDateFields: dataView.fields + .filter((f) => f.type !== 'date') + .map((f) => ({ label: f.name })), + }, + [dataView] + ); + const openEqlSettingsHandler = useCallback(() => { setIsOpenEqlSettings(true); }, []); @@ -227,7 +249,7 @@ export const EqlQueryBarFooter: FC = ({ helpText={i18n.EQL_OPTIONS_EVENT_CATEGORY_FIELD_HELPER} > = ({ helpText={i18n.EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_HELPER} > = ({ helpText={i18n.EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_HELPER} > = ({ ] ); - const optionsData = useMemo( - () => - isEmpty(indexPattern.fields) - ? { - keywordFields: [], - dateFields: [], - nonDateFields: [], - } - : { - keywordFields: (indexPattern.fields as FieldSpec[]) - .filter((f) => f.esTypes?.includes('keyword')) - .map((f) => ({ label: f.name })), - dateFields: indexPattern.fields - .filter((f) => f.type === 'date') - .map((f) => ({ label: f.name })), - nonDateFields: indexPattern.fields - .filter((f) => f.type !== 'date') - .map((f) => ({ label: f.name })), - }, - [indexPattern] - ); - const selectRuleTypeProps = useMemo( () => ({ describedByIds: ['detectionEngineStepDefineRuleType'], @@ -781,7 +759,6 @@ const StepDefineRuleComponent: FC = ({ fieldsToValidateOnChange={ALERT_SUPPRESSION_FIELDS_FIELD_NAME} required showFilterBar - eqlFieldsComboBoxOptions={optionsData} dataView={indexPattern} loading={isIndexPatternLoading} disabled={isLoading} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/hooks/use_diffable_rule_eql_fields_combo_box_options.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/hooks/use_diffable_rule_eql_fields_combo_box_options.ts deleted file mode 100644 index f6665d51706e3..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/hooks/use_diffable_rule_eql_fields_combo_box_options.ts +++ /dev/null @@ -1,39 +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 { useMemo } from 'react'; -import type { EqlFieldsComboBoxOptions } from '@kbn/timelines-plugin/common'; -import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; -import { useDiffableRuleDataView } from './use_diffable_rule_data_view'; - -export function useDiffableRuleEqlFieldsComboBoxOptions( - diffableRule: DiffableRule -): EqlFieldsComboBoxOptions { - const { dataView } = useDiffableRuleDataView(diffableRule); - - return useMemo( - () => - !dataView - ? { - keywordFields: [], - dateFields: [], - nonDateFields: [], - } - : { - keywordFields: dataView.fields - .filter((f) => f.esTypes?.includes('keyword')) - .map((f) => ({ label: f.name })), - dateFields: dataView.fields - .filter((f) => f.type === 'date') - .map((f) => ({ label: f.name })), - nonDateFields: dataView.fields - .filter((f) => f.type !== 'date') - .map((f) => ({ label: f.name })), - }, - [dataView] - ); -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts index 906f08c907510..b50ca7da1849d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts @@ -143,8 +143,7 @@ function getUnacceptedConflictsCount( ): number { const fieldNames = Object.keys(ruleFieldsDiff); const fieldNamesWithConflict = fieldNames.filter( - (fieldName) => - ruleFieldsDiff[fieldName].conflict !== ThreeWayDiffConflict.NONE && fieldName !== 'version' + (fieldName) => ruleFieldsDiff[fieldName].conflict !== ThreeWayDiffConflict.NONE ); const fieldNamesWithConflictSet = new Set(fieldNamesWithConflict); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index 5306805d78582..f925a1d83d136 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -5,12 +5,11 @@ * 2.0. */ -import { isEmpty, isEqual } from 'lodash'; +import { isEqual } from 'lodash'; import React, { memo, useCallback, useEffect, useMemo } from 'react'; import { EuiOutsideClickDetector } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; -import type { DataViewBase } from '@kbn/es-query'; import type { EqlOptions } from '../../../../../../common/search_strategy'; import { useSourcererDataView } from '../../../../../sourcerer/containers'; @@ -145,24 +144,6 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) eqlOptions.size, ]); - const eqlFieldsComboBoxOptions = useMemo(() => { - const fields = Object.values(sourcererDataView.fields || {}); - - return isEmpty(fields) - ? { - keywordFields: [], - dateFields: [], - nonDateFields: [], - } - : { - keywordFields: fields - .filter((f) => f.esTypes?.includes('keyword')) - .map((f) => ({ label: f.name })), - dateFields: fields.filter((f) => f.type === 'date').map((f) => ({ label: f.name })), - nonDateFields: fields.filter((f) => f.type !== 'date').map((f) => ({ label: f.name })), - }; - }, [sourcererDataView]); - useEffect(() => { const { index: indexField } = getFields(); const newIndexValue = [...selectedPatterns].sort(); @@ -173,6 +154,15 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) } }, [getFields, selectedPatterns]); + const dataView = useMemo( + () => ({ + ...sourcererDataView, + title: sourcererDataView.title ?? '', + fields: Object.values(sourcererDataView.fields || {}), + }), + [sourcererDataView] + ); + /* Force casting `sourcererDataView` to `DataViewBase` is required since EqlQueryEdit accepts DataViewBase but `useSourcererDataView()` returns `DataViewSpec`. @@ -186,9 +176,8 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) key="EqlQueryBar" path="eqlQueryBar" eqlOptionsPath="eqlOptions" - eqlFieldsComboBoxOptions={eqlFieldsComboBoxOptions} showEqlSizeOption - dataView={sourcererDataView as unknown as DataViewBase} + dataView={dataView} loading={indexPatternsLoading} disabled={indexPatternsLoading} /> From 45bfabcb38262e5407c00c36a2247c61379e4761 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 21 Nov 2024 17:57:05 +0100 Subject: [PATCH 26/28] display EQL options diff --- .../get_subfield_changes/eql_query.ts | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.ts index 25a4dff97dd21..b68eb44f7f86f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.ts @@ -15,26 +15,25 @@ export const getSubfieldChangesForEqlQuery = ( ): SubfieldChange[] => { const changes: SubfieldChange[] = []; - const oldQuery = stringifyToSortedJson(oldFieldValue?.query); - const newQuery = stringifyToSortedJson(newFieldValue?.query); + const subFieldNames: Array = [ + 'query', + 'filters', + 'event_category_override', + 'tiebreaker_field', + 'timestamp_field', + ]; - if (oldQuery !== newQuery) { - changes.push({ - subfieldName: 'query', - oldSubfieldValue: oldQuery, - newSubfieldValue: newQuery, - }); - } - - const oldFilters = stringifyToSortedJson(oldFieldValue?.filters); - const newFilters = stringifyToSortedJson(newFieldValue?.filters); + for (const subFieldName of subFieldNames) { + const oldValue = stringifyToSortedJson(oldFieldValue?.[subFieldName]); + const newValue = stringifyToSortedJson(newFieldValue?.[subFieldName]); - if (oldFilters !== newFilters) { - changes.push({ - subfieldName: 'filters', - oldSubfieldValue: oldFilters, - newSubfieldValue: newFilters, - }); + if (newValue !== oldValue) { + changes.push({ + subfieldName: subFieldName, + oldSubfieldValue: oldValue, + newSubfieldValue: newValue, + }); + } } return changes; From e9ee5c7f7c0f1b977d3c3a29efff1a1c64473001 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 21 Nov 2024 19:40:58 +0100 Subject: [PATCH 27/28] move debounceAsync to a package --- packages/kbn-securitysolution-utils/index.ts | 1 + .../debounce_async}/debounce_async.test.ts | 20 +++++---- .../src/debounce_async/debounce_async.ts | 43 +++++++++++++++++++ .../eql_query_edit/eql_query_edit.tsx | 2 +- .../components/step_define_rule/schema.tsx | 3 +- .../validators/debounce_async.ts | 37 ---------------- 6 files changed, 57 insertions(+), 49 deletions(-) rename {x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators => packages/kbn-securitysolution-utils/src/debounce_async}/debounce_async.test.ts (67%) create mode 100644 packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.ts diff --git a/packages/kbn-securitysolution-utils/index.ts b/packages/kbn-securitysolution-utils/index.ts index d29b356f31783..8769a281e2201 100644 --- a/packages/kbn-securitysolution-utils/index.ts +++ b/packages/kbn-securitysolution-utils/index.ts @@ -12,3 +12,4 @@ export * from './src/axios'; export * from './src/transform_data_to_ndjson'; export * from './src/path_validations'; export * from './src/esql'; +export * from './src/debounce_async/debounce_async'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.test.ts b/packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.test.ts similarity index 67% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.test.ts rename to packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.test.ts index aa75c6fddc23e..d7e1201e44e8d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.test.ts +++ b/packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.test.ts @@ -1,8 +1,10 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { debounceAsync } from './debounce_async'; @@ -10,13 +12,9 @@ import { debounceAsync } from './debounce_async'; jest.useFakeTimers({ legacyFakeTimers: true }); describe('debounceAsync', () => { - let fn: jest.Mock; - - beforeEach(() => { - fn = jest.fn().mockResolvedValueOnce('first'); - }); - it('resolves with the underlying invocation result', async () => { + const fn = jest.fn().mockResolvedValueOnce('first'); + const debounced = debounceAsync(fn, 0); const promise = debounced(); jest.runOnlyPendingTimers(); @@ -25,6 +23,8 @@ describe('debounceAsync', () => { }); it('resolves intermediate calls when the next invocation resolves', async () => { + const fn = jest.fn().mockResolvedValueOnce('first'); + const debounced = debounceAsync(fn, 200); fn.mockResolvedValueOnce('second'); @@ -39,6 +39,8 @@ describe('debounceAsync', () => { }); it('debounces the function', async () => { + const fn = jest.fn().mockResolvedValueOnce('first'); + const debounced = debounceAsync(fn, 200); debounced(); diff --git a/packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.ts b/packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.ts new file mode 100644 index 0000000000000..99fe653b0e21e --- /dev/null +++ b/packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.ts @@ -0,0 +1,43 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +/** + * Unlike lodash's debounce, which resolves intermediate calls with the most + * recent value, this implementation waits to resolve intermediate calls until + * the next invocation resolves. + * + * @param fn an async function + * + * @returns A debounced async function that resolves on the next invocation + */ +export function debounceAsync( + fn: (...args: Args) => Result, + intervalMs: number +): (...args: Args) => Promise> { + let timeoutId: ReturnType | undefined; + let resolve: (value: Awaited) => void; + let promise = new Promise>((_resolve) => { + resolve = _resolve; + }); + + return (...args) => { + if (timeoutId) { + clearTimeout(timeoutId); + } + + timeoutId = setTimeout(async () => { + resolve(await fn(...args)); + promise = new Promise((_resolve) => { + resolve = _resolve; + }); + }, intervalMs); + + return promise; + }; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx index f96ce06b545a2..5b519cb43c841 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx @@ -7,11 +7,11 @@ import React, { useMemo } from 'react'; import type { DataViewBase } from '@kbn/es-query'; +import { debounceAsync } from '@kbn/securitysolution-utils'; import type { FormData, FieldConfig, ValidationFuncArg } from '../../../../shared_imports'; import { UseMultiFields } from '../../../../shared_imports'; import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; import { queryRequiredValidatorFactory } from '../../../rule_creation_ui/validators/query_required_validator_factory'; -import { debounceAsync } from '../../../rule_creation_ui/validators/debounce_async'; import { eqlQueryValidatorFactory } from './eql_query_validator_factory'; import { EqlQueryBar } from './eql_query_bar'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx index 17406676e0eda..835cab7a4cfc2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx @@ -9,7 +9,7 @@ import { isEmpty } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiText } from '@elastic/eui'; import React from 'react'; - +import { debounceAsync } from '@kbn/securitysolution-utils'; import { singleEntryThreat, containsInvalidItems, @@ -28,7 +28,6 @@ import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../../common/constants' import { isMlRule } from '../../../../../common/machine_learning/helpers'; import type { ERROR_CODE, FormSchema, ValidationFunc } from '../../../../shared_imports'; import { FIELD_TYPES, fieldValidators } from '../../../../shared_imports'; -import { debounceAsync } from '../../validators/debounce_async'; import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types'; import { esqlValidator } from '../../../rule_creation/logic/esql_validator'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.ts deleted file mode 100644 index f9de51e4a2e65..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/debounce_async.ts +++ /dev/null @@ -1,37 +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. - */ - -/** - * Unlike lodash's debounce, which resolves intermediate calls with the most - * recent value, this implementation waits to resolve intermediate calls until - * the next invocation resolves. - * - * @param fn an async function - * - * @returns A debounced async function that resolves on the next invocation - */ -export const debounceAsync = ( - fn: (...args: Args) => Result, - interval: number -): ((...args: Args) => Result) => { - let handle: ReturnType | undefined; - let resolves: Array<(value?: Result) => void> = []; - - return (...args: Args): Result => { - if (handle) { - clearTimeout(handle); - } - - handle = setTimeout(() => { - const result = fn(...args); - resolves.forEach((resolve) => resolve(result)); - resolves = []; - }, interval); - - return new Promise((resolve) => resolves.push(resolve)) as Result; - }; -}; From 9f5d1f810e86eb3f7419645b0ca3db323d54543a Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 21 Nov 2024 22:59:12 +0100 Subject: [PATCH 28/28] map properly EQL options to eql_query in upgrade perform API endpoint --- .../diffable_rule_fields_mappings.ts | 76 +++++++++++-------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts index 8a796b5db1e28..7caa0469eebeb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { get } from 'lodash'; +import { get, has } from 'lodash'; import type { RuleSchedule, DataSourceIndexPatterns, @@ -48,9 +48,13 @@ export const mapDiffableRuleFieldValueToRuleSchemaFormat = ( return transformedValue.value; } + if (!SUBFIELD_MAPPING[fieldName] && !has(diffableField, diffableRuleSubfieldName)) { + return diffableField; + } + // From the ThreeWayDiff, get the specific field that maps to the diffable rule field // Otherwise, the diffableField itself already matches the rule field, so retrieve that value. - const mappedField = get(diffableField, diffableRuleSubfieldName, diffableField); + const mappedField = get(diffableField, diffableRuleSubfieldName); return mappedField; }; @@ -81,9 +85,27 @@ export function mapRuleFieldToDiffableRuleField({ ruleType, fieldName, }: MapRuleFieldToDiffableRuleFieldParams): keyof AllFieldsDiff { + // Handle query, filters and language fields based on rule type + if (fieldName === 'query' || fieldName === 'language' || fieldName === 'filters') { + switch (ruleType) { + case 'query': + case 'saved_query': + return 'kql_query' as const; + case 'eql': + return 'eql_query'; + case 'esql': + return 'esql_query'; + default: + return 'kql_query'; + } + } + const diffableRuleFieldMap: Record = { building_block_type: 'building_block', saved_id: 'kql_query', + event_category_override: 'eql_query', + tiebreaker_field: 'eql_query', + timestamp_field: 'eql_query', threat_query: 'threat_query', threat_language: 'threat_query', threat_filters: 'threat_query', @@ -99,24 +121,27 @@ export function mapRuleFieldToDiffableRuleField({ timestamp_override_fallback_disabled: 'timestamp_override', }; - // Handle query, filters and language fields based on rule type - if (fieldName === 'query' || fieldName === 'language' || fieldName === 'filters') { - switch (ruleType) { - case 'query': - case 'saved_query': - return 'kql_query' as const; - case 'eql': - return 'eql_query'; - case 'esql': - return 'esql_query'; - default: - return 'kql_query'; - } - } - return diffableRuleFieldMap[fieldName] || fieldName; } +const SUBFIELD_MAPPING: Record = { + index: 'index_patterns', + data_view_id: 'data_view_id', + saved_id: 'saved_query_id', + event_category_override: 'event_category_override', + tiebreaker_field: 'tiebreaker_field', + timestamp_field: 'timestamp_field', + building_block_type: 'type', + rule_name_override: 'field_name', + timestamp_override: 'field_name', + timestamp_override_fallback_disabled: 'fallback_disabled', + timeline_id: 'timeline_id', + timeline_title: 'timeline_title', + interval: 'interval', + from: 'lookback', + to: 'lookback', +}; + /** * Maps a PrebuiltRuleAsset schema field name to its corresponding property * name within a DiffableRule group. @@ -134,22 +159,7 @@ export function mapRuleFieldToDiffableRuleField({ * */ export function mapRuleFieldToDiffableRuleSubfield(fieldName: string): string { - const fieldMapping: Record = { - index: 'index_patterns', - data_view_id: 'data_view_id', - saved_id: 'saved_query_id', - building_block_type: 'type', - rule_name_override: 'field_name', - timestamp_override: 'field_name', - timestamp_override_fallback_disabled: 'fallback_disabled', - timeline_id: 'timeline_id', - timeline_title: 'timeline_title', - interval: 'interval', - from: 'lookback', - to: 'lookback', - }; - - return fieldMapping[fieldName] || fieldName; + return SUBFIELD_MAPPING[fieldName] || fieldName; } type TransformValuesReturnType =