From d79f20498cf2767a533e0eec12795f594889edc2 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Thu, 14 Nov 2024 14:11:56 +0100 Subject: [PATCH 01/12] Add `new_terms_fields` --- .../components/ml/hooks/use_ml_rule_config.ts | 8 +--- .../hooks/use_terms_aggregation_fields.ts | 29 ++++++++++++ .../new_terms_fields_edit}/index.tsx | 6 +-- .../new_terms_fields_edit}/translations.ts | 0 .../components/step_define_rule/index.tsx | 34 +++++++------- .../new_terms_fields_edit_adapter.tsx | 39 ++++++++++++++++ .../new_terms_fields_edit_form.tsx | 44 +++++++++++++++++++ .../final_edit/new_terms_rule_field_edit.tsx | 11 +++-- .../new_terms_fields/new_terms_fields.tsx | 4 ++ 9 files changed, 146 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_terms_aggregation_fields.ts rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui/components/new_terms_fields => rule_creation/components/new_terms_fields_edit}/index.tsx (84%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation_ui/components/new_terms_fields => rule_creation/components/new_terms_fields_edit}/translations.ts (100%) create mode 100644 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 create mode 100644 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 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 139acb0473c8b..9b60c15dc1354 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/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 0000000000000..e62b16e826766 --- /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/detection_engine/rule_creation_ui/components/new_terms_fields/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/index.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/index.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/index.tsx index 7dfb24200e645..12eb29b7105dc 100644 --- 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/components/new_terms_fields_edit/index.tsx @@ -19,9 +19,9 @@ interface NewTermsFieldsProps { const FIELD_COMBO_BOX_WIDTH = 410; -const fieldDescribedByIds = 'detectionEngineStepDefineRuleNewTermsField'; +const fieldDescribedByIds = 'newTermsFieldEdit'; -export const NewTermsFieldsComponent: React.FC = ({ +export const NewTermsFieldsEditComponent: React.FC = ({ browserFields, field, }: NewTermsFieldsProps) => { @@ -39,4 +39,4 @@ export const NewTermsFieldsComponent: React.FC = ({ return ; }; -export const NewTermsFields = React.memo(NewTermsFieldsComponent); +export const NewTermsFieldsEdit = React.memo(NewTermsFieldsEditComponent); 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/new_terms_fields_edit/translations.ts similarity index 100% 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/new_terms_fields_edit/translations.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..3d35910b4aaad 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 @@ -61,7 +61,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 { @@ -78,7 +77,7 @@ import { EqlQueryBar } from '../eql_query_bar'; 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 { NewTermsFieldsEdit } from '../../../rule_creation/components/new_terms_fields_edit'; 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'; @@ -94,6 +93,7 @@ import { useMLRuleConfig } from '../../../../common/components/ml/hooks/use_ml_r import { 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'; +import { useTermsAggregationFields } from '../../../../common/hooks/use_terms_aggregation_fields'; const CommonUseField = getUseField({ component: Field }); @@ -256,19 +256,21 @@ 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 = 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 [threatIndexPatternsLoading, { indexPatterns: threatIndexPatterns }] = useFetchIndex(threatIndex); @@ -925,7 +927,7 @@ const StepDefineRuleComponent: FC = ({ <> ({ + browserFields: termsAggregationFields, + }), + [termsAggregationFields] + ); + + 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 0000000000000..0eb5b311d7327 --- /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,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 type { FormData, FormSchema } from '../../../../../../../../shared_imports'; +import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; +import { NewTermsFieldsEditAdapter } from './new_terms_fields_edit_adapter'; +import { type NewTermsFields } from '../../../../../../../../../common/api/detection_engine'; +import { schema } from '../../../../../../../rule_creation_ui/components/step_define_rule/schema'; + +export function NewTermsFieldsEditForm(): JSX.Element { + return ( + + ); +} + +interface NewTermsFieldsFormData { + newTermsFields: string[]; +} + +function deserializer(defaultValue: FormData): NewTermsFieldsFormData { + return { + newTermsFields: defaultValue.new_terms_fields, + }; +} + +function serializer(formData: FormData): { new_terms_fields: NewTermsFields } { + return { + new_terms_fields: formData.newTermsFields, + }; +} + +export const newTermsFormSchema = { newTermsFields: schema.newTermsFields } as FormSchema<{ + newTermsFields: NewTermsFields; +}>; 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 e2860d431affa..bf3e95b6bced8 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 @@ -10,6 +10,7 @@ import type { UpgradeableNewTermsFields } from '../../../../model/prebuilt_rule_ import { KqlQueryEditForm } from './fields/kql_query'; import { DataSourceEditForm } from './fields/data_source'; import { AlertSuppressionEditForm } from './fields/alert_suppression'; +import { NewTermsFieldsEditForm } from './fields/new_terms_fields/new_terms_fields_edit_form'; interface NewTermsRuleFieldEditProps { fieldName: UpgradeableNewTermsFields; @@ -17,12 +18,14 @@ 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 'kql_query': + return ; + case 'new_terms_fields': + 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_readonly/fields/new_terms_fields/new_terms_fields.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/new_terms_fields/new_terms_fields.tsx index b77f7b0736482..5dca3fa851e51 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/new_terms_fields/new_terms_fields.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/new_terms_fields/new_terms_fields.tsx @@ -16,6 +16,10 @@ interface NewTermsFieldsReadOnlyProps { } export function NewTermsFieldsReadOnly({ newTermsFields }: NewTermsFieldsReadOnlyProps) { + if (!newTermsFields.length) { + return null; + } + return ( Date: Fri, 15 Nov 2024 15:41:57 +0100 Subject: [PATCH 02/12] Add `history_window_start` and refactor `new_terms_fields` --- .../public/common/constants.ts | 2 + .../public/common/utils/history_window.ts | 28 ++++++++++ .../history_window_start_edit.tsx | 24 +++++++++ .../history_window_start_edit/index.tsx | 8 +++ .../new_terms_fields_edit/index.tsx | 36 +------------ .../new_terms_fields_edit.tsx | 24 +++++++++ .../new_terms_fields_edit_field.tsx | 42 +++++++++++++++ .../components/schedule_item_field/index.ts | 8 +++ .../schedule_item_field.test.tsx} | 12 ++--- .../schedule_item_field.tsx} | 2 +- .../translations.ts | 0 .../components/step_define_rule/index.tsx | 22 ++------ .../components/step_schedule_rule/index.tsx | 6 +-- .../pages/rule_creation/helpers.ts | 3 +- .../rule_details/rule_definition_section.tsx | 2 +- .../history_window_start_edit_adapter.tsx | 13 +++++ .../history_window_start_edit_form.tsx | 53 +++++++++++++++++++ .../new_terms_fields_edit_adapter.tsx | 18 +------ .../final_edit/fields/rule_schedule.tsx | 6 +-- .../final_edit/new_terms_rule_field_edit.tsx | 7 ++- .../bulk_actions/forms/schedule_form.tsx | 6 +-- .../pages/detection_engine/rules/helpers.tsx | 12 ++--- .../cypress/screens/create_new_rule.ts | 4 +- 23 files changed, 238 insertions(+), 100 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/utils/history_window.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/history_window_start_edit.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/index.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit_field.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/index.ts rename x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/{schedule_item_form/index.test.tsx => schedule_item_field/schedule_item_field.test.tsx} (92%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/{schedule_item_form/index.tsx => schedule_item_field/schedule_item_field.tsx} (99%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/{schedule_item_form => schedule_item_field}/translations.ts (100%) create mode 100644 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 create mode 100644 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 diff --git a/x-pack/plugins/security_solution/public/common/constants.ts b/x-pack/plugins/security_solution/public/common/constants.ts index c114f70915a75..a9761e87c20e1 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/utils/history_window.ts b/x-pack/plugins/security_solution/public/common/utils/history_window.ts new file mode 100644 index 0000000000000..01f85390ffcf9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/utils/history_window.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Converts a history start string to a history size string by removing the 'now-' prefix. + * + * @param historyStart - History start string to convert. For example, "now-30d". + * @returns Converted size string. For example, "30d". + */ +export const convertHistoryStartToSize = (historyStart: string) => { + if (historyStart.startsWith('now-')) { + return historyStart.substring(4); + } else { + return historyStart; + } +}; + +/** + * Converts a history size string to a history start string by adding the 'now-' prefix. + * + * @param historySize - History size string to convert. For example, "30d". + * @returns Converted start string. For example, "now-30d". + */ +export const convertHistorySizeToStart = (historySize: string) => `now-${historySize}`; 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 0000000000000..1a4e17055ecf3 --- /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,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; 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 { UseField } from '../../../../shared_imports'; + +const componentProps = { + idAria: 'historyWindowSize', + dataTestSubj: 'historyWindowSize', + timeTypes: ['m', 'h', 'd'], +}; + +interface HistoryWindowStartEditProps { + path: string; +} + +export function HistoryWindowStartEdit({ path }: HistoryWindowStartEditProps): JSX.Element { + return ; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/index.tsx new file mode 100644 index 0000000000000..a904f5d427710 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_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 { HistoryWindowStartEdit } from './history_window_start_edit'; 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 index 12eb29b7105dc..22c3c9e9047c0 100644 --- 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 @@ -5,38 +5,4 @@ * 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 = 'newTermsFieldEdit'; - -export const NewTermsFieldsEditComponent: 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 NewTermsFieldsEdit = React.memo(NewTermsFieldsEditComponent); +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 0000000000000..07c31dc43ebdb --- /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,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; 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 { UseField } from '../../../../shared_imports'; +import { NewTermsFieldsEditField } from './new_terms_fields_edit_field'; + +interface NewTermsFieldsEditProps { + path: string; + browserFields: DataViewFieldBase[]; +} + +export function NewTermsFieldsEdit({ path, browserFields }: NewTermsFieldsEditProps): JSX.Element { + const componentProps = useMemo(() => ({ browserFields }), [browserFields]); + + return ( + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit_field.tsx new file mode 100644 index 0000000000000..f07b85c089529 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit_field.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 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 = 'newTermsFieldEdit'; + +export const NewTermsFieldsEditFieldComponent: 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 NewTermsFieldsEditField = React.memo(NewTermsFieldsEditFieldComponent); 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 0000000000000..f2458bbb5a4e1 --- /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 057ecbdae5fc8..bf019bd43f2fd 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/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx index 3d35910b4aaad..418b4a845d1dd 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 @@ -77,8 +77,6 @@ import { EqlQueryBar } from '../eql_query_bar'; import { DataViewSelectorField } from '../data_view_selector_field'; import { ThreatMatchInput } from '../threatmatch_input'; import { useFetchIndex } from '../../../../common/containers/source'; -import { NewTermsFieldsEdit } from '../../../rule_creation/components/new_terms_fields_edit'; -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 { defaultCustomQuery } from '../../../../detections/pages/detection_engine/rules/utils'; @@ -94,6 +92,8 @@ import { AlertSuppressionEdit } from '../../../rule_creation/components/alert_su 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'; const CommonUseField = getUseField({ component: Field }); @@ -925,22 +925,8 @@ const StepDefineRuleComponent: FC = ({ fullWidth > <> - - + + 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 2197b069de404..7c309ed32a68b 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 } = { @@ -528,7 +529,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: convertHistorySizeToStart(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 623ae20fa484f..77f0d1f64fc10 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 @@ -42,7 +42,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'; @@ -59,6 +58,7 @@ import { } from './rule_definition_section.styles'; import { getQueryLanguageLabel } from './helpers'; import { useDefaultIndexPattern } from '../../hooks/use_default_index_pattern'; +import { convertHistoryStartToSize } from '../../../../common/utils/history_window'; 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/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 0000000000000..084a81e3b9599 --- /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 0000000000000..b9399900721e2 --- /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,53 @@ +/* + * 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, type FormSchema } from '../../../../../../../../shared_imports'; +import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; +import { HistoryWindowStartEditAdapter } from './history_window_start_edit_adapter'; +import type { HistoryWindowStart } from '../../../../../../../../../common/api/detection_engine'; +import { schema } from '../../../../../../../rule_creation_ui/components/step_define_rule/schema'; +import { + convertHistorySizeToStart, + convertHistoryStartToSize, +} from '../../../../../../../../common/utils/history_window'; +import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../../../../../common/constants'; + +export function HistoryWindowStartEditForm(): JSX.Element { + return ( + + ); +} + +interface HistoryWindowFormData { + historyWindowSize: HistoryWindowStart; +} + +function deserializer(defaultValue: FormData): HistoryWindowFormData { + return { + historyWindowSize: defaultValue.history_window_start + ? convertHistoryStartToSize(defaultValue.history_window_start) + : DEFAULT_HISTORY_WINDOW_SIZE, + }; +} + +function serializer(formData: FormData): { history_window_start: HistoryWindowStart } { + return { + history_window_start: convertHistorySizeToStart(formData.historyWindowSize), + }; +} + +export const historyWindowFormSchema = { + historyWindowSize: schema.historyWindowSize, +} as FormSchema<{ + historyWindowSize: HistoryWindowStart; +}>; 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 index 9f029a7f46f9d..0352a9747a778 100644 --- 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 @@ -5,8 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; -import { UseField } from '../../../../../../../../shared_imports'; +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'; @@ -22,18 +21,5 @@ export function NewTermsFieldsEditAdapter({ const { dataView } = useDiffableRuleDataView(finalDiffableRule); const termsAggregationFields = useTermsAggregationFields(dataView?.fields || []); - const componentProps = useMemo( - () => ({ - browserFields: termsAggregationFields, - }), - [termsAggregationFields] - ); - - return ( - - ); + return ; } 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 12e7bbea9a20a..3acd7c3050fbb 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 bf3e95b6bced8..c14d528760873 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,10 @@ 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'; interface NewTermsRuleFieldEditProps { @@ -22,6 +23,8 @@ export function NewTermsRuleFieldEdit({ fieldName }: NewTermsRuleFieldEditProps) return ; case 'data_source': return ; + case 'history_window_start': + return ; case 'kql_query': return ; case 'new_terms_fields': 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 88e1411a5e0bc..518c18a59a413 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 > ({ historyWindowSize: 'history_window_start' in rule && rule.history_window_start ? convertHistoryStartToSize(rule.history_window_start) - : '7d', + : 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 315e89564aae2..e2f12aa30790e 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 @@ -276,10 +276,10 @@ export const ESQL_QUERY_BAR = '[data-test-subj="detectionEngineStepDefineRuleEsq 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'; From 1170e92f4ff949f76120521f9c149d673e5d2f1e Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Fri, 15 Nov 2024 15:54:37 +0100 Subject: [PATCH 03/12] Add `assertUnreachable` for type safety --- .../three_way_diff/final_edit/new_terms_rule_field_edit.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 c14d528760873..f47607abe3844 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 @@ -12,6 +12,7 @@ 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; @@ -30,6 +31,6 @@ export function NewTermsRuleFieldEdit({ fieldName }: NewTermsRuleFieldEditProps) case 'new_terms_fields': return ; default: - return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented + return assertUnreachable(fieldName); } } From a67e1b0385ce65ffaaef5f3ef0348131dace3724 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Fri, 15 Nov 2024 15:59:42 +0100 Subject: [PATCH 04/12] Removed commented out code --- .../components/step_define_rule/index.tsx | 13 ------------- 1 file changed, 13 deletions(-) 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 418b4a845d1dd..2343d40bb95fe 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 @@ -256,19 +256,6 @@ 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); From a9591c7b3521cf55f3bc6747feda1fce6e12d81c Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Mon, 18 Nov 2024 11:42:39 +0100 Subject: [PATCH 05/12] Fix validation for `new_terms_fields` --- .../components/step_define_rule/schema.tsx | 31 ++--------------- .../new_terms_fields_validator_factory.ts | 34 +++++++++++++++++++ .../new_terms_fields_edit_form.tsx | 23 +++++++++++-- 3 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts 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..150ff89c4ae59 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 @@ -25,7 +25,6 @@ import { 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 { FieldValueQueryBar } from '../query_bar'; import type { ERROR_CODE, FormSchema, ValidationFunc } from '../../../../shared_imports'; @@ -53,6 +52,7 @@ 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 { newTermsFieldsValidationFactory } from '../../validators/new_terms_fields_validator_factory'; export const schema: FormSchema = { index: { @@ -580,34 +580,7 @@ export const schema: FormSchema = { 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); + return newTermsFieldsValidationFactory(...args); }, }, ], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts new file mode 100644 index 0000000000000..bdbe71f47f499 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { fieldValidators, type ERROR_CODE, type ValidationFunc } from '../../../shared_imports'; +import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../common/constants'; + +export function newTermsFieldsValidationFactory( + ...args: Parameters +): ReturnType> | undefined { + return ( + fieldValidators.emptyField( + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin', + { + defaultMessage: 'A minimum of one field is required.', + } + ) + )(...args) ?? + 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) + ); +} 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 index 0eb5b311d7327..5ef5448898d20 100644 --- 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 @@ -6,11 +6,17 @@ */ import React from 'react'; -import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; +import type { + ERROR_CODE, + FormData, + FormSchema, + ValidationFunc, +} from '../../../../../../../../shared_imports'; import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; import { NewTermsFieldsEditAdapter } from './new_terms_fields_edit_adapter'; import { type NewTermsFields } from '../../../../../../../../../common/api/detection_engine'; import { schema } from '../../../../../../../rule_creation_ui/components/step_define_rule/schema'; +import { newTermsFieldsValidationFactory } from '../../../../../../../rule_creation_ui/validators/new_terms_fields_validator_factory'; export function NewTermsFieldsEditForm(): JSX.Element { return ( @@ -39,6 +45,19 @@ function serializer(formData: FormData): { new_terms_fields: NewTermsFields } { }; } -export const newTermsFormSchema = { newTermsFields: schema.newTermsFields } as FormSchema<{ +export const newTermsFormSchema = { + newTermsFields: { + ...schema.newTermsFields, + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + return newTermsFieldsValidationFactory(...args); + }, + }, + ], + }, +} as FormSchema<{ newTermsFields: NewTermsFields; }>; From 2bfd93777d3b219a62ba0e1fcbd988a0503526ec Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Mon, 18 Nov 2024 12:26:19 +0100 Subject: [PATCH 06/12] Fix validation for `history_window_start` --- .../components/step_define_rule/schema.tsx | 22 ++------- .../history_window_start_validator_factory.ts | 49 +++++++++++++++++++ .../new_terms_fields_validator_factory.ts | 2 +- .../history_window_start_edit_form.tsx | 17 ++++++- .../new_terms_fields_edit_form.tsx | 4 +- 5 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/history_window_start_validator_factory.ts 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 150ff89c4ae59..a5f9d764b8a9f 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 @@ -52,7 +52,8 @@ 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 { newTermsFieldsValidationFactory } from '../../validators/new_terms_fields_validator_factory'; +import { newTermsFieldsValidatorFactory } from '../../validators/new_terms_fields_validator_factory'; +import { historyWindowStartValidationFactory } from '../../validators/history_window_start_validator_factory'; export const schema: FormSchema = { index: { @@ -580,7 +581,7 @@ export const schema: FormSchema = { return; } - return newTermsFieldsValidationFactory(...args); + return newTermsFieldsValidatorFactory(...args); }, }, ], @@ -603,27 +604,14 @@ export const schema: FormSchema = { validator: ( ...args: Parameters ): ReturnType> | undefined => { - const [{ path, formData }] = args; + const [{ 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.', - } - ), - }; - } + return historyWindowStartValidationFactory(...args); }, }, ], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/history_window_start_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/history_window_start_validator_factory.ts new file mode 100644 index 0000000000000..5db4e6685dd1c --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/history_window_start_validator_factory.ts @@ -0,0 +1,49 @@ +/* + * 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 type { FieldHook } from '../../../shared_imports'; +import { type ERROR_CODE, type ValidationFunc } from '../../../shared_imports'; + +export function historyWindowStartValidationFactory( + ...args: Parameters +): ReturnType> | undefined { + const [{ path, form }] = args; + + const field = form.getFields()[path] as FieldHook | undefined; + const value = field?.value ?? ''; + + const numberMatchResult = value.match(/\d+/g); + + if (numberMatchResult === null) { + return { + code: 'ERR_NOT_INT_NUMBER', + path, + message: i18n.translate( + 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errNumber', + { + defaultMessage: 'History window size must be a positive number.', + } + ), + }; + } + + const numericValue = parseInt(numberMatchResult[0], 10); + + if (numericValue <= 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.', + } + ), + }; + } +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts index bdbe71f47f499..784faa1f74961 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { fieldValidators, type ERROR_CODE, type ValidationFunc } from '../../../shared_imports'; import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../common/constants'; -export function newTermsFieldsValidationFactory( +export function newTermsFieldsValidatorFactory( ...args: Parameters ): ReturnType> | undefined { 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 index b9399900721e2..fca3605a7126c 100644 --- 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 @@ -6,6 +6,7 @@ */ import React from 'react'; +import type { ERROR_CODE, ValidationFunc } from '../../../../../../../../shared_imports'; import { type FormData, type FormSchema } from '../../../../../../../../shared_imports'; import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; import { HistoryWindowStartEditAdapter } from './history_window_start_edit_adapter'; @@ -16,6 +17,7 @@ import { convertHistoryStartToSize, } from '../../../../../../../../common/utils/history_window'; import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../../../../../common/constants'; +import { historyWindowStartValidationFactory } from '../../../../../../../rule_creation_ui/validators/history_window_start_validator_factory'; export function HistoryWindowStartEditForm(): JSX.Element { return ( @@ -47,7 +49,20 @@ function serializer(formData: FormData): { history_window_start: HistoryWindowSt } export const historyWindowFormSchema = { - historyWindowSize: schema.historyWindowSize, + historyWindowSize: { + /* + For some reason, TS complains that schema.historyWindowSize can be a string, which is incorrect. Nevertheless, adding a runtime check to ensure it's an object and make TS happy. + */ + ...(typeof schema.historyWindowSize === 'object' ? schema.historyWindowSize : {}), + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => + historyWindowStartValidationFactory(...args), + }, + ], + }, } as FormSchema<{ historyWindowSize: HistoryWindowStart; }>; 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 index 5ef5448898d20..3dd9c3b291511 100644 --- 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 @@ -16,7 +16,7 @@ import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; import { NewTermsFieldsEditAdapter } from './new_terms_fields_edit_adapter'; import { type NewTermsFields } from '../../../../../../../../../common/api/detection_engine'; import { schema } from '../../../../../../../rule_creation_ui/components/step_define_rule/schema'; -import { newTermsFieldsValidationFactory } from '../../../../../../../rule_creation_ui/validators/new_terms_fields_validator_factory'; +import { newTermsFieldsValidatorFactory } from '../../../../../../../rule_creation_ui/validators/new_terms_fields_validator_factory'; export function NewTermsFieldsEditForm(): JSX.Element { return ( @@ -53,7 +53,7 @@ export const newTermsFormSchema = { validator: ( ...args: Parameters ): ReturnType> | undefined => { - return newTermsFieldsValidationFactory(...args); + return newTermsFieldsValidatorFactory(...args); }, }, ], From 65d5d9aab6e41f2c88417cd4f5024c79d6bba0de Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Mon, 18 Nov 2024 14:55:27 +0100 Subject: [PATCH 07/12] Refactor to address code review feedback --- .../public/common/utils/date_math.ts | 29 ++++++++++++++++++ .../public/common/utils/history_window.ts | 28 ----------------- .../history_window_start_edit.tsx | 4 +-- .../new_terms_fields_edit.tsx | 20 ++++++------- ...t_field.tsx => new_terms_fields_field.tsx} | 30 +++++++++---------- .../components/step_define_rule/index.tsx | 6 +++- .../pages/rule_creation/helpers.ts | 4 +-- .../rule_details/rule_definition_section.tsx | 7 +++-- .../history_window_start_edit_form.tsx | 10 +++---- .../new_terms_fields_edit_adapter.tsx | 5 ++-- .../new_terms_fields_edit_form.tsx | 23 ++------------ .../pages/detection_engine/rules/helpers.tsx | 4 +-- 12 files changed, 78 insertions(+), 92 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/utils/date_math.ts delete mode 100644 x-pack/plugins/security_solution/public/common/utils/history_window.ts rename x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/{new_terms_fields_edit_field.tsx => new_terms_fields_field.tsx} (55%) 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 0000000000000..28dbe15955a27 --- /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/common/utils/history_window.ts b/x-pack/plugins/security_solution/public/common/utils/history_window.ts deleted file mode 100644 index 01f85390ffcf9..0000000000000 --- a/x-pack/plugins/security_solution/public/common/utils/history_window.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/** - * Converts a history start string to a history size string by removing the 'now-' prefix. - * - * @param historyStart - History start string to convert. For example, "now-30d". - * @returns Converted size string. For example, "30d". - */ -export const convertHistoryStartToSize = (historyStart: string) => { - if (historyStart.startsWith('now-')) { - return historyStart.substring(4); - } else { - return historyStart; - } -}; - -/** - * Converts a history size string to a history start string by adding the 'now-' prefix. - * - * @param historySize - History size string to convert. For example, "30d". - * @returns Converted start string. For example, "now-30d". - */ -export const convertHistorySizeToStart = (historySize: string) => `now-${historySize}`; 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 index 1a4e17055ecf3..1321ef027d88f 100644 --- 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 @@ -9,7 +9,7 @@ import React from 'react'; import { ScheduleItemField } from '../schedule_item_field'; import { UseField } from '../../../../shared_imports'; -const componentProps = { +const COMPONENT_PROPS = { idAria: 'historyWindowSize', dataTestSubj: 'historyWindowSize', timeTypes: ['m', 'h', 'd'], @@ -20,5 +20,5 @@ interface HistoryWindowStartEditProps { } export function HistoryWindowStartEdit({ path }: HistoryWindowStartEditProps): JSX.Element { - return ; + return ; } 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 index 07c31dc43ebdb..f4eed2a9a6e8d 100644 --- 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 @@ -5,20 +5,18 @@ * 2.0. */ -import React, { useMemo } from 'react'; -import type { DataViewFieldBase } from '@kbn/es-query'; +import React, { memo } from 'react'; import { UseField } from '../../../../shared_imports'; -import { NewTermsFieldsEditField } from './new_terms_fields_edit_field'; +import { NewTermsFieldsField } from './new_terms_fields_field'; interface NewTermsFieldsEditProps { path: string; - browserFields: DataViewFieldBase[]; + fieldNames: string[]; } -export function NewTermsFieldsEdit({ path, browserFields }: NewTermsFieldsEditProps): JSX.Element { - const componentProps = useMemo(() => ({ browserFields }), [browserFields]); - - return ( - - ); -} +export const NewTermsFieldsEdit = memo(function NewTermsFieldsEdit({ + path, + fieldNames, +}: NewTermsFieldsEditProps): JSX.Element { + return ; +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_field.tsx similarity index 55% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit_field.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_field.tsx index f07b85c089529..22c61d6f7fd4d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit_field.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_field.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import type { DataViewFieldBase } from '@kbn/es-query'; import type { FieldHook } from '../../../../shared_imports'; @@ -13,7 +13,7 @@ import { Field } from '../../../../shared_imports'; import { NEW_TERMS_FIELD_PLACEHOLDER } from './translations'; interface NewTermsFieldsProps { - browserFields: DataViewFieldBase[]; + fieldNames: DataViewFieldBase[]; field: FieldHook; } @@ -21,22 +21,20 @@ const FIELD_COMBO_BOX_WIDTH = 410; const fieldDescribedByIds = 'newTermsFieldEdit'; -export const NewTermsFieldsEditFieldComponent: React.FC = ({ - browserFields, +const NewTermsFieldsEditFieldComponent: React.FC = ({ + fieldNames, 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] - ); + const fieldEuiFieldProps = { + fullWidth: true, + noSuggestions: false, + options: fieldNames.map((name) => ({ label: name })), + placeholder: NEW_TERMS_FIELD_PLACEHOLDER, + onCreateOption: undefined, + style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, + }; + return ; }; -export const NewTermsFieldsEditField = React.memo(NewTermsFieldsEditFieldComponent); +export const NewTermsFieldsField = React.memo(NewTermsFieldsEditFieldComponent); 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 2343d40bb95fe..008dd5d0e5501 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 @@ -258,6 +258,10 @@ const StepDefineRuleComponent: FC = ({ ); const termsAggregationFields = useTermsAggregationFields(indexPattern.fields); + const termsAggregationFieldNames = useMemo( + () => termsAggregationFields.map((field) => field.name), + [termsAggregationFields] + ); const [threatIndexPatternsLoading, { indexPatterns: threatIndexPatterns }] = useFetchIndex(threatIndex); @@ -912,7 +916,7 @@ const StepDefineRuleComponent: FC = ({ fullWidth > <> - + diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts index 396ac608d1831..0cbf0e19d96fb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts @@ -70,7 +70,7 @@ 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 { convertHistorySizeToStart } from '../../../../common/utils/history_window'; +import { convertDurationToDateMath } from '../../../../common/utils/date_math'; export const getTimeTypeValue = (time: string): { unit: Unit; value: number } => { const timeObj: { unit: Unit; value: number } = { @@ -529,7 +529,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: convertHistorySizeToStart(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 77f0d1f64fc10..2851a4d8a8720 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 @@ -58,7 +58,8 @@ import { } from './rule_definition_section.styles'; import { getQueryLanguageLabel } from './helpers'; import { useDefaultIndexPattern } from '../../hooks/use_default_index_pattern'; -import { convertHistoryStartToSize } from '../../../../common/utils/history_window'; +import { convertDateMathToDuration } from '../../../../common/utils/date_math'; +import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../common/constants'; interface SavedQueryNameProps { savedQueryName: string; @@ -404,7 +405,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_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 index fca3605a7126c..7dcbaeb7dc39b 100644 --- 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 @@ -13,9 +13,9 @@ import { HistoryWindowStartEditAdapter } from './history_window_start_edit_adapt import type { HistoryWindowStart } from '../../../../../../../../../common/api/detection_engine'; import { schema } from '../../../../../../../rule_creation_ui/components/step_define_rule/schema'; import { - convertHistorySizeToStart, - convertHistoryStartToSize, -} from '../../../../../../../../common/utils/history_window'; + convertDurationToDateMath, + convertDateMathToDuration, +} from '../../../../../../../../common/utils/date_math'; import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../../../../../common/constants'; import { historyWindowStartValidationFactory } from '../../../../../../../rule_creation_ui/validators/history_window_start_validator_factory'; @@ -37,14 +37,14 @@ interface HistoryWindowFormData { function deserializer(defaultValue: FormData): HistoryWindowFormData { return { historyWindowSize: defaultValue.history_window_start - ? convertHistoryStartToSize(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: convertHistorySizeToStart(formData.historyWindowSize), + history_window_start: convertDurationToDateMath(formData.historyWindowSize), }; } 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 index 0352a9747a778..476bf9869bd96 100644 --- 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 @@ -19,7 +19,8 @@ export function NewTermsFieldsEditAdapter({ finalDiffableRule, }: NewTermsFieldsEditAdapterProps): JSX.Element { const { dataView } = useDiffableRuleDataView(finalDiffableRule); - const termsAggregationFields = useTermsAggregationFields(dataView?.fields || []); + const termsAggregationFields = useTermsAggregationFields(dataView?.fields ?? []); + const fieldNames = termsAggregationFields.map((field) => field.name); - return ; + 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 index 3dd9c3b291511..e83aef0bb442d 100644 --- 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 @@ -8,7 +8,6 @@ import React from 'react'; import type { ERROR_CODE, - FormData, FormSchema, ValidationFunc, } from '../../../../../../../../shared_imports'; @@ -23,30 +22,12 @@ export function NewTermsFieldsEditForm(): JSX.Element { ); } -interface NewTermsFieldsFormData { - newTermsFields: string[]; -} - -function deserializer(defaultValue: FormData): NewTermsFieldsFormData { - return { - newTermsFields: defaultValue.new_terms_fields, - }; -} - -function serializer(formData: FormData): { new_terms_fields: NewTermsFields } { - return { - new_terms_fields: formData.newTermsFields, - }; -} - export const newTermsFormSchema = { - newTermsFields: { + new_terms_fields: { ...schema.newTermsFields, validations: [ { @@ -59,5 +40,5 @@ export const newTermsFormSchema = { ], }, } as FormSchema<{ - newTermsFields: NewTermsFields; + new_terms_fields: NewTermsFields; }>; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index 7b7541f7588d6..b244ecba07941 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -47,7 +47,7 @@ import { DataSourceType, AlertSuppressionDurationType } from './types'; import { severityOptions } from '../../../../detection_engine/rule_creation_ui/components/step_about_rule/data'; import { DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY } from '../../../../../common/detection_engine/constants'; import type { RuleAction, RuleResponse } from '../../../../../common/api/detection_engine'; -import { convertHistoryStartToSize } from '../../../../common/utils/history_window'; +import { convertDateMathToDuration } from '../../../../common/utils/date_math'; import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../common/constants'; export interface GetStepsData { @@ -162,7 +162,7 @@ export const getDefineStepsData = (rule: RuleResponse): DefineStepRule => ({ 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) + ? convertDateMathToDuration(rule.history_window_start) : DEFAULT_HISTORY_WINDOW_SIZE, shouldLoadQueryDynamically: Boolean(rule.type === 'saved_query' && rule.saved_id), [ALERT_SUPPRESSION_FIELDS_FIELD_NAME]: From e7942e24c7f80b9ca67c3659ec724ccb6f1b30c0 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Mon, 18 Nov 2024 19:52:10 +0100 Subject: [PATCH 08/12] Move validation into components to enhance reusability --- .../history_window_start_edit.tsx | 38 +++++++++- .../new_terms_fields_edit.tsx | 39 +++++++++- .../components/rule_preview/helpers.ts | 2 +- .../components/step_define_rule/index.tsx | 25 ++++--- .../components/step_define_rule/schema.tsx | 65 +---------------- .../use_persistent_new_terms_state.tsx | 73 +++++++++++++++++++ .../history_window_start_edit_form.tsx | 26 +------ .../new_terms_fields_edit_form.tsx | 30 +------- 8 files changed, 169 insertions(+), 129 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_persistent_new_terms_state.tsx 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 index 1321ef027d88f..96b2414653b53 100644 --- 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 @@ -6,8 +6,12 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { ScheduleItemField } from '../schedule_item_field'; -import { UseField } from '../../../../shared_imports'; +import type { ERROR_CODE, ValidationFunc } from '../../../../shared_imports'; +import { type FieldConfig, UseField } from '../../../../shared_imports'; +import { type HistoryWindowStart } from '../../../../../common/api/detection_engine'; +import { historyWindowStartValidationFactory } from '../../../rule_creation_ui/validators/history_window_start_validator_factory'; const COMPONENT_PROPS = { idAria: 'historyWindowSize', @@ -20,5 +24,35 @@ interface HistoryWindowStartEditProps { } export function HistoryWindowStartEdit({ path }: HistoryWindowStartEditProps): JSX.Element { - return ; + return ( + + ); } + +const HISTORY_WINDOW_START_FIELD_CONFIG: FieldConfig = { + 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 => + historyWindowStartValidationFactory(...args), + }, + ], +}; 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 index f4eed2a9a6e8d..ea2fc3a2b2ff3 100644 --- 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 @@ -6,8 +6,11 @@ */ import React, { memo } from 'react'; -import { UseField } from '../../../../shared_imports'; +import { i18n } from '@kbn/i18n'; +import type { ERROR_CODE, ValidationFunc } from '../../../../shared_imports'; +import { FIELD_TYPES, UseField } from '../../../../shared_imports'; import { NewTermsFieldsField } from './new_terms_fields_field'; +import { newTermsFieldsValidatorFactory } from '../../../rule_creation_ui/validators/new_terms_fields_validator_factory'; interface NewTermsFieldsEditProps { path: string; @@ -18,5 +21,37 @@ export const NewTermsFieldsEdit = memo(function NewTermsFieldsEdit({ path, fieldNames, }: NewTermsFieldsEditProps): JSX.Element { - return ; + return ( + + ); }); + +const NEW_TERMS_FIELDS_CONFIG = { + 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 => { + return newTermsFieldsValidatorFactory(...args); + }, + }, + ], +}; 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 e6f9945737444..01a4833e9320d 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 008dd5d0e5501..37529f28e4017 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 @@ -94,6 +94,7 @@ import { usePersistentAlertSuppressionState } from './use_persistent_alert_suppr 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'; const CommonUseField = getUseField({ component: Field }); @@ -368,6 +369,12 @@ const StepDefineRuleComponent: FC = ({ }, [ruleType, previousRuleType, getFields]); usePersistentAlertSuppressionState({ form }); + usePersistentNewTermsState({ + form, + ruleTypePath: 'ruleType', + newTermsFieldsPath: 'newTermsFields', + historyWindowStartPath: 'historyWindowSize', + }); // if saved query failed to load: // - reset shouldLoadFormDynamically to false, as non existent query cannot be used for loading and execution @@ -910,16 +917,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 a5f9d764b8a9f..953b46eda0dc3 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 @@ -20,7 +20,6 @@ import { isEqlRule, isEqlSequenceQuery, isEsqlRule, - isNewTermsRule, isThreatMatchRule, isThresholdRule, isSuppressionRuleConfiguredWithGroupBy, @@ -52,8 +51,6 @@ 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 { newTermsFieldsValidatorFactory } from '../../validators/new_terms_fields_validator_factory'; -import { historyWindowStartValidationFactory } from '../../validators/history_window_start_validator_factory'; export const schema: FormSchema = { index: { @@ -556,66 +553,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 newTermsFieldsValidatorFactory(...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 [{ formData }] = args; - const needsValidation = isNewTermsRule(formData.ruleType); - - if (!needsValidation) { - return; - } - - return historyWindowStartValidationFactory(...args); - }, - }, - ], - }, + 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 0000000000000..0789030e6f4a1 --- /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 LastNewTermsStateState { + 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_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 index 7dcbaeb7dc39b..4d989cd505b12 100644 --- 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 @@ -6,24 +6,21 @@ */ import React from 'react'; -import type { ERROR_CODE, ValidationFunc } from '../../../../../../../../shared_imports'; -import { type FormData, type FormSchema } from '../../../../../../../../shared_imports'; +import { type FormData } from '../../../../../../../../shared_imports'; import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; import { HistoryWindowStartEditAdapter } from './history_window_start_edit_adapter'; import type { HistoryWindowStart } from '../../../../../../../../../common/api/detection_engine'; -import { schema } from '../../../../../../../rule_creation_ui/components/step_define_rule/schema'; import { convertDurationToDateMath, convertDateMathToDuration, } from '../../../../../../../../common/utils/date_math'; import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../../../../../common/constants'; -import { historyWindowStartValidationFactory } from '../../../../../../../rule_creation_ui/validators/history_window_start_validator_factory'; export function HistoryWindowStartEditForm(): JSX.Element { return ( @@ -48,21 +45,4 @@ function serializer(formData: FormData): { history_window_start: HistoryWindowSt }; } -export const historyWindowFormSchema = { - historyWindowSize: { - /* - For some reason, TS complains that schema.historyWindowSize can be a string, which is incorrect. Nevertheless, adding a runtime check to ensure it's an object and make TS happy. - */ - ...(typeof schema.historyWindowSize === 'object' ? schema.historyWindowSize : {}), - validations: [ - { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => - historyWindowStartValidationFactory(...args), - }, - ], - }, -} as FormSchema<{ - historyWindowSize: HistoryWindowStart; -}>; +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_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 index e83aef0bb442d..fd4995b395b90 100644 --- 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 @@ -6,39 +6,13 @@ */ import React from 'react'; -import type { - ERROR_CODE, - FormSchema, - ValidationFunc, -} from '../../../../../../../../shared_imports'; import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; import { NewTermsFieldsEditAdapter } from './new_terms_fields_edit_adapter'; -import { type NewTermsFields } from '../../../../../../../../../common/api/detection_engine'; -import { schema } from '../../../../../../../rule_creation_ui/components/step_define_rule/schema'; -import { newTermsFieldsValidatorFactory } from '../../../../../../../rule_creation_ui/validators/new_terms_fields_validator_factory'; export function NewTermsFieldsEditForm(): JSX.Element { return ( - + ); } -export const newTermsFormSchema = { - new_terms_fields: { - ...schema.newTermsFields, - validations: [ - { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - return newTermsFieldsValidatorFactory(...args); - }, - }, - ], - }, -} as FormSchema<{ - new_terms_fields: NewTermsFields; -}>; +const schema = {}; From 4284e9a46b56ae2d0ae32168be882523691981c9 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Tue, 19 Nov 2024 18:03:10 +0100 Subject: [PATCH 09/12] Address more code review feedback --- .../history_window_start_edit.tsx | 44 ++++++++++------- .../history_window_start_edit/translations.ts | 36 ++++++++++++++ .../new_terms_fields_edit.tsx | 30 ++++-------- .../new_terms_fields_field.tsx | 14 +++--- .../new_terms_fields_edit/translations.ts | 34 ++++++++++++- .../components/description_step/index.tsx | 8 +++ .../history_window_start_validator_factory.ts | 49 ------------------- .../new_terms_fields_validator_factory.ts | 34 ------------- .../new_terms_fields/new_terms_fields.tsx | 4 -- 9 files changed, 117 insertions(+), 136 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/history_window_start_validator_factory.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts 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 index 96b2414653b53..46bafe91b99ad 100644 --- 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 @@ -6,12 +6,11 @@ */ import React from 'react'; -import { i18n } from '@kbn/i18n'; import { ScheduleItemField } from '../schedule_item_field'; -import type { ERROR_CODE, ValidationFunc } from '../../../../shared_imports'; +import type { ValidationFunc } from '../../../../shared_imports'; import { type FieldConfig, UseField } from '../../../../shared_imports'; import { type HistoryWindowStart } from '../../../../../common/api/detection_engine'; -import { historyWindowStartValidationFactory } from '../../../rule_creation_ui/validators/history_window_start_validator_factory'; +import * as i18n from './translations'; const COMPONENT_PROPS = { idAria: 'historyWindowSize', @@ -35,24 +34,31 @@ export function HistoryWindowStartEdit({ path }: HistoryWindowStartEditProps): J } const HISTORY_WINDOW_START_FIELD_CONFIG: FieldConfig = { - 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.", - } - ), + label: i18n.HISTORY_WINDOW_START_LABEL, + helpText: i18n.HELP_TEXT, validations: [ { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => - historyWindowStartValidationFactory(...args), + validator: (...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/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 0000000000000..75ff11fe5e1b6 --- /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/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 index ea2fc3a2b2ff3..c4064c2343da1 100644 --- 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 @@ -6,11 +6,10 @@ */ import React, { memo } from 'react'; -import { i18n } from '@kbn/i18n'; -import type { ERROR_CODE, ValidationFunc } from '../../../../shared_imports'; -import { FIELD_TYPES, UseField } from '../../../../shared_imports'; +// import { i18n } from '@kbn/i18n'; +import { FIELD_TYPES, UseField, fieldValidators } from '../../../../shared_imports'; import { NewTermsFieldsField } from './new_terms_fields_field'; -import { newTermsFieldsValidatorFactory } from '../../../rule_creation_ui/validators/new_terms_fields_validator_factory'; +import * as i18n from './translations'; interface NewTermsFieldsEditProps { path: string; @@ -33,25 +32,14 @@ export const NewTermsFieldsEdit = memo(function NewTermsFieldsEdit({ const NEW_TERMS_FIELDS_CONFIG = { 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.', - } - ), + label: i18n.NEW_TERMS_FIELDS_LABEL, + helpText: i18n.HELP_TEXT, validations: [ { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - return newTermsFieldsValidatorFactory(...args); - }, + 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 index 22c61d6f7fd4d..f2a9fd43684a9 100644 --- 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 @@ -5,12 +5,12 @@ * 2.0. */ -import React from 'react'; +import React, { memo } 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'; +import { PLACEHOLDER } from './translations'; interface NewTermsFieldsProps { fieldNames: DataViewFieldBase[]; @@ -21,20 +21,18 @@ const FIELD_COMBO_BOX_WIDTH = 410; const fieldDescribedByIds = 'newTermsFieldEdit'; -const NewTermsFieldsEditFieldComponent: React.FC = ({ +export const NewTermsFieldsField = memo(function NewTermsFieldsField({ fieldNames, field, -}: NewTermsFieldsProps) => { +}: NewTermsFieldsProps): JSX.Element { const fieldEuiFieldProps = { fullWidth: true, noSuggestions: false, options: fieldNames.map((name) => ({ label: name })), - placeholder: NEW_TERMS_FIELD_PLACEHOLDER, + placeholder: PLACEHOLDER, onCreateOption: undefined, style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, }; return ; -}; - -export const NewTermsFieldsField = React.memo(NewTermsFieldsEditFieldComponent); +}); 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 index 1bf73b4a46b48..4eb18b9a318e0 100644 --- 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 @@ -6,10 +6,42 @@ */ import { i18n } from '@kbn/i18n'; +import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../../common/constants'; -export const NEW_TERMS_FIELD_PLACEHOLDER = i18n.translate( +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_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..814bb94b8e709 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'; const DescriptionListContainer = styled(EuiDescriptionList)` max-width: 600px; @@ -339,6 +341,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); @@ -355,6 +360,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/validators/history_window_start_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/history_window_start_validator_factory.ts deleted file mode 100644 index 5db4e6685dd1c..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/history_window_start_validator_factory.ts +++ /dev/null @@ -1,49 +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'; -import type { FieldHook } from '../../../shared_imports'; -import { type ERROR_CODE, type ValidationFunc } from '../../../shared_imports'; - -export function historyWindowStartValidationFactory( - ...args: Parameters -): ReturnType> | undefined { - const [{ path, form }] = args; - - const field = form.getFields()[path] as FieldHook | undefined; - const value = field?.value ?? ''; - - const numberMatchResult = value.match(/\d+/g); - - if (numberMatchResult === null) { - return { - code: 'ERR_NOT_INT_NUMBER', - path, - message: i18n.translate( - 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errNumber', - { - defaultMessage: 'History window size must be a positive number.', - } - ), - }; - } - - const numericValue = parseInt(numberMatchResult[0], 10); - - if (numericValue <= 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.', - } - ), - }; - } -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts deleted file mode 100644 index 784faa1f74961..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/new_terms_fields_validator_factory.ts +++ /dev/null @@ -1,34 +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'; -import { fieldValidators, type ERROR_CODE, type ValidationFunc } from '../../../shared_imports'; -import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../common/constants'; - -export function newTermsFieldsValidatorFactory( - ...args: Parameters -): ReturnType> | undefined { - return ( - fieldValidators.emptyField( - i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin', - { - defaultMessage: 'A minimum of one field is required.', - } - ) - )(...args) ?? - 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) - ); -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/new_terms_fields/new_terms_fields.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/new_terms_fields/new_terms_fields.tsx index 5dca3fa851e51..b77f7b0736482 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/new_terms_fields/new_terms_fields.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/new_terms_fields/new_terms_fields.tsx @@ -16,10 +16,6 @@ interface NewTermsFieldsReadOnlyProps { } export function NewTermsFieldsReadOnly({ newTermsFields }: NewTermsFieldsReadOnlyProps) { - if (!newTermsFields.length) { - return null; - } - return ( Date: Wed, 20 Nov 2024 15:10:39 +0100 Subject: [PATCH 10/12] Address new code review feedback --- .../history_window_start_edit.tsx | 24 ++------------ .../validate_history_window_start.ts | 31 +++++++++++++++++++ .../new_terms_fields_edit.tsx | 4 +-- .../new_terms_fields_field.tsx | 6 ++-- .../translations/translations/fr-FR.json | 6 ++-- .../translations/translations/ja-JP.json | 6 ++-- .../translations/translations/zh-CN.json | 6 ++-- 7 files changed, 44 insertions(+), 39 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/validate_history_window_start.ts 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 index 46bafe91b99ad..c876fe926ce4f 100644 --- 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 @@ -7,10 +7,10 @@ import React from 'react'; import { ScheduleItemField } from '../schedule_item_field'; -import type { ValidationFunc } from '../../../../shared_imports'; 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', @@ -38,27 +38,7 @@ const HISTORY_WINDOW_START_FIELD_CONFIG: FieldConfig = { helpText: i18n.HELP_TEXT, validations: [ { - validator: (...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, - }; - } - }, + validator: validateHistoryWindowStart, }, ], }; 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 0000000000000..4e274683ad08b --- /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/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 index c4064c2343da1..f50c62ed8f7a0 100644 --- 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 @@ -6,8 +6,7 @@ */ import React, { memo } from 'react'; -// import { i18n } from '@kbn/i18n'; -import { FIELD_TYPES, UseField, fieldValidators } from '../../../../shared_imports'; +import { UseField, fieldValidators } from '../../../../shared_imports'; import { NewTermsFieldsField } from './new_terms_fields_field'; import * as i18n from './translations'; @@ -31,7 +30,6 @@ export const NewTermsFieldsEdit = memo(function NewTermsFieldsEdit({ }); const NEW_TERMS_FIELDS_CONFIG = { - type: FIELD_TYPES.COMBO_BOX, label: i18n.NEW_TERMS_FIELDS_LABEL, helpText: i18n.HELP_TEXT, validations: [ 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 index f2a9fd43684a9..a9c1148402f12 100644 --- 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 @@ -8,8 +8,8 @@ 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 { Field } from '../../../../shared_imports'; import { PLACEHOLDER } from './translations'; interface NewTermsFieldsProps { @@ -34,5 +34,7 @@ export const NewTermsFieldsField = memo(function NewTermsFieldsField({ style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, }; - return ; + return ( + + ); }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 6d1ba23a5f6c4..1f049892c87d5 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -3145,6 +3145,8 @@ "esqlEditor.query.EnableWordWrapLabel": "Ajouter des sauts de ligne aux barres verticales", "esqlEditor.query.errorCount": "{count} {count, plural, one {erreur} other {erreurs}}", "esqlEditor.query.errorsTitle": "Erreurs", + "esqlEditor.query.esqlQueriesCopy": "Copier la requête dans le presse-papier", + "esqlEditor.query.esqlQueriesListRun": "Exécuter la requête", "esqlEditor.query.expandLabel": "Développer", "esqlEditor.query.feedback": "Commentaires", "esqlEditor.query.hideQueriesLabel": "Masquer les recherches récentes", @@ -3154,8 +3156,6 @@ "esqlEditor.query.lineNumber": "Ligne {lineNumber}", "esqlEditor.query.querieshistory.error": "La requête a échouée", "esqlEditor.query.querieshistory.success": "La requête a été exécuté avec succès", - "esqlEditor.query.esqlQueriesCopy": "Copier la requête dans le presse-papier", - "esqlEditor.query.esqlQueriesListRun": "Exécuter la requête", "esqlEditor.query.querieshistoryTable": "Tableau d'historique des recherches", "esqlEditor.query.recentQueriesColumnLabel": "Recherches récentes", "esqlEditor.query.refreshLabel": "Actualiser", @@ -38129,7 +38129,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", @@ -39458,7 +39457,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/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 04bc03184b777..548f1e798363c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3139,6 +3139,8 @@ "esqlEditor.query.EnableWordWrapLabel": "パイプの改行を追加", "esqlEditor.query.errorCount": "{count} {count, plural, other {# 件のエラー}}", "esqlEditor.query.errorsTitle": "エラー", + "esqlEditor.query.esqlQueriesCopy": "クエリをクリップボードにコピー", + "esqlEditor.query.esqlQueriesListRun": "クエリーを実行", "esqlEditor.query.expandLabel": "拡張", "esqlEditor.query.feedback": "フィードバック", "esqlEditor.query.hideQueriesLabel": "最近のクエリーを非表示", @@ -3148,8 +3150,6 @@ "esqlEditor.query.lineNumber": "行{lineNumber}", "esqlEditor.query.querieshistory.error": "クエリ失敗", "esqlEditor.query.querieshistory.success": "クエリは正常に実行されました", - "esqlEditor.query.esqlQueriesCopy": "クエリをクリップボードにコピー", - "esqlEditor.query.esqlQueriesListRun": "クエリーを実行", "esqlEditor.query.querieshistoryTable": "クエリ履歴テーブル", "esqlEditor.query.recentQueriesColumnLabel": "最近のクエリー", "esqlEditor.query.refreshLabel": "更新", @@ -38096,7 +38096,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": "ルールプレビュー", @@ -39424,7 +39423,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/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1d48e398dfd0c..25158b9d9a537 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3095,6 +3095,8 @@ "esqlEditor.query.EnableWordWrapLabel": "在管道符上添加换行符", "esqlEditor.query.errorCount": "{count} 个{count, plural, other {错误}}", "esqlEditor.query.errorsTitle": "错误", + "esqlEditor.query.esqlQueriesCopy": "复制查询到剪贴板", + "esqlEditor.query.esqlQueriesListRun": "运行查询", "esqlEditor.query.expandLabel": "展开", "esqlEditor.query.feedback": "反馈", "esqlEditor.query.hideQueriesLabel": "隐藏最近查询", @@ -3104,8 +3106,6 @@ "esqlEditor.query.lineNumber": "第 {lineNumber} 行", "esqlEditor.query.querieshistory.error": "查询失败", "esqlEditor.query.querieshistory.success": "已成功运行查询", - "esqlEditor.query.esqlQueriesCopy": "复制查询到剪贴板", - "esqlEditor.query.esqlQueriesListRun": "运行查询", "esqlEditor.query.querieshistoryTable": "查询历史记录表", "esqlEditor.query.recentQueriesColumnLabel": "最近查询", "esqlEditor.query.refreshLabel": "刷新", @@ -37490,7 +37490,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": "规则预览", @@ -38812,7 +38811,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 个。", From 3e3fe80f239bf98be306f07f62ac0d3d7dc3cb25 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Tue, 26 Nov 2024 10:08:01 +0100 Subject: [PATCH 11/12] Fix typo --- .../step_define_rule/use_persistent_new_terms_state.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 0789030e6f4a1..61bfa45ba8a72 100644 --- 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 @@ -23,7 +23,7 @@ interface UsePersistentNewTermsStateParams { historyWindowStartPath: string; } -interface LastNewTermsStateState { +interface LastNewTermsState { newTermsFields: NewTermsFields; historyWindowStart: HistoryWindowStart; } @@ -34,7 +34,7 @@ export function usePersistentNewTermsState({ newTermsFieldsPath, historyWindowStartPath, }: UsePersistentNewTermsStateParams): void { - const lastNewTermsState = useRef(); + const lastNewTermsState = useRef(); const [formData] = useFormData({ form, watch: [newTermsFieldsPath, historyWindowStartPath] }); const { From 422118637a55074432d3f99f0661dc4f4a7dc509 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Mon, 16 Dec 2024 13:40:54 +0100 Subject: [PATCH 12/12] Update imports to reflect latest directory structure changes --- .../history_window_start/history_window_start_edit_form.tsx | 2 +- .../fields/new_terms_fields/new_terms_fields_edit_form.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 4d989cd505b12..87ac2fee64e7b 100644 --- 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 @@ -7,7 +7,6 @@ import React from 'react'; import { type FormData } from '../../../../../../../../shared_imports'; -import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; import { HistoryWindowStartEditAdapter } from './history_window_start_edit_adapter'; import type { HistoryWindowStart } from '../../../../../../../../../common/api/detection_engine'; import { @@ -15,6 +14,7 @@ import { 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 ( 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 index fd4995b395b90..9b8d709b27f06 100644 --- 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 @@ -6,7 +6,7 @@ */ import React from 'react'; -import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; +import { RuleFieldEditFormWrapper } from '../../../field_final_side'; import { NewTermsFieldsEditAdapter } from './new_terms_fields_edit_adapter'; export function NewTermsFieldsEditForm(): JSX.Element {