diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 6c6c7f8516d5..b9fdd88d2d64 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -37785,7 +37785,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.multiSelectFields.placeholderText": "Sélectionner un champ", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText": "Sélectionner un champ", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel": "Champs", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin": "Au moins un champ est requis.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError": "Le format de l’URL n’est pas valide.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton": "Réinitialiser sur les modèles d'indexation par défaut", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.rulePreviewTitle": "Aperçu de la règle", @@ -39091,7 +39090,6 @@ "xpack.securitySolution.detectionEngine.userUnauthenticatedMsgBody": "Vous ne disposez pas des autorisations requises pour visualiser le moteur de détection. Pour une aide supplémentaire, contactez votre administrateur.", "xpack.securitySolution.detectionEngine.userUnauthenticatedTitle": "Autorisations de moteur de détection requises", "xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errMin": "La taille de la fenêtre d'historique doit être supérieure à 0.", - "xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsFieldsMax": "Le nombre de champs doit être de 3 au maximum.", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityFieldFieldData.thresholdCardinalityFieldNotSuppliedMessage": "Un champ Cardinalité est requis.", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityValueFieldData.numberGreaterThanOrEqualOneErrorMessage": "La valeur doit être supérieure ou égale à un.", "xpack.securitySolution.detectionEngine.validations.thresholdFieldFieldData.arrayLengthGreaterThanMaxErrorMessage": "Le nombre de champs doit être de 3 au maximum.", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 569228e7d408..caca8084d790 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -37643,7 +37643,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.multiSelectFields.placeholderText": "フィールドを選択", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText": "フィールドを選択", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel": "フィールド", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin": "1つ以上のフィールドが必要です。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError": "URLの形式が無効です", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton": "デフォルトインデックスパターンにリセット", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.rulePreviewTitle": "ルールプレビュー", @@ -38948,7 +38947,6 @@ "xpack.securitySolution.detectionEngine.userUnauthenticatedMsgBody": "検出エンジンを表示するための必要なアクセス権がありません。ヘルプについては、管理者にお問い合わせください。", "xpack.securitySolution.detectionEngine.userUnauthenticatedTitle": "検出エンジンアクセス権が必要です", "xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errMin": "履歴ウィンドウサイズは0よりも大きい値でなければなりません。", - "xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsFieldsMax": "フィールド数は3以下でなければなりません。", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityFieldFieldData.thresholdCardinalityFieldNotSuppliedMessage": "カーディナリティフィールドは必須です。", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityValueFieldData.numberGreaterThanOrEqualOneErrorMessage": "値は 1 以上でなければなりません。", "xpack.securitySolution.detectionEngine.validations.thresholdFieldFieldData.arrayLengthGreaterThanMaxErrorMessage": "フィールド数は3以下でなければなりません。", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 611dbf21d2a4..8fe52666e960 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -37071,7 +37071,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.multiSelectFields.placeholderText": "选择字段", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText": "选择字段", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel": "字段", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin": "至少需要一个字段。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError": "URL 的格式无效", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton": "重置为默认索引模式", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.rulePreviewTitle": "规则预览", @@ -38371,7 +38370,6 @@ "xpack.securitySolution.detectionEngine.userUnauthenticatedMsgBody": "您没有所需的权限,无法查看检测引擎。若需要更多帮助,请联系您的管理员。", "xpack.securitySolution.detectionEngine.userUnauthenticatedTitle": "需要检测引擎权限", "xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errMin": "历史记录窗口大小必须大于 0。", - "xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsFieldsMax": "字段数目不得超过 3 个。", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityFieldFieldData.thresholdCardinalityFieldNotSuppliedMessage": "基数字段必填。", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityValueFieldData.numberGreaterThanOrEqualOneErrorMessage": "值必须大于或等于 1。", "xpack.securitySolution.detectionEngine.validations.thresholdFieldFieldData.arrayLengthGreaterThanMaxErrorMessage": "字段数目不得超过 3 个。", diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts index 139acb0473c8..9b60c15dc135 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts @@ -5,16 +5,15 @@ * 2.0. */ -import { useMemo } from 'react'; import type { DataViewFieldBase } from '@kbn/es-query'; import type { FieldSpec } from '@kbn/data-plugin/common'; -import { getTermsAggregationFields } from '../../../../detection_engine/rule_creation_ui/components/step_define_rule/utils'; import { useRuleFields } from '../../../../detection_engine/rule_management/logic/use_rule_fields'; import { useMlCapabilities } from './use_ml_capabilities'; import { useMlRuleValidations } from './use_ml_rule_validations'; import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license'; +import { useTermsAggregationFields } from '../../../hooks/use_terms_aggregation_fields'; export interface UseMlRuleConfigReturn { hasMlAdminPermissions: boolean; @@ -45,10 +44,7 @@ export const useMLRuleConfig = ({ const { loading: mlFieldsLoading, fields: mlFields } = useRuleFields({ machineLearningJobId, }); - const mlSuppressionFields = useMemo( - () => getTermsAggregationFields(mlFields as FieldSpec[]), - [mlFields] - ); + const mlSuppressionFields = useTermsAggregationFields(mlFields); return { hasMlAdminPermissions: hasMlAdminPermissions(mlCapabilities), diff --git a/x-pack/plugins/security_solution/public/common/constants.ts b/x-pack/plugins/security_solution/public/common/constants.ts index c114f70915a7..a9761e87c20e 100644 --- a/x-pack/plugins/security_solution/public/common/constants.ts +++ b/x-pack/plugins/security_solution/public/common/constants.ts @@ -18,3 +18,5 @@ export const RISK_SCORE_HIGH = 73; export const RISK_SCORE_CRITICAL = 99; export const ONBOARDING_VIDEO_SOURCE = '//play.vidyard.com/K6kKDBbP9SpXife9s2tHNP.html?'; + +export const DEFAULT_HISTORY_WINDOW_SIZE = '7d'; diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_terms_aggregation_fields.ts b/x-pack/plugins/security_solution/public/common/hooks/use_terms_aggregation_fields.ts new file mode 100644 index 000000000000..e62b16e82676 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_terms_aggregation_fields.ts @@ -0,0 +1,29 @@ +/* + * 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 { FieldSpec } from '@kbn/data-views-plugin/common'; +import type { DataViewFieldBase } from '@kbn/es-query'; +import { getTermsAggregationFields } from '../../detection_engine/rule_creation_ui/components/step_define_rule/utils'; + +export function useTermsAggregationFields(fields?: DataViewFieldBase[]) { + const termsAggregationFields = useMemo( + /** + * Typecasting to FieldSpec because fields is + * typed as DataViewFieldBase[] which does not have + * the 'aggregatable' property, however the type is incorrect + * + * fields does contain elements with the aggregatable property. + * We will need to determine where these types are defined and + * figure out where the discrepancy is. + */ + () => getTermsAggregationFields((fields as FieldSpec[]) ?? []), + [fields] + ); + + return termsAggregationFields; +} diff --git a/x-pack/plugins/security_solution/public/common/utils/date_math.ts b/x-pack/plugins/security_solution/public/common/utils/date_math.ts new file mode 100644 index 000000000000..28dbe15955a2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/utils/date_math.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/** + * Converts a date math string to a duration string by removing the 'now-' prefix. + * + * @param historyStart - Date math string to convert. For example, "now-30d". + * @returns Resulting duration string. For example, "30d". + */ +export const convertDateMathToDuration = (dateMathString: string): string => { + if (dateMathString.startsWith('now-')) { + return dateMathString.substring(4); + } + + return dateMathString; +}; + +/** + * Converts a duration string to a dateMath string by adding the 'now-' prefix. + * + * @param durationString - Duration string to convert. For example, "30d". + * @returns Resulting date math string. For example, "now-30d". + */ +export const convertDurationToDateMath = (durationString: string): string => + `now-${durationString}`; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/history_window_start_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/history_window_start_edit.tsx new file mode 100644 index 000000000000..c876fe926ce4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/history_window_start_edit.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ScheduleItemField } from '../schedule_item_field'; +import { type FieldConfig, UseField } from '../../../../shared_imports'; +import { type HistoryWindowStart } from '../../../../../common/api/detection_engine'; +import * as i18n from './translations'; +import { validateHistoryWindowStart } from './validate_history_window_start'; + +const COMPONENT_PROPS = { + idAria: 'historyWindowSize', + dataTestSubj: 'historyWindowSize', + timeTypes: ['m', 'h', 'd'], +}; + +interface HistoryWindowStartEditProps { + path: string; +} + +export function HistoryWindowStartEdit({ path }: HistoryWindowStartEditProps): JSX.Element { + return ( + + ); +} + +const HISTORY_WINDOW_START_FIELD_CONFIG: FieldConfig = { + label: i18n.HISTORY_WINDOW_START_LABEL, + helpText: i18n.HELP_TEXT, + validations: [ + { + validator: validateHistoryWindowStart, + }, + ], +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/index.tsx similarity index 51% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/index.tsx index 1bf73b4a46b4..a904f5d42771 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/index.tsx @@ -5,11 +5,4 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; - -export const NEW_TERMS_FIELD_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText', - { - defaultMessage: 'Select a field', - } -); +export { HistoryWindowStartEdit } from './history_window_start_edit'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/translations.ts new file mode 100644 index 000000000000..75ff11fe5e1b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/translations.ts @@ -0,0 +1,36 @@ +/* + * 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 HISTORY_WINDOW_START_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.historyWindowSizeLabel', + { + defaultMessage: 'History Window Size', + } +); + +export const HELP_TEXT = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.historyWindowSizeHelpText', + { + defaultMessage: "New terms rules only alert if terms don't appear in historical data.", + } +); + +export const MUST_BE_POSITIVE_INTEGER_VALIDATION_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errNumber', + { + defaultMessage: 'History window size must be a positive number.', + } +); + +export const MUST_BE_GREATER_THAN_ZERO_VALIDATION_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errMin', + { + defaultMessage: 'History window size must be greater than 0.', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/validate_history_window_start.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/validate_history_window_start.ts new file mode 100644 index 000000000000..4e274683ad08 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/validate_history_window_start.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { type ValidationFunc } from '../../../../shared_imports'; +import * as i18n from './translations'; + +export function validateHistoryWindowStart(...args: Parameters) { + const [{ path, value }] = args; + + const historyWindowSize = Number.parseInt(String(value), 10); + + if (Number.isNaN(historyWindowSize)) { + return { + code: 'ERR_NOT_INT_NUMBER', + path, + message: i18n.MUST_BE_POSITIVE_INTEGER_VALIDATION_ERROR, + }; + } + + if (historyWindowSize <= 0) { + return { + code: 'ERR_MIN_LENGTH', + path, + message: i18n.MUST_BE_GREATER_THAN_ZERO_VALIDATION_ERROR, + }; + } +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/index.tsx new file mode 100644 index 000000000000..22c3c9e9047c --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/index.tsx @@ -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 { NewTermsFieldsEdit } from './new_terms_fields_edit'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit.tsx new file mode 100644 index 000000000000..f50c62ed8f7a --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import { UseField, fieldValidators } from '../../../../shared_imports'; +import { NewTermsFieldsField } from './new_terms_fields_field'; +import * as i18n from './translations'; + +interface NewTermsFieldsEditProps { + path: string; + fieldNames: string[]; +} + +export const NewTermsFieldsEdit = memo(function NewTermsFieldsEdit({ + path, + fieldNames, +}: NewTermsFieldsEditProps): JSX.Element { + return ( + + ); +}); + +const NEW_TERMS_FIELDS_CONFIG = { + label: i18n.NEW_TERMS_FIELDS_LABEL, + helpText: i18n.HELP_TEXT, + validations: [ + { + validator: fieldValidators.emptyField(i18n.MIN_FIELDS_COUNT_VALIDATION_ERROR), + }, + { + validator: fieldValidators.maxLengthField(i18n.MAX_FIELDS_COUNT_VALIDATION_ERROR), + }, + ], +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_field.tsx new file mode 100644 index 000000000000..a9c1148402f1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_field.tsx @@ -0,0 +1,40 @@ +/* + * 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, { memo } from 'react'; + +import type { DataViewFieldBase } from '@kbn/es-query'; +import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import type { FieldHook } from '../../../../shared_imports'; +import { PLACEHOLDER } from './translations'; + +interface NewTermsFieldsProps { + fieldNames: DataViewFieldBase[]; + field: FieldHook; +} + +const FIELD_COMBO_BOX_WIDTH = 410; + +const fieldDescribedByIds = 'newTermsFieldEdit'; + +export const NewTermsFieldsField = memo(function NewTermsFieldsField({ + fieldNames, + field, +}: NewTermsFieldsProps): JSX.Element { + const fieldEuiFieldProps = { + fullWidth: true, + noSuggestions: false, + options: fieldNames.map((name) => ({ label: name })), + placeholder: PLACEHOLDER, + onCreateOption: undefined, + style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, + }; + + return ( + + ); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/translations.ts new file mode 100644 index 000000000000..4eb18b9a318e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/translations.ts @@ -0,0 +1,47 @@ +/* + * 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 { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../../common/constants'; + +export const NEW_TERMS_FIELDS_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel', + { + defaultMessage: 'Fields', + } +); + +export const PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText', + { + defaultMessage: 'Select a field', + } +); + +export const HELP_TEXT = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNewTermsFieldHelpText', + { + defaultMessage: 'Select a field to check for new terms.', + } +); + +export const MIN_FIELDS_COUNT_VALIDATION_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.minFieldsCountError', + { + defaultMessage: 'A minimum of one field is required.', + } +); + +export const MAX_FIELDS_COUNT_VALIDATION_ERROR = { + length: MAX_NUMBER_OF_NEW_TERMS_FIELDS, + message: i18n.translate( + 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsField.maxFieldsCountError', + { + defaultMessage: 'Number of fields must be 3 or less.', + } + ), +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/index.ts new file mode 100644 index 000000000000..f2458bbb5a4e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_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 { ScheduleItemField } from './schedule_item_field'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_form/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.test.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_form/index.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.test.tsx index 057ecbdae5fc..bf019bd43f2f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_form/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.test.tsx @@ -8,14 +8,14 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; -import { ScheduleItem } from '.'; +import { ScheduleItemField } from './schedule_item_field'; import { TestProviders, useFormFieldMock } from '../../../../common/mock'; -describe('ScheduleItem', () => { +describe('ScheduleItemField', () => { it('renders correctly', () => { const mockField = useFormFieldMock(); const wrapper = shallow( - { const mockField = useFormFieldMock(); const wrapper = mount( - { const mockField = useFormFieldMock(); const wrapper = mount( - { const mockField = useFormFieldMock(); const wrapper = mount( - { } }; -export const ScheduleItem = ({ +export const ScheduleItemField = ({ dataTestSubj, field, idAria, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_form/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_form/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/translations.ts 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 26c81f325ea1..a91487afc448 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 @@ -72,6 +72,8 @@ import { ALERT_SUPPRESSION_MISSING_FIELDS_FIELD_NAME, } from '../../../rule_creation/components/alert_suppression_edit'; import { THRESHOLD_ALERT_SUPPRESSION_ENABLED } from '../../../rule_creation/components/threshold_alert_suppression_edit'; +import { NEW_TERMS_FIELDS_LABEL } from '../../../rule_creation/components/new_terms_fields_edit/translations'; +import { HISTORY_WINDOW_START_LABEL } from '../../../rule_creation/components/history_window_start_edit/translations'; import type { FieldValueQueryBar } from '../query_bar_field'; const DescriptionListContainer = styled(EuiDescriptionList)` @@ -341,6 +343,9 @@ export const getDescriptionItem = ( } else if (field === 'threatMapping') { const threatMap: ThreatMapping = get(field, data); return buildThreatMappingDescription(label, threatMap); + } else if (field === 'newTermsFields') { + const values: string[] = get(field, data); + return buildStringArrayDescription(NEW_TERMS_FIELDS_LABEL, field, values); } else if (Array.isArray(get(field, data)) && field !== 'threatMapping') { const values: string[] = get(field, data); return buildStringArrayDescription(label, field, values); @@ -357,6 +362,9 @@ export const getDescriptionItem = ( } else if (field === 'maxSignals') { const value: number | undefined = get(field, data); return value ? [{ title: label, description: value }] : []; + } else if (field === 'historyWindowSize') { + const value: number = get(field, data); + return value ? [{ title: HISTORY_WINDOW_START_LABEL, description: value }] : []; } const description: string = get(field, data); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/index.tsx deleted file mode 100644 index 7dfb24200e64..000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/index.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 type { DataViewFieldBase } from '@kbn/es-query'; -import type { FieldHook } from '../../../../shared_imports'; -import { Field } from '../../../../shared_imports'; -import { NEW_TERMS_FIELD_PLACEHOLDER } from './translations'; - -interface NewTermsFieldsProps { - browserFields: DataViewFieldBase[]; - field: FieldHook; -} - -const FIELD_COMBO_BOX_WIDTH = 410; - -const fieldDescribedByIds = 'detectionEngineStepDefineRuleNewTermsField'; - -export const NewTermsFieldsComponent: React.FC = ({ - browserFields, - field, -}: NewTermsFieldsProps) => { - const fieldEuiFieldProps = useMemo( - () => ({ - fullWidth: true, - noSuggestions: false, - options: browserFields.map((browserField) => ({ label: browserField.name })), - placeholder: NEW_TERMS_FIELD_PLACEHOLDER, - onCreateOption: undefined, - style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, - }), - [browserFields] - ); - return ; -}; - -export const NewTermsFields = React.memo(NewTermsFieldsComponent); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts index 045e957c4e12..fcbdfdf4f86b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts @@ -107,7 +107,7 @@ export const getIsRulePreviewDisabled = ({ threatMapping, machineLearningJobId, queryBar, - newTermsFields, + newTermsFields = [], }: { ruleType: Type; isQueryBarValid: boolean; 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 7b056b596979..f7845c31378a 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 @@ -55,7 +55,6 @@ import { } from '../../../../shared_imports'; import type { FormHook, FieldHook } from '../../../../shared_imports'; import { schema } from './schema'; -import { getTermsAggregationFields } from './utils'; import { useExperimentalFeatureFieldsTransform } from './use_experimental_feature_fields_transform'; import * as i18n from './translations'; import { @@ -72,8 +71,6 @@ 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'; -import { NewTermsFields } from '../new_terms_fields'; -import { ScheduleItem } from '../../../rule_creation/components/schedule_item_form'; import { RequiredFields } from '../../../rule_creation/components/required_fields'; import { DocLink } from '../../../../common/components/links_to_docs/doc_link'; import { useLicense } from '../../../../common/hooks/use_license'; @@ -90,6 +87,10 @@ import { } 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'; +import { useTermsAggregationFields } from '../../../../common/hooks/use_terms_aggregation_fields'; +import { HistoryWindowStartEdit } from '../../../rule_creation/components/history_window_start_edit'; +import { NewTermsFieldsEdit } from '../../../rule_creation/components/new_terms_fields_edit'; +import { usePersistentNewTermsState } from './use_persistent_new_terms_state'; import { EsqlQueryEdit } from '../../../rule_creation/components/esql_query_edit'; import { usePersistentQuery } from './use_persistent_query'; @@ -211,18 +212,11 @@ const StepDefineRuleComponent: FC = ({ () => (indexPattern.fields as FieldSpec[]).filter((field) => field.aggregatable === true), [indexPattern.fields] ); - const termsAggregationFields = useMemo( - /** - * Typecasting to FieldSpec because fields is - * typed as DataViewFieldBase[] which does not have - * the 'aggregatable' property, however the type is incorrect - * - * fields does contain elements with the aggregatable property. - * We will need to determine where these types are defined and - * figure out where the discrepency is. - */ - () => getTermsAggregationFields(indexPattern.fields as FieldSpec[]), - [indexPattern.fields] + + const termsAggregationFields = useTermsAggregationFields(indexPattern.fields); + const termsAggregationFieldNames = useMemo( + () => termsAggregationFields.map((field) => field.name), + [termsAggregationFields] ); const [threatIndexPatternsLoading, { indexPatterns: threatIndexPatterns }] = @@ -245,6 +239,12 @@ const StepDefineRuleComponent: FC = ({ form, }); usePersistentAlertSuppressionState({ form }); + usePersistentNewTermsState({ + form, + ruleTypePath: 'ruleType', + newTermsFieldsPath: 'newTermsFields', + historyWindowStartPath: 'historyWindowSize', + }); const handleSetRuleFromTimeline = useCallback( ({ index: timelineIndex, queryBar: timelineQueryBar, eqlOptions }) => { @@ -730,30 +730,14 @@ const StepDefineRuleComponent: FC = ({ - - <> - - - - + {isNewTermsRule(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 323e321eee8d..4f168d94388d 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 @@ -17,12 +17,10 @@ import { } from '../../../../common/components/threat_match/helpers'; import { isEsqlRule, - isNewTermsRule, isThreatMatchRule, isThresholdRule, isSuppressionRuleConfiguredWithGroupBy, } from '../../../../../common/detection_engine/utils'; -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'; @@ -478,106 +476,8 @@ export const schema: FormSchema = { }, ], }, - newTermsFields: { - type: FIELD_TYPES.COMBO_BOX, - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel', - { - defaultMessage: 'Fields', - } - ), - helpText: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNewTermsFieldHelpText', - { - defaultMessage: 'Select a field to check for new terms.', - } - ), - validations: [ - { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ formData }] = args; - const needsValidation = isNewTermsRule(formData.ruleType); - if (!needsValidation) { - return; - } - - return fieldValidators.emptyField( - i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin', - { - defaultMessage: 'A minimum of one field is required.', - } - ) - )(...args); - }, - }, - { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ formData }] = args; - const needsValidation = isNewTermsRule(formData.ruleType); - if (!needsValidation) { - return; - } - return fieldValidators.maxLengthField({ - length: MAX_NUMBER_OF_NEW_TERMS_FIELDS, - message: i18n.translate( - 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsFieldsMax', - { - defaultMessage: 'Number of fields must be 3 or less.', - } - ), - })(...args); - }, - }, - ], - }, - historyWindowSize: { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.historyWindowSizeLabel', - { - defaultMessage: 'History Window Size', - } - ), - helpText: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.historyWindowSizeHelpText', - { - defaultMessage: "New terms rules only alert if terms don't appear in historical data.", - } - ), - validations: [ - { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ path, formData }] = args; - const needsValidation = isNewTermsRule(formData.ruleType); - - if (!needsValidation) { - return; - } - - const filterTimeVal = formData.historyWindowSize.match(/\d+/g); - - if (filterTimeVal <= 0) { - return { - code: 'ERR_MIN_LENGTH', - path, - message: i18n.translate( - 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errMin', - { - defaultMessage: 'History window size must be greater than 0.', - } - ), - }; - } - }, - }, - ], - }, + newTermsFields: {}, + historyWindowSize: {}, [ALERT_SUPPRESSION_FIELDS_FIELD_NAME]: { validations: [ { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_persistent_new_terms_state.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_persistent_new_terms_state.tsx new file mode 100644 index 000000000000..61bfa45ba8a7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_persistent_new_terms_state.tsx @@ -0,0 +1,73 @@ +/* + * 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 { useEffect, useRef } from 'react'; +import usePrevious from 'react-use/lib/usePrevious'; +import { isNewTermsRule } from '../../../../../common/detection_engine/utils'; +import type { FormHook } from '../../../../shared_imports'; +import { useFormData } from '../../../../shared_imports'; +import { type DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; +import { + type NewTermsFields, + type HistoryWindowStart, +} from '../../../../../common/api/detection_engine'; + +interface UsePersistentNewTermsStateParams { + form: FormHook; + ruleTypePath: string; + newTermsFieldsPath: string; + historyWindowStartPath: string; +} + +interface LastNewTermsState { + newTermsFields: NewTermsFields; + historyWindowStart: HistoryWindowStart; +} + +export function usePersistentNewTermsState({ + form, + ruleTypePath, + newTermsFieldsPath, + historyWindowStartPath, +}: UsePersistentNewTermsStateParams): void { + const lastNewTermsState = useRef(); + const [formData] = useFormData({ form, watch: [newTermsFieldsPath, historyWindowStartPath] }); + + const { + [ruleTypePath]: ruleType, + [newTermsFieldsPath]: newTermsFields, + [historyWindowStartPath]: historyWindowStart, + } = formData; + const previousRuleType = usePrevious(ruleType); + + useEffect(() => { + if ( + isNewTermsRule(ruleType) && + !isNewTermsRule(previousRuleType) && + lastNewTermsState.current + ) { + form.updateFieldValues({ + [newTermsFieldsPath]: lastNewTermsState.current.newTermsFields, + [historyWindowStartPath]: lastNewTermsState.current.historyWindowStart, + }); + + return; + } + + if (isNewTermsRule(ruleType)) { + lastNewTermsState.current = { newTermsFields, historyWindowStart }; + } + }, [ + form, + ruleType, + previousRuleType, + newTermsFieldsPath, + historyWindowStartPath, + newTermsFields, + historyWindowStart, + ]); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_schedule_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_schedule_rule/index.tsx index 2197b069de40..7c309ed32a68 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_schedule_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_schedule_rule/index.tsx @@ -13,11 +13,11 @@ import type { ScheduleStepRule, } from '../../../../detections/pages/detection_engine/rules/types'; import { StepRuleDescription } from '../description_step'; -import { ScheduleItem } from '../../../rule_creation/components/schedule_item_form'; import { Form, UseField } from '../../../../shared_imports'; import type { FormHook } from '../../../../shared_imports'; import { StepContentWrapper } from '../../../rule_creation/components/step_content_wrapper'; import { schema } from './schema'; +import { ScheduleItemField } from '../../../rule_creation/components/schedule_item_field'; const StyledForm = styled(Form)` max-width: 235px !important; @@ -43,7 +43,7 @@ const StepScheduleRuleComponent: FC = ({ = ({ /> { const timeObj: { unit: Unit; value: number } = { @@ -530,7 +531,7 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep query: ruleFields.queryBar?.query?.query as string, required_fields: requiredFields, new_terms_fields: ruleFields.newTermsFields, - history_window_start: `now-${ruleFields.historyWindowSize}`, + history_window_start: convertDurationToDateMath(ruleFields.historyWindowSize), ...alertSuppressionFields, } : isEsqlFields(ruleFields) && !('index' in ruleFields) 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 3e08f4ce3acc..487154918553 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 @@ -43,7 +43,6 @@ import * as timelinesI18n from '../../../../timelines/components/timeline/transl import { useRuleIndexPattern } from '../../../rule_creation_ui/pages/form'; import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types'; import type { Duration } from '../../../../detections/pages/detection_engine/rules/types'; -import { convertHistoryStartToSize } from '../../../../detections/pages/detection_engine/rules/helpers'; import { MlJobsDescription } from '../../../rule_creation/components/ml_jobs_description/ml_jobs_description'; import { MlJobLink } from '../../../rule_creation/components/ml_job_link/ml_job_link'; import { useSecurityJobs } from '../../../../common/components/ml_popover/hooks/use_security_jobs'; @@ -60,6 +59,8 @@ import { } from './rule_definition_section.styles'; import { getQueryLanguageLabel } from './helpers'; import { useDefaultIndexPattern } from '../../hooks/use_default_index_pattern'; +import { convertDateMathToDuration } from '../../../../common/utils/date_math'; +import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../common/constants'; import { EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL, EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL, @@ -418,7 +419,9 @@ interface HistoryWindowSizeProps { } export const HistoryWindowSize = ({ historyWindowStart }: HistoryWindowSizeProps) => { - const size = historyWindowStart ? convertHistoryStartToSize(historyWindowStart) : '7d'; + const size = historyWindowStart + ? convertDateMathToDuration(historyWindowStart) + : DEFAULT_HISTORY_WINDOW_SIZE; return ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_adapter.tsx new file mode 100644 index 000000000000..084a81e3b959 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_adapter.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { HistoryWindowStartEdit } from '../../../../../../../rule_creation/components/history_window_start_edit'; + +export function HistoryWindowStartEditAdapter(): 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/history_window_start/history_window_start_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_form.tsx new file mode 100644 index 000000000000..87ac2fee64e7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_form.tsx @@ -0,0 +1,48 @@ +/* + * 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 FormData } from '../../../../../../../../shared_imports'; +import { HistoryWindowStartEditAdapter } from './history_window_start_edit_adapter'; +import type { HistoryWindowStart } from '../../../../../../../../../common/api/detection_engine'; +import { + convertDurationToDateMath, + convertDateMathToDuration, +} from '../../../../../../../../common/utils/date_math'; +import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../../../../../common/constants'; +import { RuleFieldEditFormWrapper } from '../../../field_final_side'; + +export function HistoryWindowStartEditForm(): JSX.Element { + return ( + + ); +} + +interface HistoryWindowFormData { + historyWindowSize: HistoryWindowStart; +} + +function deserializer(defaultValue: FormData): HistoryWindowFormData { + return { + historyWindowSize: defaultValue.history_window_start + ? convertDateMathToDuration(defaultValue.history_window_start) + : DEFAULT_HISTORY_WINDOW_SIZE, + }; +} + +function serializer(formData: FormData): { history_window_start: HistoryWindowStart } { + return { + history_window_start: convertDurationToDateMath(formData.historyWindowSize), + }; +} + +const schema = {}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_adapter.tsx new file mode 100644 index 000000000000..476bf9869bd9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_adapter.tsx @@ -0,0 +1,26 @@ +/* + * 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 { NewTermsFieldsEdit } from '../../../../../../../rule_creation/components/new_terms_fields_edit'; +import { useDiffableRuleDataView } from '../hooks/use_diffable_rule_data_view'; +import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; +import { useTermsAggregationFields } from '../../../../../../../../common/hooks/use_terms_aggregation_fields'; + +interface NewTermsFieldsEditAdapterProps { + finalDiffableRule: DiffableRule; +} + +export function NewTermsFieldsEditAdapter({ + finalDiffableRule, +}: NewTermsFieldsEditAdapterProps): JSX.Element { + const { dataView } = useDiffableRuleDataView(finalDiffableRule); + const termsAggregationFields = useTermsAggregationFields(dataView?.fields ?? []); + const fieldNames = termsAggregationFields.map((field) => field.name); + + return ; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_form.tsx new file mode 100644 index 000000000000..9b8d709b27f0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_form.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { RuleFieldEditFormWrapper } from '../../../field_final_side'; +import { NewTermsFieldsEditAdapter } from './new_terms_fields_edit_adapter'; + +export function NewTermsFieldsEditForm(): JSX.Element { + return ( + + ); +} + +const schema = {}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_schedule.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_schedule.tsx index 12e7bbea9a20..3acd7c3050fb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_schedule.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_schedule.tsx @@ -10,8 +10,8 @@ import { parseDuration } from '@kbn/alerting-plugin/common'; import { type FormSchema, type FormData, UseField } from '../../../../../../../shared_imports'; import { schema } from '../../../../../../rule_creation_ui/components/step_schedule_rule/schema'; import type { RuleSchedule } from '../../../../../../../../common/api/detection_engine'; -import { ScheduleItem } from '../../../../../../rule_creation/components/schedule_item_form'; import { secondsToDurationString } from '../../../../../../../detections/pages/detection_engine/rules/helpers'; +import { ScheduleItemField } from '../../../../../../rule_creation/components/schedule_item_field'; export const ruleScheduleSchema = { interval: schema.interval, @@ -28,8 +28,8 @@ const componentProps = { export function RuleScheduleEdit(): JSX.Element { return ( <> - - + + ); } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/new_terms_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/new_terms_rule_field_edit.tsx index e2860d431aff..f47607abe384 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/new_terms_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/new_terms_rule_field_edit.tsx @@ -7,9 +7,12 @@ import React from 'react'; import type { UpgradeableNewTermsFields } from '../../../../model/prebuilt_rule_upgrade/fields'; -import { KqlQueryEditForm } from './fields/kql_query'; -import { DataSourceEditForm } from './fields/data_source'; import { AlertSuppressionEditForm } from './fields/alert_suppression'; +import { DataSourceEditForm } from './fields/data_source'; +import { HistoryWindowStartEditForm } from './fields/history_window_start/history_window_start_edit_form'; +import { KqlQueryEditForm } from './fields/kql_query'; +import { NewTermsFieldsEditForm } from './fields/new_terms_fields/new_terms_fields_edit_form'; +import { assertUnreachable } from '../../../../../../../common/utility_types'; interface NewTermsRuleFieldEditProps { fieldName: UpgradeableNewTermsFields; @@ -17,13 +20,17 @@ interface NewTermsRuleFieldEditProps { export function NewTermsRuleFieldEdit({ fieldName }: NewTermsRuleFieldEditProps) { switch (fieldName) { - case 'kql_query': - return ; - case 'data_source': - return ; case 'alert_suppression': return ; + case 'data_source': + return ; + case 'history_window_start': + return ; + case 'kql_query': + return ; + case 'new_terms_fields': + 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_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx index 88e1411a5e0b..518c18a59a41 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx @@ -9,11 +9,11 @@ import { EuiCallOut } from '@elastic/eui'; import React, { useCallback } from 'react'; import type { BulkActionEditPayload } from '../../../../../../../common/api/detection_engine/rule_management'; import { BulkActionEditTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management'; -import { ScheduleItem } from '../../../../../rule_creation/components/schedule_item_form'; import type { FormSchema } from '../../../../../../shared_imports'; import { UseField, useForm } from '../../../../../../shared_imports'; import { bulkSetSchedule as i18n } from '../translations'; import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; +import { ScheduleItemField } from '../../../../../rule_creation/components/schedule_item_field'; export interface ScheduleFormData { interval: string; @@ -79,7 +79,7 @@ export const ScheduleForm = ({ rulesCount, onClose, onConfirm }: ScheduleFormCom > ({ newTermsFields: ('new_terms_fields' in rule && rule.new_terms_fields) || [], historyWindowSize: 'history_window_start' in rule && rule.history_window_start - ? convertHistoryStartToSize(rule.history_window_start) - : '7d', + ? convertDateMathToDuration(rule.history_window_start) + : DEFAULT_HISTORY_WINDOW_SIZE, shouldLoadQueryDynamically: Boolean(rule.type === 'saved_query' && rule.saved_id), [ALERT_SUPPRESSION_FIELDS_FIELD_NAME]: ('alert_suppression' in rule && @@ -189,14 +191,6 @@ export const getDefineStepsData = (rule: RuleResponse): DefineStepRule => ({ ), }); -export const convertHistoryStartToSize = (relativeTime: string) => { - if (relativeTime.startsWith('now-')) { - return relativeTime.substring(4); - } else { - return relativeTime; - } -}; - export const getScheduleStepsData = (rule: RuleResponse): ScheduleStepRule => { const { interval, from } = rule; const fromHumanizedValue = getHumanizedDuration(from, interval); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts index 903b463d6f58..483cbf06b625 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts @@ -275,10 +275,10 @@ export const ESQL_QUERY_BAR = '[data-test-subj="ruleEsqlQueryBar"]'; export const NEW_TERMS_INPUT_AREA = '[data-test-subj="newTermsInput"]'; export const NEW_TERMS_HISTORY_SIZE = - '[data-test-subj="detectionEngineStepDefineRuleHistoryWindowSize"] [data-test-subj="interval"]'; + '[data-test-subj="historyWindowSize"] [data-test-subj="interval"]'; export const NEW_TERMS_HISTORY_TIME_TYPE = - '[data-test-subj="detectionEngineStepDefineRuleHistoryWindowSize"] [data-test-subj="timeType"]'; + '[data-test-subj="historyWindowSize"] [data-test-subj="timeType"]'; export const LOAD_QUERY_DYNAMICALLY_CHECKBOX = '[data-test-subj="detectionEngineStepDefineRuleShouldLoadQueryDynamically"] input';