From b0faaa9a6bbea396fb123284e23464cfafd6bf8a Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:33:41 +0000 Subject: [PATCH 1/7] esql highlighted fields --- .../esql_autocomplete/esql_autocomplete.tsx | 2 +- .../components/esql_autocomplete/index.ts | 0 .../esql_fields_select/esql_fields_select.tsx | 71 +++++++++++++++++++ .../components/esql_fields_select/index.ts | 8 +++ .../esql_fields_select/translations.ts | 6 ++ .../components/step_about_rule/index.tsx | 3 +- .../hooks}/use_esql_fields_options.test.ts | 0 .../hooks}/use_esql_fields_options.ts | 6 +- 8 files changed, 91 insertions(+), 5 deletions(-) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation => rule_creation_ui}/components/esql_autocomplete/esql_autocomplete.tsx (97%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation => rule_creation_ui}/components/esql_autocomplete/index.ts (100%) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/esql_fields_select.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/index.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/translations.ts rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation/components/esql_autocomplete => rule_creation_ui/hooks}/use_esql_fields_options.test.ts (100%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_creation/components/esql_autocomplete => rule_creation_ui/hooks}/use_esql_fields_options.ts (90%) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_autocomplete/esql_autocomplete.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_autocomplete/esql_autocomplete.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx index cc524e0c5cb79..a0da0e51a8341 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_autocomplete/esql_autocomplete.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx @@ -11,7 +11,7 @@ import { EuiFormRow, EuiComboBox } from '@elastic/eui'; import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { useEsqlFieldOptions } from './use_esql_fields_options'; +import { useEsqlFieldOptions } from '../../hooks/use_esql_fields_options'; const AS_PLAIN_TEXT = { asPlainText: true }; const COMPONENT_WIDTH = 500; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_autocomplete/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/index.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_autocomplete/index.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/index.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/esql_fields_select.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/esql_fields_select.tsx new file mode 100644 index 0000000000000..5f3407b7f13e8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/esql_fields_select.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import React, { useMemo, useState, useCallback, memo } from 'react'; +import { EuiFormRow, EuiComboBox } from '@elastic/eui'; +import { isEsqlRule } from '../../../../../common/detection_engine/utils'; + +import type { FieldHook } from '../../../../shared_imports'; +import { Field } from '../../../../shared_imports'; + +import { useEsqlFieldOptions } from '../../hooks/use_esql_fields_options'; + +interface EsqlFieldsSelectProps { + dataTestSubj: string; + field: FieldHook; + idAria: string; + isDisabled: boolean; + placeholder?: string; + esqlQuery: string | undefined; +} + +const fieldDescribedByIds = 'detectionEngineStepDefineRuleEsqlFieldsSelect'; + +/** + * Select component, that displays all available(returned from ES|QL query) fields + * Primary use: is to allow user select fields from result that will be used for suppression of possible duplicated alerts + */ +export const EsqlFieldsSelectComponent: React.FC = ({ + field, + dataTestSubj, + idAria, + isDisabled, + placeholder, + esqlQuery, +}: EsqlFieldsSelectProps) => { + const { options, isLoading } = useEsqlFieldOptions(esqlQuery); + + const fieldEuiFieldProps = useMemo( + () => ({ + fullWidth: true, + noSuggestions: false, + options, + placeholder, + onCreateOption: undefined, + isDisabled: isDisabled || isLoading, + }), + [isDisabled, isLoading, options, placeholder] + ); + + return ( + + + + ); +}; + +export const EsqlFieldsSelect = memo(EsqlFieldsSelectComponent); + +EsqlFieldsSelect.displayName = 'EsqlFieldsSelect'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/index.ts new file mode 100644 index 0000000000000..767e309a5b2f3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/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 { EsqlFieldsSelect } from './esql_fields_select'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/translations.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/translations.ts @@ -0,0 +1,6 @@ +/* + * 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. + */ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx index 85f5284f83452..c53a516f2f1fe 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx @@ -37,7 +37,8 @@ import { useFetchIndex } from '../../../../common/containers/source'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; import { useKibana } from '../../../../common/lib/kibana'; import { useRuleIndices } from '../../../rule_management/logic/use_rule_indices'; -import { EsqlAutocomplete } from '../../../rule_creation/components/esql_autocomplete'; +import { EsqlAutocomplete } from '../esql_autocomplete'; +import { EsqlFieldsSelect } from '../esql_fields_select'; import { MultiSelectFieldsAutocomplete } from '../multi_select_fields'; const CommonUseField = getUseField({ component: Field }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_autocomplete/use_esql_fields_options.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_fields_options.test.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_autocomplete/use_esql_fields_options.test.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_fields_options.test.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_autocomplete/use_esql_fields_options.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_fields_options.ts similarity index 90% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_autocomplete/use_esql_fields_options.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_fields_options.ts index 47312c4b84be4..95e6d3f84aa20 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_autocomplete/use_esql_fields_options.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_fields_options.ts @@ -12,8 +12,8 @@ import { useQuery } from '@tanstack/react-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { getEsqlQueryConfig } from '../../logic/get_esql_query_config'; -import type { FieldType } from '../../logic/esql_validator'; +import { getEsqlQueryConfig } from '../../rule_creation/logic/get_esql_query_config'; +import type { FieldType } from '../../rule_creation/logic/esql_validator'; export const esqlToOptions = ( data: { error: unknown } | Datatable | undefined | null, @@ -36,7 +36,7 @@ export const esqlToOptions = ( type UseEsqlFieldOptions = ( esqlQuery: string | undefined, - fieldType: FieldType + fieldType?: FieldType ) => { isLoading: boolean; options: Array>; From bc847c892b08ddf2f2ff394c37d6e00b3933a126 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:03:48 +0000 Subject: [PATCH 2/7] refactoring --- .../esql_autocomplete/esql_autocomplete.tsx | 4 +- .../esql_fields_select/esql_fields_select.tsx | 71 -------------- .../components/esql_fields_select/index.ts | 8 -- .../esql_fields_select/translations.ts | 6 -- .../components/step_about_rule/index.tsx | 12 ++- .../hooks/use_investigation_fields.ts | 93 +++++++++++++++++++ 6 files changed, 105 insertions(+), 89 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/esql_fields_select.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/index.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/translations.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx index a0da0e51a8341..478d80496e835 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx @@ -23,6 +23,7 @@ interface AutocompleteFieldProps { fieldType: 'string'; placeholder?: string; esqlQuery: string | undefined; + singleSelection: boolean | undefined; } /** @@ -38,6 +39,7 @@ export const EsqlAutocomplete: React.FC = ({ fieldType, placeholder, esqlQuery, + singleSelection, }): JSX.Element => { const handleValuesChange = useCallback( ([newOption]: EuiComboBoxOptionOption[]): void => { @@ -70,7 +72,7 @@ export const EsqlAutocomplete: React.FC = ({ isLoading={isLoading} isDisabled={isDisabled || isLoading} isClearable={false} - singleSelection={AS_PLAIN_TEXT} + singleSelection={singleSelection ? AS_PLAIN_TEXT : false} data-test-subj="esqlAutocompleteComboBox" style={{ width: `${COMPONENT_WIDTH}px` }} fullWidth diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/esql_fields_select.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/esql_fields_select.tsx deleted file mode 100644 index 5f3407b7f13e8..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/esql_fields_select.tsx +++ /dev/null @@ -1,71 +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 type { EuiComboBoxOptionOption } from '@elastic/eui'; -import React, { useMemo, useState, useCallback, memo } from 'react'; -import { EuiFormRow, EuiComboBox } from '@elastic/eui'; -import { isEsqlRule } from '../../../../../common/detection_engine/utils'; - -import type { FieldHook } from '../../../../shared_imports'; -import { Field } from '../../../../shared_imports'; - -import { useEsqlFieldOptions } from '../../hooks/use_esql_fields_options'; - -interface EsqlFieldsSelectProps { - dataTestSubj: string; - field: FieldHook; - idAria: string; - isDisabled: boolean; - placeholder?: string; - esqlQuery: string | undefined; -} - -const fieldDescribedByIds = 'detectionEngineStepDefineRuleEsqlFieldsSelect'; - -/** - * Select component, that displays all available(returned from ES|QL query) fields - * Primary use: is to allow user select fields from result that will be used for suppression of possible duplicated alerts - */ -export const EsqlFieldsSelectComponent: React.FC = ({ - field, - dataTestSubj, - idAria, - isDisabled, - placeholder, - esqlQuery, -}: EsqlFieldsSelectProps) => { - const { options, isLoading } = useEsqlFieldOptions(esqlQuery); - - const fieldEuiFieldProps = useMemo( - () => ({ - fullWidth: true, - noSuggestions: false, - options, - placeholder, - onCreateOption: undefined, - isDisabled: isDisabled || isLoading, - }), - [isDisabled, isLoading, options, placeholder] - ); - - return ( - - - - ); -}; - -export const EsqlFieldsSelect = memo(EsqlFieldsSelectComponent); - -EsqlFieldsSelect.displayName = 'EsqlFieldsSelect'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/index.ts deleted file mode 100644 index 767e309a5b2f3..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { EsqlFieldsSelect } from './esql_fields_select'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/translations.ts deleted file mode 100644 index 1fec1c76430eb..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_fields_select/translations.ts +++ /dev/null @@ -1,6 +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. - */ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx index c53a516f2f1fe..3113ff29c0a9a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx @@ -38,8 +38,8 @@ import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; import { useKibana } from '../../../../common/lib/kibana'; import { useRuleIndices } from '../../../rule_management/logic/use_rule_indices'; import { EsqlAutocomplete } from '../esql_autocomplete'; -import { EsqlFieldsSelect } from '../esql_fields_select'; import { MultiSelectFieldsAutocomplete } from '../multi_select_fields'; +import { useInvestigationFields } from '../../hooks/use_investigation_fields'; const CommonUseField = getUseField({ component: Field }); @@ -129,6 +129,12 @@ const StepAboutRuleComponent: FC = ({ [getFields] ); + const { investigationFields, isLoading: isInvestigationFieldsLoading } = useInvestigationFields({ + esqlQuery, + isEsqlRule: isEsqlRuleValue, + indexPatternsFields: indexPattern.fields, + }); + return ( <> @@ -241,8 +247,8 @@ const StepAboutRuleComponent: FC = ({ path="investigationFields" component={MultiSelectFieldsAutocomplete} componentProps={{ - browserFields: indexPattern.fields, - isDisabled: isLoading || indexPatternLoading, + browserFields: investigationFields, + isDisabled: isLoading || indexPatternLoading || isInvestigationFieldsLoading, fullWidth: true, dataTestSubj: 'detectionEngineStepAboutRuleInvestigationFields', }} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.ts new file mode 100644 index 0000000000000..f7dd517436612 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.ts @@ -0,0 +1,93 @@ +/* + * 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 { Datatable, ExpressionsStart } from '@kbn/expressions-plugin/public'; +import type { DataViewFieldBase } from '@kbn/es-query'; +import { computeIsESQLQueryAggregating } from '@kbn/securitysolution-utils'; + +import { useQuery } from '@tanstack/react-query'; + +import { useKibana } from '@kbn/kibana-react-plugin/public'; + +import { getEsqlQueryConfig } from '../../rule_creation/logic/get_esql_query_config'; + +const esqlToFields = ( + data: { error: unknown } | Datatable | undefined | null +): DataViewFieldBase[] => { + if (data && 'error' in data) { + return []; + } + + const fields = (data?.columns ?? []).map(({ id, meta }) => { + return { + name: id, + type: meta.type, + }; + }); + + return fields; +}; + +type UseEsqlFields = (esqlQuery: string | undefined) => { + isLoading: boolean; + fields: DataViewFieldBase[]; +}; + +/** + * fetches ES|QL fields and convert them to DataViewBase fields + */ +const useEsqlFields: UseEsqlFields = (esqlQuery) => { + const kibana = useKibana<{ expressions: ExpressionsStart }>(); + + const { expressions } = kibana.services; + + const queryConfig = getEsqlQueryConfig({ esqlQuery, expressions }); + const { data, isLoading } = useQuery(queryConfig); + + const fields = useMemo(() => { + return esqlToFields(data); + }, [data]); + + return { + fields, + isLoading, + }; +}; + +type UseInvestigationFields = (params: { + isEsqlRule: boolean; + esqlQuery: string | undefined; + indexPatternsFields: DataViewFieldBase[]; +}) => { + isLoading: boolean; + investigationFields: DataViewFieldBase[]; +}; + +export const useInvestigationFields: UseInvestigationFields = ({ + isEsqlRule, + esqlQuery, + indexPatternsFields, +}) => { + const { fields: esqlFields, isLoading } = useEsqlFields(esqlQuery); + + const investigationFields = useMemo(() => { + if (!esqlQuery || !isEsqlRule) { + return indexPatternsFields; + } + + // alerts generated from non-aggregating queries are enriched with source document + // so, index patterns fields should be included in the list of investigation fields + const isEsqlQueryAggregating = computeIsESQLQueryAggregating(esqlQuery); + + return isEsqlQueryAggregating ? esqlFields : [...esqlFields, ...indexPatternsFields]; + }, [esqlFields, esqlQuery, indexPatternsFields, isEsqlRule]); + + return { + investigationFields, + isLoading, + }; +}; From 342e0d090ee0bd36ff349de6065d4ba1704e2fd0 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:03:01 +0000 Subject: [PATCH 3/7] rveerts --- .../components/esql_autocomplete/esql_autocomplete.tsx | 6 ++---- .../esql_autocomplete}/use_esql_fields_options.test.ts | 0 .../esql_autocomplete}/use_esql_fields_options.ts | 8 ++++---- 3 files changed, 6 insertions(+), 8 deletions(-) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/{hooks => components/esql_autocomplete}/use_esql_fields_options.test.ts (100%) rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/{hooks => components/esql_autocomplete}/use_esql_fields_options.ts (88%) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx index 478d80496e835..cc524e0c5cb79 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/esql_autocomplete.tsx @@ -11,7 +11,7 @@ import { EuiFormRow, EuiComboBox } from '@elastic/eui'; import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { useEsqlFieldOptions } from '../../hooks/use_esql_fields_options'; +import { useEsqlFieldOptions } from './use_esql_fields_options'; const AS_PLAIN_TEXT = { asPlainText: true }; const COMPONENT_WIDTH = 500; @@ -23,7 +23,6 @@ interface AutocompleteFieldProps { fieldType: 'string'; placeholder?: string; esqlQuery: string | undefined; - singleSelection: boolean | undefined; } /** @@ -39,7 +38,6 @@ export const EsqlAutocomplete: React.FC = ({ fieldType, placeholder, esqlQuery, - singleSelection, }): JSX.Element => { const handleValuesChange = useCallback( ([newOption]: EuiComboBoxOptionOption[]): void => { @@ -72,7 +70,7 @@ export const EsqlAutocomplete: React.FC = ({ isLoading={isLoading} isDisabled={isDisabled || isLoading} isClearable={false} - singleSelection={singleSelection ? AS_PLAIN_TEXT : false} + singleSelection={AS_PLAIN_TEXT} data-test-subj="esqlAutocompleteComboBox" style={{ width: `${COMPONENT_WIDTH}px` }} fullWidth diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_fields_options.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/use_esql_fields_options.test.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_fields_options.test.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/use_esql_fields_options.test.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_fields_options.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/use_esql_fields_options.ts similarity index 88% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_fields_options.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/use_esql_fields_options.ts index 95e6d3f84aa20..3460ce8713b1f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_fields_options.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/use_esql_fields_options.ts @@ -12,12 +12,12 @@ import { useQuery } from '@tanstack/react-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { getEsqlQueryConfig } from '../../rule_creation/logic/get_esql_query_config'; -import type { FieldType } from '../../rule_creation/logic/esql_validator'; +import { getEsqlQueryConfig } from '../../../rule_creation/logic/get_esql_query_config'; +import type { FieldType } from '../../../rule_creation/logic/esql_validator'; export const esqlToOptions = ( data: { error: unknown } | Datatable | undefined | null, - fieldType?: FieldType + fieldType: FieldType ) => { if (data && 'error' in data) { return []; @@ -36,7 +36,7 @@ export const esqlToOptions = ( type UseEsqlFieldOptions = ( esqlQuery: string | undefined, - fieldType?: FieldType + fieldType: FieldType ) => { isLoading: boolean; options: Array>; From 25ab821c45bdbcec7c123ad20bf20d7b196fe5b4 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:04:53 +0000 Subject: [PATCH 4/7] lint --- .../components/esql_autocomplete/use_esql_fields_options.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/use_esql_fields_options.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/use_esql_fields_options.ts index 3460ce8713b1f..b8fc47c41c798 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/use_esql_fields_options.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/esql_autocomplete/use_esql_fields_options.ts @@ -17,7 +17,7 @@ import type { FieldType } from '../../../rule_creation/logic/esql_validator'; export const esqlToOptions = ( data: { error: unknown } | Datatable | undefined | null, - fieldType: FieldType + fieldType?: FieldType ) => { if (data && 'error' in data) { return []; From 88ffafe80071791cea2ce353aaabb45a8b5ce5d5 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Mon, 4 Mar 2024 19:12:58 +0000 Subject: [PATCH 5/7] add Cypress test --- .../rule_creation/esql_rule_ess.cy.ts | 45 ++++++++++++++++++- .../cypress/tasks/create_new_rule.ts | 2 +- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule_ess.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule_ess.cy.ts index 089cfc01ce0bb..ec15e66f45dbd 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule_ess.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule_ess.cy.ts @@ -7,7 +7,11 @@ import { getEsqlRule } from '../../../../objects/rule'; -import { RULES_MANAGEMENT_TABLE, RULE_NAME } from '../../../../screens/alerts_detection_rules'; +import { + RULES_MANAGEMENT_TABLE, + RULE_NAME, + INVESTIGATION_FIELDS_VALUE_ITEM, +} from '../../../../screens/alerts_detection_rules'; import { RULE_NAME_HEADER, RULE_TYPE_DETAILS, @@ -29,6 +33,11 @@ import { fillEsqlQueryBar, fillAboutSpecificEsqlRuleAndContinue, createRuleWithoutEnabling, + expandAdvancedSettings, + fillCustomInvestigationFields, + fillRuleName, + fillDescription, + getAboutContinueButton, } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; @@ -176,4 +185,38 @@ describe('Detection ES|QL rules, creation', { tags: ['@ess'] }, () => { cy.get(ESQL_QUERY_BAR).contains('Error validating ES|QL'); }); }); + + describe('ES|QL investigation fields', () => { + beforeEach(() => { + login(); + visit(CREATE_RULE_URL); + }); + it('shows custom ES|QL field in investigation fields autocomplete and saves it in rule', function () { + const CUSTOM_ESQL_FIELD = '_custom_agent_name'; + const queryWithCustomFields = [ + `from auditbeat* [metadata _id, _version, _index]`, + `eval ${CUSTOM_ESQL_FIELD} = agent.name`, + `keep _id, _custom_agent_name`, + `limit 5`, + ].join(' | '); + + workaroundForResizeObserver(); + + selectEsqlRuleType(); + expandEsqlQueryBar(); + fillEsqlQueryBar(queryWithCustomFields); + getDefineContinueButton().click(); + + expandAdvancedSettings(); + fillRuleName(); + fillDescription(); + fillCustomInvestigationFields([CUSTOM_ESQL_FIELD]); + getAboutContinueButton().click(); + + fillScheduleRuleAndContinue(rule); + createRuleWithoutEnabling(); + + cy.get(INVESTIGATION_FIELDS_VALUE_ITEM).should('have.text', CUSTOM_ESQL_FIELD); + }); + }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts index 8c0fb625ab22a..fc1263081c022 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts @@ -684,7 +684,7 @@ export const getIndicatorAtLeastOneInvalidationText = () => cy.contains(AT_LEAST export const getIndexPatternInvalidationText = () => cy.contains(AT_LEAST_ONE_INDEX_PATTERN); /** Returns the continue button on the step of about */ -const getAboutContinueButton = () => cy.get(ABOUT_CONTINUE_BTN); +export const getAboutContinueButton = () => cy.get(ABOUT_CONTINUE_BTN); /** Returns the continue button on the step of define */ export const getDefineContinueButton = () => cy.get(DEFINE_CONTINUE_BUTTON); From 9626eb9d4c86222f27ea5a35c1b8693d917e5ccf Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Tue, 5 Mar 2024 15:10:12 +0000 Subject: [PATCH 6/7] unit tests --- .../public/common/__mocks__/query_wrapper.tsx | 34 +++++ .../hooks/use_investigation_fields.test.ts | 140 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 x-pack/plugins/security_solution/public/common/__mocks__/query_wrapper.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts diff --git a/x-pack/plugins/security_solution/public/common/__mocks__/query_wrapper.tsx b/x-pack/plugins/security_solution/public/common/__mocks__/query_wrapper.tsx new file mode 100644 index 0000000000000..605e998fe678f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/__mocks__/query_wrapper.tsx @@ -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 React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +export const createQueryWrapperMock = (): { + queryClient: QueryClient; + wrapper: React.FC; +} => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + logger: { + error: () => undefined, + log: () => undefined, + warn: () => undefined, + }, + }); + + return { + queryClient, + wrapper: ({ children }) => ( + {children} + ), + }; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts new file mode 100644 index 0000000000000..beae91010ce05 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts @@ -0,0 +1,140 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import type { DataViewFieldBase } from '@kbn/es-query'; + +import { useInvestigationFields } from './use_investigation_fields'; + +import { createQueryWrapperMock } from '../../../common/__mocks__/query_wrapper'; + +import { computeIsESQLQueryAggregating } from '@kbn/securitysolution-utils'; +import { fetchFieldsFromESQL } from '@kbn/text-based-editor'; + +jest.mock('@kbn/securitysolution-utils', () => ({ + computeIsESQLQueryAggregating: jest.fn(), +})); + +jest.mock('@kbn/text-based-editor', () => ({ + fetchFieldsFromESQL: jest.fn(), +})); + +const computeIsESQLQueryAggregatingMock = computeIsESQLQueryAggregating as jest.Mock; +const fetchFieldsFromESQLMock = fetchFieldsFromESQL as jest.Mock; + +const { wrapper } = createQueryWrapperMock(); + +const mockEsqlQuery = 'from auditbeat* [metadata _id]'; +const mockIndexPatternFields: DataViewFieldBase[] = [ + { + name: 'agent.name', + type: 'string', + }, + { + name: 'agent.type', + type: 'string', + }, +]; +const mockEsqlDatatable = { + type: 'datatable', + rows: [], + columns: [{ id: '_custom_field', name: '_custom_field', meta: { type: 'string' } }], +}; + +describe('useInvestigationFields', () => { + beforeEach(() => { + jest.clearAllMocks(); + fetchFieldsFromESQLMock.mockResolvedValue(mockEsqlDatatable); + }); + + it('should return loading true when esql fields still loading', () => { + const { result } = renderHook( + () => + useInvestigationFields({ + isEsqlRule: true, + esqlQuery: mockEsqlQuery, + indexPatternsFields: mockIndexPatternFields, + }), + { wrapper } + ); + + expect(result.current.isLoading).toBe(true); + }); + + it('should return only index pattern fields when ES|QL query is empty', async () => { + const { result, waitForNextUpdate } = renderHook( + () => + useInvestigationFields({ + isEsqlRule: true, + esqlQuery: '', + indexPatternsFields: mockIndexPatternFields, + }), + { wrapper } + ); + + await waitForNextUpdate(); + + expect(result.current.investigationFields).toEqual(mockIndexPatternFields); + }); + + it('should return only index pattern fields when rule type is not ES|QL', async () => { + const { result } = renderHook( + () => + useInvestigationFields({ + isEsqlRule: false, + esqlQuery: mockEsqlQuery, + indexPatternsFields: mockIndexPatternFields, + }), + { wrapper } + ); + + expect(result.current.investigationFields).toEqual(mockIndexPatternFields); + }); + + it('should return index pattern fields concatenated with ES|QL fields when ES|QL query is non-aggregating', async () => { + computeIsESQLQueryAggregatingMock.mockReturnValue(false); + + const { result } = renderHook( + () => + useInvestigationFields({ + isEsqlRule: true, + esqlQuery: mockEsqlQuery, + indexPatternsFields: mockIndexPatternFields, + }), + { wrapper } + ); + + expect(result.current.investigationFields).toEqual([ + { + name: '_custom_field', + type: 'string', + }, + ...mockIndexPatternFields, + ]); + }); + + it('should return only ES|QL fields when ES|QL query is aggregating', async () => { + computeIsESQLQueryAggregatingMock.mockReturnValue(true); + + const { result } = renderHook( + () => + useInvestigationFields({ + isEsqlRule: true, + esqlQuery: mockEsqlQuery, + indexPatternsFields: mockIndexPatternFields, + }), + { wrapper } + ); + + expect(result.current.investigationFields).toEqual([ + { + name: '_custom_field', + type: 'string', + }, + ]); + }); +}); From c4649d3b13f58c75478733c4e925bbfeaf1ddd8a Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:58:52 +0000 Subject: [PATCH 7/7] simplify investigation fields hook --- .../components/step_about_rule/index.tsx | 3 +-- .../hooks/use_investigation_fields.test.ts | 9 ++------- .../rule_creation_ui/hooks/use_investigation_fields.ts | 6 ++---- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx index 3113ff29c0a9a..839618669dc06 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx @@ -130,8 +130,7 @@ const StepAboutRuleComponent: FC = ({ ); const { investigationFields, isLoading: isInvestigationFieldsLoading } = useInvestigationFields({ - esqlQuery, - isEsqlRule: isEsqlRuleValue, + esqlQuery: isEsqlRuleValue ? esqlQuery : undefined, indexPatternsFields: indexPattern.fields, }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts index beae91010ce05..4819f87d5a41f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts @@ -55,7 +55,6 @@ describe('useInvestigationFields', () => { const { result } = renderHook( () => useInvestigationFields({ - isEsqlRule: true, esqlQuery: mockEsqlQuery, indexPatternsFields: mockIndexPatternFields, }), @@ -69,7 +68,6 @@ describe('useInvestigationFields', () => { const { result, waitForNextUpdate } = renderHook( () => useInvestigationFields({ - isEsqlRule: true, esqlQuery: '', indexPatternsFields: mockIndexPatternFields, }), @@ -81,12 +79,11 @@ describe('useInvestigationFields', () => { expect(result.current.investigationFields).toEqual(mockIndexPatternFields); }); - it('should return only index pattern fields when rule type is not ES|QL', async () => { + it('should return only index pattern fields when ES|QL query is undefined', async () => { const { result } = renderHook( () => useInvestigationFields({ - isEsqlRule: false, - esqlQuery: mockEsqlQuery, + esqlQuery: undefined, indexPatternsFields: mockIndexPatternFields, }), { wrapper } @@ -101,7 +98,6 @@ describe('useInvestigationFields', () => { const { result } = renderHook( () => useInvestigationFields({ - isEsqlRule: true, esqlQuery: mockEsqlQuery, indexPatternsFields: mockIndexPatternFields, }), @@ -123,7 +119,6 @@ describe('useInvestigationFields', () => { const { result } = renderHook( () => useInvestigationFields({ - isEsqlRule: true, esqlQuery: mockEsqlQuery, indexPatternsFields: mockIndexPatternFields, }), diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.ts index f7dd517436612..b9b5dc1a07713 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.ts @@ -59,7 +59,6 @@ const useEsqlFields: UseEsqlFields = (esqlQuery) => { }; type UseInvestigationFields = (params: { - isEsqlRule: boolean; esqlQuery: string | undefined; indexPatternsFields: DataViewFieldBase[]; }) => { @@ -68,14 +67,13 @@ type UseInvestigationFields = (params: { }; export const useInvestigationFields: UseInvestigationFields = ({ - isEsqlRule, esqlQuery, indexPatternsFields, }) => { const { fields: esqlFields, isLoading } = useEsqlFields(esqlQuery); const investigationFields = useMemo(() => { - if (!esqlQuery || !isEsqlRule) { + if (!esqlQuery) { return indexPatternsFields; } @@ -84,7 +82,7 @@ export const useInvestigationFields: UseInvestigationFields = ({ const isEsqlQueryAggregating = computeIsESQLQueryAggregating(esqlQuery); return isEsqlQueryAggregating ? esqlFields : [...esqlFields, ...indexPatternsFields]; - }, [esqlFields, esqlQuery, indexPatternsFields, isEsqlRule]); + }, [esqlFields, esqlQuery, indexPatternsFields]); return { investigationFields,