From b7981281384dc565f8e48c08da44816d7efc5345 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Fri, 25 Oct 2024 19:52:27 +0300 Subject: [PATCH] [Security Solution] Add data source editable component (#196948) **Partially addresses:** https://github.com/elastic/kibana/issues/171520 ## Summary This PR adds is built on top of https://github.com/elastic/kibana/pull/193828 and add a Data Source editable component for final edit side of Three Way Diff tab of the upgrade prebuilt rule workflow. ## Details https://github.com/elastic/kibana/issues/171520 required adding editable components for each field diffable rule field. It imposes some difficulties since it's quite problematic to reuse existing especially complex components like Data Source from Define Rule step component. This PR make little refactoring to the Define Rule step component to make it simpler and make it easier to reuse Data Source related code chunks scattered in Define Rule step component. You may notice some copy-paste chunks of Data Source editable component in the PR. At this stage it's the simplest way to proceed to avoid huge refactoring and potential new bugs. Taking into account deadlines for the task it looks like a good trade off. There is a plan to work on improvements for rules creation/editing forms later on. (cherry picked from commit f34802bc6fae7a17e2b75e22ccde3fee380a4dee) --- .../data_view_selector/index.test.tsx | 84 ------ .../components/data_view_selector/index.tsx | 152 ----------- .../__mocks__/use_data_views.ts | 11 + .../data_view_selector_field.test.tsx | 114 +++++++++ .../data_view_selector_field.tsx | 95 +++++++ .../data_view_selector_field/index.ts | 8 + .../translations.tsx | 9 +- .../use_data_views.ts | 45 ++++ .../step_define_rule/index.test.tsx | 100 +------- .../components/step_define_rule/index.tsx | 26 +- .../components/step_define_rule/schema.tsx | 49 +--- .../rule_creation_ui/pages/form.tsx | 2 +- .../pages/rule_creation/index.tsx | 25 +- .../pages/rule_editing/index.tsx | 24 +- .../data_view_id_validator_factory.ts | 31 +++ .../index_pattern_validator_factory.ts | 21 ++ .../rule_details/rule_definition_section.tsx | 2 +- .../final_edit/common_rule_field_edit.tsx | 4 +- .../custom_query_rule_field_edit.tsx | 20 +- .../final_edit/eql_rule_field_edit.tsx | 23 ++ .../fields/data_source/data_source_edit.tsx | 71 ++++++ .../data_source/data_source_edit_form.tsx | 104 ++++++++ .../data_source/data_source_info_text.tsx | 36 +++ .../data_source_type_selector_field.tsx | 68 +++++ .../fields/data_source/data_view_field.tsx | 18 ++ .../final_edit/fields/data_source/index.ts | 8 + .../fields/data_source/index_pattern_edit.tsx | 57 +++++ .../fields/data_source/translations.tsx | 15 ++ .../final_edit/fields/kql_query.tsx | 239 ------------------ .../final_edit/fields/kql_query/index.ts | 8 + .../fields/kql_query/kql_query_edit.tsx | 139 ++++++++++ .../fields/kql_query/kql_query_edit_form.tsx | 118 +++++++++ .../fields/rule_field_edit_component_props.ts | 22 ++ .../rule_field_edit_form_wrapper.tsx} | 69 +++-- .../three_way_diff/final_edit/final_edit.tsx | 4 +- .../final_edit/new_terms_rule_field_edit.tsx | 20 +- .../saved_query_rule_field_edit.tsx | 20 +- .../threat_match_rule_field_edit.tsx | 20 +- .../final_edit/threshold_rule_field_edit.tsx | 20 +- .../use_default_index_pattern.tsx | 16 +- .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 43 files changed, 1115 insertions(+), 808 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/index.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/index.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/__mocks__/use_data_views.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/data_view_selector_field.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/data_view_selector_field.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/index.ts rename x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/{data_view_selector => data_view_selector_field}/translations.tsx (84%) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/use_data_views.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/data_view_id_validator_factory.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/index_pattern_validator_factory.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_edit.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_edit_form.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_info_text.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_type_selector_field.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_view_field.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/index.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/index_pattern_edit.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/translations.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/index.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/kql_query_edit.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/kql_query_edit_form.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_component_props.ts rename x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/{field_form_wrapper.tsx => fields/rule_field_edit_form_wrapper.tsx} (61%) rename x-pack/plugins/security_solution/public/detection_engine/rule_management/{components/rule_details => hooks}/use_default_index_pattern.tsx (63%) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/index.test.tsx deleted file mode 100644 index e37b21550852b..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/index.test.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { shallow, mount } from 'enzyme'; - -import { DataViewSelector } from '.'; -import type { DataViewSelectorProps } from '.'; -import { TestProviders, useFormFieldMock } from '../../../../common/mock'; - -jest.mock('../../../../common/lib/kibana'); - -describe('data_view_selector', () => { - let mockField: DataViewSelectorProps['field']; - - beforeEach(() => { - mockField = useFormFieldMock({ - value: undefined, - }); - }); - - it('renders correctly', () => { - const Component = () => { - return ; - }; - const wrapper = shallow(); - - expect(wrapper.dive().find('[data-test-subj="pick-rule-data-source"]')).toHaveLength(1); - }); - - it('displays alerts on alerts warning when default security view selected', () => { - const wrapper = mount( - - ({ - value: 'security-solution-default', - })} - /> - - ); - - expect(wrapper.find('[data-test-subj="defaultSecurityDataViewWarning"]').exists()).toBeTruthy(); - }); - - it('does not display alerts on alerts warning when default security view is not selected', () => { - const wrapper = mount( - - ({ - value: '1234', - })} - /> - - ); - - expect(wrapper.find('[data-test-subj="defaultSecurityDataViewWarning"]').exists()).toBeFalsy(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/index.tsx deleted file mode 100644 index 45efbfcadec8c..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/index.tsx +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useMemo, useState, useEffect } from 'react'; - -import type { EuiComboBoxOptionOption } from '@elastic/eui'; -import { EuiCallOut, EuiComboBox, EuiFormRow, EuiSpacer } from '@elastic/eui'; - -import type { DataViewListItem } from '@kbn/data-views-plugin/common'; -import type { FieldHook } from '../../../../shared_imports'; -import { getFieldValidityAndErrorMessage } from '../../../../shared_imports'; -import * as i18n from './translations'; -import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; - -export interface DataViewSelectorProps { - kibanaDataViews: Record; - field: FieldHook; -} - -export const DataViewSelector = ({ kibanaDataViews, field }: DataViewSelectorProps) => { - let isInvalid; - let errorMessage; - let dataViewId: string | null | undefined; - - if (field != null) { - const fieldAndError = getFieldValidityAndErrorMessage(field); - isInvalid = fieldAndError.isInvalid; - errorMessage = fieldAndError.errorMessage; - dataViewId = field.value; - } - - const kibanaDataViewsDefined = useMemo( - () => kibanaDataViews != null && Object.keys(kibanaDataViews).length > 0, - [kibanaDataViews] - ); - - // Most likely case here is that a data view of an existing rule was deleted - // and can no longer be found - const selectedDataViewNotFound = useMemo( - () => - dataViewId != null && - dataViewId !== '' && - kibanaDataViewsDefined && - !Object.hasOwn(kibanaDataViews, dataViewId), - [kibanaDataViewsDefined, dataViewId, kibanaDataViews] - ); - const [selectedOption, setSelectedOption] = useState>>( - !selectedDataViewNotFound && dataViewId != null && dataViewId !== '' - ? [{ id: kibanaDataViews[dataViewId].id, label: kibanaDataViews[dataViewId].title }] - : [] - ); - - const [showDataViewAlertsOnAlertsWarning, setShowDataViewAlertsOnAlertsWarning] = useState(false); - - useEffect(() => { - if (!selectedDataViewNotFound && dataViewId) { - const dataViewsTitle = kibanaDataViews[dataViewId].title; - const dataViewsId = kibanaDataViews[dataViewId].id; - - setShowDataViewAlertsOnAlertsWarning(dataViewsId === 'security-solution-default'); - - setSelectedOption([{ id: dataViewsId, label: dataViewsTitle }]); - } else { - setSelectedOption([]); - } - }, [ - dataViewId, - field, - kibanaDataViews, - selectedDataViewNotFound, - setShowDataViewAlertsOnAlertsWarning, - ]); - - // TODO: optimize this, pass down array of data view ids - // at the same time we grab the data views in the top level form component - const dataViewOptions = useMemo(() => { - return kibanaDataViewsDefined - ? Object.values(kibanaDataViews).map((dv) => ({ - label: dv.title, - id: dv.id, - })) - : []; - }, [kibanaDataViewsDefined, kibanaDataViews]); - - const onChangeDataViews = (options: Array>) => { - const selectedDataViewOption = options; - setSelectedOption(selectedDataViewOption ?? []); - - if ( - selectedDataViewOption != null && - selectedDataViewOption.length > 0 && - selectedDataViewOption[0].id != null - ) { - const selectedDataViewId = selectedDataViewOption[0].id; - field?.setValue(selectedDataViewId); - } else { - field?.setValue(undefined); - } - }; - - return ( - <> - {selectedDataViewNotFound && dataViewId != null && ( - <> - -

{i18n.DATA_VIEW_NOT_FOUND_WARNING_DESCRIPTION(dataViewId)}

-
- - - )} - {showDataViewAlertsOnAlertsWarning && ( - <> - -

{i18n.DATA_VIEW_ALERTS_ON_ALERTS_WARNING_DESCRIPTION}

-
- - - )} - - - - - ); -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/__mocks__/use_data_views.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/__mocks__/use_data_views.ts new file mode 100644 index 0000000000000..248729f1f46e7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/__mocks__/use_data_views.ts @@ -0,0 +1,11 @@ +/* + * 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 const useDataViews = jest.fn().mockReturnValue({ + data: [], + isFetching: false, +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/data_view_selector_field.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/data_view_selector_field.test.tsx new file mode 100644 index 0000000000000..6cfdf060434b8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/data_view_selector_field.test.tsx @@ -0,0 +1,114 @@ +/* + * 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 { screen, render } from '@testing-library/react'; +import { TestProviders, useFormFieldMock } from '../../../../common/mock'; +import { DataViewSelectorField } from './data_view_selector_field'; +import { useDataViews } from './use_data_views'; + +jest.mock('../../../../common/lib/kibana'); +jest.mock('./use_data_views'); + +describe('data_view_selector', () => { + it('renders correctly', () => { + (useDataViews as jest.Mock).mockReturnValue({ data: [], isFetching: false }); + + render( + ({ + value: undefined, + })} + />, + { wrapper: TestProviders } + ); + + expect(screen.queryByTestId('pick-rule-data-source')).toBeInTheDocument(); + }); + + it('disables the combobox while data views are fetching', () => { + (useDataViews as jest.Mock).mockReturnValue({ data: [], isFetching: true }); + + render( + ({ + value: undefined, + })} + />, + { wrapper: TestProviders } + ); + + expect(screen.getByRole('combobox')).toBeDisabled(); + }); + + it('displays alerts on alerts warning when default security view selected', () => { + const dataViews = [ + { + id: 'security-solution-default', + title: + '-*elastic-cloud-logs-*,.alerts-security.alerts-default,apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*', + }, + { + id: '1234', + title: 'logs-*', + }, + ]; + (useDataViews as jest.Mock).mockReturnValue({ data: dataViews, isFetching: false }); + + render( + ({ + value: 'security-solution-default', + })} + />, + { wrapper: TestProviders } + ); + + expect(screen.queryByTestId('defaultSecurityDataViewWarning')).toBeInTheDocument(); + }); + + it('does not display alerts on alerts warning when default security view is not selected', () => { + const dataViews = [ + { + id: 'security-solution-default', + title: + '-*elastic-cloud-logs-*,.alerts-security.alerts-default,apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*', + }, + { + id: '1234', + title: 'logs-*', + }, + ]; + (useDataViews as jest.Mock).mockReturnValue({ data: dataViews, isFetching: false }); + + render( + ({ + value: '1234', + })} + />, + { wrapper: TestProviders } + ); + + expect(screen.queryByTestId('defaultSecurityDataViewWarning')).not.toBeInTheDocument(); + }); + + it('displays warning on missing data view', () => { + (useDataViews as jest.Mock).mockReturnValue({ data: [], isFetching: false }); + + render( + ({ + value: 'non-existent-id', + })} + />, + { wrapper: TestProviders } + ); + + expect(screen.queryByTestId('missingDataViewWarning')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/data_view_selector_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/data_view_selector_field.tsx new file mode 100644 index 0000000000000..aacd80ea53236 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/data_view_selector_field.tsx @@ -0,0 +1,95 @@ +/* + * 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, useCallback } from 'react'; +import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import { EuiCallOut, EuiComboBox, EuiFormRow, EuiSpacer } from '@elastic/eui'; +import type { FieldHook } from '../../../../shared_imports'; +import { getFieldValidityAndErrorMessage } from '../../../../shared_imports'; +import { isDataViewIdValid } from '../../validators/data_view_id_validator_factory'; +import { useDataViews } from './use_data_views'; +import * as i18n from './translations'; + +const SECURITY_DEFAULT_DATA_VIEW_ID = 'security-solution-default'; + +export interface DataViewSelectorProps { + field: FieldHook; +} + +export function DataViewSelectorField({ field }: DataViewSelectorProps): JSX.Element { + const { data: dataViews, isFetching: areDataViewsFetching } = useDataViews(); + const fieldAndError = field ? getFieldValidityAndErrorMessage(field) : undefined; + const isInvalid = fieldAndError?.isInvalid; + const errorMessage = fieldAndError?.errorMessage; + const comboBoxOptions = useMemo( + () => + dataViews.map(({ id, title: label }) => ({ + id, + label, + })), + [dataViews] + ); + const selectedOption = useMemo( + () => comboBoxOptions.find(({ id }) => id === field.value), + [comboBoxOptions, field] + ); + + const handleDataViewsChange = useCallback( + (options: Array>) => field.setValue(options[0]?.id), + [field] + ); + + return ( + <> + {!areDataViewsFetching && isDataViewIdValid(field.value) && !selectedOption && ( + <> + +

{i18n.DATA_VIEW_NOT_FOUND_WARNING_DESCRIPTION(field.value)}

+
+ + + )} + {field.value === SECURITY_DEFAULT_DATA_VIEW_ID && ( + <> + +

{i18n.DATA_VIEW_ALERTS_ON_ALERTS_WARNING_DESCRIPTION}

+
+ + + )} + + + + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/index.ts new file mode 100644 index 0000000000000..5cc0e111b13de --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './data_view_selector_field'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/translations.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/translations.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/translations.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/translations.tsx index eff760157e82f..717666ac0c0c1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector/translations.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/translations.tsx @@ -31,7 +31,7 @@ export const DATA_VIEW_NOT_FOUND_WARNING_DESCRIPTION = (dataView: string) => } ); -export const DDATA_VIEW_ALERTS_ON_ALERTS_WARNING_LABEL = i18n.translate( +export const DATA_VIEW_ALERTS_ON_ALERTS_WARNING_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.stepDefineRule.dataViewIncludesAlertsIndexLabel', { defaultMessage: 'Default Security data view', @@ -45,3 +45,10 @@ export const DATA_VIEW_ALERTS_ON_ALERTS_WARNING_DESCRIPTION = i18n.translate( 'The default Security data view includes the alerts index. This could result in redundant alerts being generated from existing alerts.', } ); + +export const DATA_VIEWS_FETCH_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.stepDefineRule.dataViewFetchError', + { + defaultMessage: 'Unable to retrieve available data views', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/use_data_views.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/use_data_views.ts new file mode 100644 index 0000000000000..a68aa4f976269 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/data_view_selector_field/use_data_views.ts @@ -0,0 +1,45 @@ +/* + * 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, useState } from 'react'; +import type { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useKibana } from '../../../../common/lib/kibana'; +import * as i18n from './translations'; + +interface UseDataViewsResult { + data: DataViewListItem[]; + isFetching: boolean; +} + +/** + * Fetches known Kibana data views from the Data View Service. + */ +export function useDataViews(): UseDataViewsResult { + const { + data: { dataViews: dataViewsService }, + } = useKibana().services; + const { addError } = useAppToasts(); + + const [isFetching, setIsFetching] = useState(false); + const [dataViews, setDataViews] = useState([]); + + useEffect(() => { + setIsFetching(true); + (async () => { + try { + setDataViews(await dataViewsService.getIdsWithTitle(true)); + } catch (e) { + addError(e, { title: i18n.DATA_VIEWS_FETCH_ERROR }); + } finally { + setIsFetching(false); + } + })(); + }, [dataViewsService, addError]); + + return { data: dataViews, isFetching }; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx index 3e7d19ad7db5f..cc8f2abda9c4e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx @@ -48,6 +48,8 @@ jest.mock('../ai_assistant', () => { }; }); +jest.mock('../data_view_selector_field/use_data_views'); + const mockRedirectLegacyUrl = jest.fn(); const mockGetLegacyUrlConflict = jest.fn(); jest.mock('../../../../common/lib/kibana', () => { @@ -79,104 +81,6 @@ jest.mock('../../../../common/lib/kibana', () => { }, }, data: { - dataViews: { - getIdsWithTitle: async () => - Promise.resolve([{ id: 'myfakeid', title: 'hello*,world*,refreshed*' }]), - create: async ({ title }: { title: string }) => - Promise.resolve({ - id: 'myfakeid', - matchedIndices: ['hello', 'world', 'refreshed'], - fields: [ - { - name: 'bytes', - type: 'number', - esTypes: ['long'], - aggregatable: true, - searchable: true, - count: 10, - readFromDocValues: true, - scripted: false, - isMapped: true, - }, - { - name: 'ssl', - type: 'boolean', - esTypes: ['boolean'], - aggregatable: true, - searchable: true, - count: 20, - readFromDocValues: true, - scripted: false, - isMapped: true, - }, - { - name: '@timestamp', - type: 'date', - esTypes: ['date'], - aggregatable: true, - searchable: true, - count: 30, - readFromDocValues: true, - scripted: false, - isMapped: true, - }, - ], - getIndexPattern: () => 'hello*,world*,refreshed*', - getRuntimeMappings: () => ({ - myfield: { - type: 'keyword', - }, - }), - }), - get: async (dataViewId: string, displayErrors?: boolean, refreshFields = false) => - Promise.resolve({ - id: dataViewId, - matchedIndices: refreshFields - ? ['hello', 'world', 'refreshed'] - : ['hello', 'world'], - fields: [ - { - name: 'bytes', - type: 'number', - esTypes: ['long'], - aggregatable: true, - searchable: true, - count: 10, - readFromDocValues: true, - scripted: false, - isMapped: true, - }, - { - name: 'ssl', - type: 'boolean', - esTypes: ['boolean'], - aggregatable: true, - searchable: true, - count: 20, - readFromDocValues: true, - scripted: false, - isMapped: true, - }, - { - name: '@timestamp', - type: 'date', - esTypes: ['date'], - aggregatable: true, - searchable: true, - count: 30, - readFromDocValues: true, - scripted: false, - isMapped: true, - }, - ], - getIndexPattern: () => 'hello*,world*,refreshed*', - getRuntimeMappings: () => ({ - myfield: { - type: 'keyword', - }, - }), - }), - }, search: { search: () => ({ subscribe: () => ({ 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 6b9780b8c029b..99fb8f2ba469e 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 @@ -11,7 +11,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, - EuiLoadingSpinner, EuiSpacer, EuiButtonGroup, EuiText, @@ -82,7 +81,7 @@ import { isSuppressionRuleInGA, } from '../../../../../common/detection_engine/utils'; import { EqlQueryBar } from '../eql_query_bar'; -import { DataViewSelector } from '../data_view_selector'; +import { DataViewSelectorField } from '../data_view_selector_field'; import { ThreatMatchInput } from '../threatmatch_input'; import { useFetchIndex } from '../../../../common/containers/source'; import { NewTermsFields } from '../new_terms_fields'; @@ -184,7 +183,6 @@ const StepDefineRuleComponent: FC = ({ isLoading, isQueryBarValid, isUpdateView = false, - kibanaDataViews, optionsSelected, queryBarSavedId, queryBarTitle, @@ -653,21 +651,6 @@ const StepDefineRuleComponent: FC = ({ [dataSourceType] ); - const DataViewSelectorMemo = useMemo(() => { - return kibanaDataViews == null || Object.keys(kibanaDataViews).length === 0 ? ( - - ) : ( - - ); - }, [kibanaDataViews]); - const DataSource = useMemo(() => { return ( @@ -714,7 +697,11 @@ const StepDefineRuleComponent: FC = ({ - {DataViewSelectorMemo} + = ({ dataSourceType, onChangeDataSource, dataViewIndexPatternToggleButtonOptions, - DataViewSelectorMemo, indexModified, handleResetIndices, ]); 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 5c8d8d89c46d0..fc8468b094fa1 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 @@ -44,6 +44,8 @@ import { EQL_SEQUENCE_SUPPRESSION_GROUPBY_VALIDATION_TEXT, } from './translations'; import { getQueryRequiredMessage } from './utils'; +import { dataViewIdValidatorFactory } from '../../validators/data_view_id_validator_factory'; +import { indexPatternValidatorFactory } from '../../validators/index_pattern_validator_factory'; export const schema: FormSchema = { index: { @@ -59,27 +61,18 @@ export const schema: FormSchema = { helpText: {INDEX_HELPER_TEXT}, validations: [ { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { + validator: (...args: Parameters) => { const [{ formData }] = args; - const skipValidation = + + if ( isMlRule(formData.ruleType) || isEsqlRule(formData.ruleType) || - formData.dataSourceType !== DataSourceType.IndexPatterns; - - if (skipValidation) { + formData.dataSourceType !== DataSourceType.IndexPatterns + ) { return; } - return fieldValidators.emptyField( - i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.outputIndiceNameFieldRequiredError', - { - defaultMessage: 'A minimum of one index pattern is required.', - } - ) - )(...args); + return indexPatternValidatorFactory()(...args); }, }, ], @@ -94,32 +87,14 @@ export const schema: FormSchema = { fieldsToValidateOnChange: ['dataViewId'], validations: [ { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ path, formData }] = args; - // the dropdown defaults the dataViewId to an empty string somehow on render.. - // need to figure this out. - const notEmptyDataViewId = formData.dataViewId != null && formData.dataViewId !== ''; - - const skipValidation = - isMlRule(formData.ruleType) || - notEmptyDataViewId || - formData.dataSourceType !== DataSourceType.DataView; + validator: (...args: Parameters) => { + const [{ formData }] = args; - if (skipValidation) { + if (isMlRule(formData.ruleType) || formData.dataSourceType !== DataSourceType.DataView) { return; } - return { - path, - message: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.dataViewSelectorFieldRequired', - { - defaultMessage: 'Please select an available Data View or Index Pattern.', - } - ), - }; + return dataViewIdValidatorFactory()(...args); }, }, ], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx index 90b302c3bc904..9e232e4bff2be 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx @@ -149,7 +149,7 @@ export const useRuleIndexPattern = ({ if (dataSourceType === DataSourceType.DataView) { const fetchDataView = async () => { - if (dataViewId != null) { + if (dataViewId != null && dataViewId !== '') { const dv = await data.dataViews.get(dataViewId); setIndexPattern(dv); } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx index 28d137ac522ae..0c6a6fb07ce5c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx @@ -19,8 +19,6 @@ import { import React, { memo, useCallback, useRef, useState, useMemo, useEffect } from 'react'; import styled from 'styled-components'; -import type { DataViewListItem } from '@kbn/data-views-plugin/common'; - import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { isMlRule, @@ -123,11 +121,7 @@ const CreateRulePageComponent: React.FC = () => { useListsConfig(); const { addSuccess } = useAppToasts(); const { navigateToApp } = useKibana().services.application; - const { - application, - data: { dataViews }, - triggersActionsUi, - } = useKibana().services; + const { application, triggersActionsUi } = useKibana().services; const loading = userInfoLoading || listsConfigLoading; const [activeStep, setActiveStep] = useState(RuleStep.defineRule); const getNextStep = (step: RuleStep): RuleStep | undefined => @@ -204,7 +198,6 @@ const CreateRulePageComponent: React.FC = () => { const { mutateAsync: createRule, isLoading: isCreateRuleLoading } = useCreateRule(); const ruleType = defineStepData.ruleType; const actionMessageParams = useMemo(() => getActionMessageParams(ruleType), [ruleType]); - const [dataViewOptions, setDataViewOptions] = useState<{ [x: string]: DataViewListItem }>({}); const [isRulePreviewVisible, setIsRulePreviewVisible] = useState(true); const collapseFn = useRef<() => void | undefined>(); const [prevRuleType, setPrevRuleType] = useState(); @@ -256,20 +249,6 @@ const CreateRulePageComponent: React.FC = () => { const { starting: isStartingJobs, startMlJobs } = useStartMlJobs(); - useEffect(() => { - const fetchDV = async () => { - const dataViewsRefs = await dataViews.getIdsWithTitle(); - const dataViewIdIndexPatternMap = dataViewsRefs.reduce( - (acc, item) => ({ - ...acc, - [item.id]: item, - }), - {} - ); - setDataViewOptions(dataViewIdIndexPatternMap); - }; - fetchDV(); - }, [dataViews]); const { indexPattern, isIndexPatternLoading } = useRuleIndexPattern({ dataSourceType: defineStepData.dataSourceType, index: memoizedIndex, @@ -573,7 +552,6 @@ const CreateRulePageComponent: React.FC = () => { > { ), [ activeStep, - dataViewOptions, defineRuleNextStep, defineStepData.dataSourceType, defineStepData.groupByFields, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx index 9151e6965bd11..1657f57ec83e8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx @@ -18,11 +18,9 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { FC } from 'react'; -import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { memo, useCallback, useMemo, useRef, useState } from 'react'; import { useParams } from 'react-router-dom'; -import type { DataViewListItem } from '@kbn/data-views-plugin/common'; - import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { isEsqlRule } from '../../../../../common/detection_engine/utils'; import { RulePreview } from '../../components/rule_preview'; @@ -85,7 +83,7 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { ] = useUserData(); const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = useListsConfig(); - const { data: dataServices, application, triggersActionsUi } = useKibana().services; + const { application, triggersActionsUi } = useKibana().services; const { navigateToApp } = application; const { detailName: ruleId } = useParams<{ detailName: string }>(); @@ -94,7 +92,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { rule.immutable ? RuleStep.ruleActions : RuleStep.defineRule ); const { mutateAsync: updateRule, isLoading } = useUpdateRule(); - const [dataViewOptions, setDataViewOptions] = useState<{ [x: string]: DataViewListItem }>({}); const [isRulePreviewVisible, setIsRulePreviewVisible] = useState(true); const collapseFn = useRef<() => void | undefined>(); const [isQueryBarValid, setIsQueryBarValid] = useState(false); @@ -103,21 +100,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { const [isSaveWithErrorsModalVisible, setIsSaveWithErrorsModalVisible] = useState(false); const [nonBlockingRuleErrors, setNonBlockingRuleErrors] = useState([]); - useEffect(() => { - const fetchDataViews = async () => { - const dataViewsRefs = await dataServices.dataViews.getIdsWithTitle(); - const dataViewIdIndexPatternMap = dataViewsRefs.reduce( - (acc, item) => ({ - ...acc, - [item.id]: item, - }), - {} - ); - setDataViewOptions(dataViewIdIndexPatternMap); - }; - fetchDataViews(); - }, [dataServices.dataViews]); - const backOptions = useMemo( () => ({ path: getRuleDetailsUrl(ruleId ?? ''), @@ -241,7 +223,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { = ({ rule }) => { loading, isSavedQueryLoading, isLoading, - dataViewOptions, indicesConfig, threatIndicesConfig, savedQuery, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/data_view_id_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/data_view_id_validator_factory.ts new file mode 100644 index 0000000000000..57ef5ff6e0133 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/data_view_id_validator_factory.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 { i18n } from '@kbn/i18n'; +import type { FormData, ValidationFunc } from '../../../shared_imports'; + +export function dataViewIdValidatorFactory(): ValidationFunc { + return (...args) => { + const [{ path, value }] = args; + + return !isDataViewIdValid(value) + ? { + path, + message: i18n.translate( + 'xpack.securitySolution.ruleManagement.ruleCreation.validation.dataView.requiredError', + { + defaultMessage: 'Please select an available Data View.', + } + ), + } + : undefined; + }; +} + +export function isDataViewIdValid(dataViewId: unknown): dataViewId is string { + return typeof dataViewId === 'string' && dataViewId !== ''; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/index_pattern_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/index_pattern_validator_factory.ts new file mode 100644 index 0000000000000..9962d3b835b3c --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/index_pattern_validator_factory.ts @@ -0,0 +1,21 @@ +/* + * 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 { ERROR_CODE } from '../../../shared_imports'; +import { fieldValidators, type FormData, type ValidationFunc } from '../../../shared_imports'; + +export function indexPatternValidatorFactory(): ValidationFunc { + return fieldValidators.emptyField( + i18n.translate( + 'xpack.securitySolution.ruleManagement.ruleCreation.validation.indexPatterns.requiredError', + { + defaultMessage: 'A minimum of one index pattern is required.', + } + ) + ); +} 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 184633d813675..623ae20fa484f 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,7 @@ import { useRequiredFieldsStyles, } from './rule_definition_section.styles'; import { getQueryLanguageLabel } from './helpers'; -import { useDefaultIndexPattern } from './use_default_index_pattern'; +import { useDefaultIndexPattern } from '../../hooks/use_default_index_pattern'; 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/common_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/common_rule_field_edit.tsx index 0cb7ce3982868..fefd35fcfaf65 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/common_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/common_rule_field_edit.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { FieldFormWrapper } from './field_form_wrapper'; +import { RuleFieldEditFormWrapper } from './fields/rule_field_edit_form_wrapper'; import { NameEdit, nameSchema } from './fields/name'; import type { UpgradeableCommonFields } from '../../../../model/prebuilt_rule_upgrade/fields'; interface CommonRuleFieldEditProps { @@ -16,7 +16,7 @@ interface CommonRuleFieldEditProps { export function CommonRuleFieldEdit({ fieldName }: CommonRuleFieldEditProps) { switch (fieldName) { case 'name': - return ; + return ; default: return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx index 3dc3cc5b87023..e71f061f140e4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx @@ -6,14 +6,9 @@ */ import React from 'react'; -import { FieldFormWrapper } from './field_form_wrapper'; -import { - KqlQueryEdit, - kqlQuerySchema, - kqlQuerySerializer, - kqlQueryDeserializer, -} from './fields/kql_query'; import type { UpgradeableCustomQueryFields } from '../../../../model/prebuilt_rule_upgrade/fields'; +import { KqlQueryEditForm } from './fields/kql_query'; +import { DataSourceEditForm } from './fields/data_source'; interface CustomQueryRuleFieldEditProps { fieldName: UpgradeableCustomQueryFields; @@ -22,14 +17,9 @@ interface CustomQueryRuleFieldEditProps { export function CustomQueryRuleFieldEdit({ fieldName }: CustomQueryRuleFieldEditProps) { switch (fieldName) { case 'kql_query': - return ( - - ); + return ; + case 'data_source': + return ; default: return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx new file mode 100644 index 0000000000000..a15cc87b3324c --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { UpgradeableEqlFields } from '../../../../model/prebuilt_rule_upgrade/fields'; +import { DataSourceEditForm } from './fields/data_source'; + +interface EqlRuleFieldEditProps { + fieldName: UpgradeableEqlFields; +} + +export function EqlRuleFieldEdit({ fieldName }: EqlRuleFieldEditProps) { + switch (fieldName) { + case 'data_source': + return ; + default: + return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented + } +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_edit.tsx new file mode 100644 index 0000000000000..2f697288221cf --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_edit.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 React from 'react'; +import type { PropsWithChildren } from 'react'; +import { css } from '@emotion/css'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { DataSourceType } from '../../../../../../../../../common/api/detection_engine/prebuilt_rules'; +import { UseMultiFields } from '../../../../../../../../shared_imports'; +import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; +import { IndexPatternField } from './index_pattern_edit'; +import { DataSourceInfoText } from './data_source_info_text'; +import { DataViewField } from './data_view_field'; +import { DataSourceTypeSelectorField } from './data_source_type_selector_field'; + +export function DataSourceEdit({ resetForm }: RuleFieldEditComponentProps): JSX.Element { + return ( + + fields={{ + type: { + path: 'type', + }, + indexPatterns: { + path: 'index_patterns', + }, + dataViewId: { + path: 'data_view_id', + }, + }} + > + {({ type, indexPatterns, dataViewId }) => ( + + + + + + + + + + + + + + + + + )} + + ); +} + +interface TabProps { + visible: boolean; +} + +const hidden = css` + display: none; +`; + +function TabContent({ visible, children }: PropsWithChildren): 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/data_source/data_source_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_edit_form.tsx new file mode 100644 index 0000000000000..b1dc66ad032d1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_edit_form.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiText } from '@elastic/eui'; +import { indexPatternValidatorFactory } from '../../../../../../../rule_creation_ui/validators/index_pattern_validator_factory'; +import { dataViewIdValidatorFactory } from '../../../../../../../rule_creation_ui/validators/data_view_id_validator_factory'; +import type { ValidationFunc, ERROR_CODE } from '../../../../../../../../shared_imports'; +import { + type FormData, + type FormSchema, + FIELD_TYPES, +} from '../../../../../../../../shared_imports'; +import { DataSourceType } from '../../../../../../../../../common/api/detection_engine/prebuilt_rules'; +import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; +import { DataSourceEdit } from './data_source_edit'; +import { INDEX_HELPER_TEXT } from '../../../../../../../rule_creation_ui/components/step_define_rule/translations'; + +export function DataSourceEditForm(): JSX.Element { + return ( + + ); +} + +function dataSourceDeserializer(defaultValue: FormData): FormData { + if (!defaultValue.data_source) { + throw new Error(`dataSourceDeserializer expects "data_source" field`); + } + + return defaultValue.data_source; +} + +function dataSourceSerializer(formData: FormData): FormData { + return { + data_source: formData, + }; +} + +const dataSourceSchema = { + type: { + default: DataSourceType.index_patterns, + }, + index_patterns: { + defaultValue: [], + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate( + 'xpack.securitySolution.ruleManagement.threeWayDiff.finalEdit.indexPatterns.label', + { + defaultMessage: 'Index patterns', + } + ), + helpText: {INDEX_HELPER_TEXT}, + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ formData }] = args; + + if (formData.type !== DataSourceType.index_patterns) { + return; + } + + return indexPatternValidatorFactory()(...args); + }, + }, + ], + }, + data_view_id: { + label: i18n.translate( + 'xpack.securitySolution.ruleManagement.threeWayDiff.finalEdit.dataViewSelector.name', + { + defaultMessage: 'Data view', + } + ), + validations: [ + { + validator: (...args: Parameters) => { + const [{ formData }] = args; + + if (formData.type !== DataSourceType.data_view) { + return; + } + + return dataViewIdValidatorFactory()(...args); + }, + }, + ], + }, +} as FormSchema<{ + type: string; + index_patterns: string[]; + data_view_id: string; +}>; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_info_text.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_info_text.tsx new file mode 100644 index 0000000000000..737dcf2061f29 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_info_text.tsx @@ -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 React from 'react'; +import { EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { DocLink } from '../../../../../../../../common/components/links_to_docs/doc_link'; + +export function DataSourceInfoText(): 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/data_source/data_source_type_selector_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_type_selector_field.tsx new file mode 100644 index 0000000000000..f43051853a4d4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_source_type_selector_field.tsx @@ -0,0 +1,68 @@ +/* + * 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, { useCallback, useMemo } from 'react'; +import { i18n as i18nCore } from '@kbn/i18n'; +import type { EuiButtonGroupOptionProps } from '@elastic/eui'; +import { EuiButtonGroup } from '@elastic/eui'; +import { DataSourceType } from '../../../../../../../../../common/api/detection_engine/prebuilt_rules'; +import type { FieldHook } from '../../../../../../../../shared_imports'; +import type { ResetFormFn } from '../rule_field_edit_component_props'; + +interface DataSourceTypeSelectorFieldProps { + field: FieldHook; + resetForm: ResetFormFn; +} + +export function DataSourceTypeSelectorField({ + field, + resetForm, +}: DataSourceTypeSelectorFieldProps): JSX.Element { + const dataViewIndexPatternToggleButtonOptions: EuiButtonGroupOptionProps[] = useMemo( + () => [ + { + id: DataSourceType.index_patterns, + label: i18nCore.translate( + 'xpack.securitySolution.ruleDefine.indexTypeSelect.indexPattern', + { + defaultMessage: 'Index Patterns', + } + ), + iconType: field.value === DataSourceType.index_patterns ? 'checkInCircleFilled' : 'empty', + 'data-test-subj': `rule-index-toggle-${DataSourceType.index_patterns}`, + }, + { + id: DataSourceType.data_view, + label: i18nCore.translate('xpack.securitySolution.ruleDefine.indexTypeSelect.dataView', { + defaultMessage: 'Data View', + }), + iconType: field.value === DataSourceType.data_view ? 'checkInCircleFilled' : 'empty', + 'data-test-subj': `rule-index-toggle-${DataSourceType.data_view}`, + }, + ], + [field.value] + ); + const handleDataSourceChange = useCallback( + (optionId: string) => { + field.setValue(optionId); + resetForm({ resetValues: false }); + }, + [field, resetForm] + ); + + return ( + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_view_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_view_field.tsx new file mode 100644 index 0000000000000..b534817596e66 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/data_view_field.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { DataViewSelectorField } from '../../../../../../../rule_creation_ui/components/data_view_selector_field'; +import type { FieldHook } from '../../../../../../../../shared_imports'; + +interface DataViewFieldProps { + field: FieldHook; +} + +export function DataViewField({ field }: DataViewFieldProps): 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/data_source/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/index.ts new file mode 100644 index 0000000000000..407874ac13143 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './data_source_edit_form'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/index_pattern_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/index_pattern_edit.tsx new file mode 100644 index 0000000000000..ec9f294af7612 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/index_pattern_edit.tsx @@ -0,0 +1,57 @@ +/* + * 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, { useCallback } from 'react'; +import { isEqual } from 'lodash'; +import { css } from '@emotion/css'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { useDefaultIndexPattern } from '../../../../../../hooks/use_default_index_pattern'; +import type { FieldHook } from '../../../../../../../../shared_imports'; +import { Field } from '../../../../../../../../shared_imports'; +import * as i18n from './translations'; + +interface IndexPatternFieldProps { + field: FieldHook; +} + +export function IndexPatternField({ field }: IndexPatternFieldProps): JSX.Element { + const defaultIndexPattern = useDefaultIndexPattern(); + const isIndexModified = !isEqual(field.value, defaultIndexPattern); + + const handleResetIndices = useCallback( + () => field.setValue(defaultIndexPattern), + [field, defaultIndexPattern] + ); + + return ( + } + idAria="indexPatternEdit" + data-test-subj="indexPatternEdit" + euiFieldProps={{ + fullWidth: true, + placeholder: '', + }} + labelAppend={ + isIndexModified ? ( + + {i18n.RESET_DEFAULT_INDEX} + + ) : undefined + } + /> + ); +} + +const xxsHeight = css` + height: 16px; +`; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/translations.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/translations.tsx new file mode 100644 index 0000000000000..c1aede50af35f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/data_source/translations.tsx @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const RESET_DEFAULT_INDEX = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton', + { + defaultMessage: 'Reset to default index patterns', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query.tsx deleted file mode 100644 index 69a00436b6992..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query.tsx +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useCallback } from 'react'; -import useToggle from 'react-use/lib/useToggle'; -import { css } from '@emotion/css'; -import { EuiButtonEmpty } from '@elastic/eui'; -import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { FormSchema, FormData } from '../../../../../../../shared_imports'; -import { HiddenField, UseField } from '../../../../../../../shared_imports'; -import { schema } from '../../../../../../rule_creation_ui/components/step_define_rule/schema'; -import { QueryBarDefineRule } from '../../../../../../rule_creation_ui/components/query_bar'; -import type { FieldValueQueryBar } from '../../../../../../rule_creation_ui/components/query_bar'; -import * as stepDefineRuleI18n from '../../../../../../rule_creation_ui/components/step_define_rule/translations'; -import { useRuleIndexPattern } from '../../../../../../rule_creation_ui/pages/form'; -import { - DataSourceType as DataSourceTypeSnakeCase, - KqlQueryLanguage, - KqlQueryType, - RuleQuery, - SavedQueryId, - RuleKqlQuery, -} from '../../../../../../../../common/api/detection_engine'; -import type { - DiffableRule, - DiffableRuleTypes, - InlineKqlQuery, - SavedKqlQuery, -} from '../../../../../../../../common/api/detection_engine'; -import { useDefaultIndexPattern } from '../../../use_default_index_pattern'; -import { DataSourceType } from '../../../../../../../detections/pages/detection_engine/rules/types'; -import { isFilters } from '../../../helpers'; -import type { SetRuleQuery } from '../../../../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; -import { useRuleFromTimeline } from '../../../../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; -import { useGetSavedQuery } from '../../../../../../../detections/pages/detection_engine/rules/use_get_saved_query'; - -export const kqlQuerySchema = { - ruleType: schema.ruleType, - queryBar: schema.queryBar, -} as FormSchema<{ - ruleType: DiffableRuleTypes; - queryBar: FieldValueQueryBar; -}>; - -interface KqlQueryEditProps { - finalDiffableRule: DiffableRule; - setValidity: (isValid: boolean) => void; - setFieldValue: (fieldName: string, fieldValue: unknown) => void; -} - -export function KqlQueryEdit({ - finalDiffableRule, - setValidity, - setFieldValue, -}: KqlQueryEditProps): JSX.Element { - const defaultIndexPattern = useDefaultIndexPattern(); - const indexPatternParameters = getUseRuleIndexPatternParameters( - finalDiffableRule, - defaultIndexPattern - ); - const { indexPattern, isIndexPatternLoading } = useRuleIndexPattern(indexPatternParameters); - - const [isTimelineSearchOpen, toggleIsTimelineSearchOpen] = useToggle(false); - - const handleSetRuleFromTimeline = useCallback( - ({ queryBar: timelineQueryBar }) => { - setFieldValue('queryBar', timelineQueryBar); - }, - [setFieldValue] - ); - - const { onOpenTimeline } = useRuleFromTimeline(handleSetRuleFromTimeline); - - const isSavedQueryRule = finalDiffableRule.type === 'saved_query'; - - const { savedQuery } = useGetSavedQuery({ - savedQueryId: getSavedQueryId(finalDiffableRule), - ruleType: finalDiffableRule.type, - }); - - return ( - <> - - - ), - }} - component={QueryBarDefineRule} - componentProps={{ - indexPattern, - isLoading: isIndexPatternLoading, - openTimelineSearch: isTimelineSearchOpen, - onCloseTimelineSearch: toggleIsTimelineSearchOpen, - onValidityChange: setValidity, - onOpenTimeline, - isDisabled: isSavedQueryRule, - defaultSavedQuery: savedQuery, - resetToSavedQuery: isSavedQueryRule, - }} - /> - - ); -} - -const timelineButtonClassName = css` - height: 18px; - font-size: 12px; -`; - -function ImportTimelineQueryButton({ - handleOpenTimelineSearch, -}: { - handleOpenTimelineSearch: () => void; -}) { - return ( - - {stepDefineRuleI18n.IMPORT_TIMELINE_QUERY} - - ); -} - -export function kqlQuerySerializer(formData: FormData): { - kql_query: RuleKqlQuery; -} { - const formValue = formData as { ruleType: Type; queryBar: FieldValueQueryBar }; - - if (formValue.ruleType === 'saved_query') { - const savedQueryId = SavedQueryId.parse(formValue.queryBar.saved_id); - - const savedKqlQuery: SavedKqlQuery = { - type: KqlQueryType.saved_query, - saved_query_id: savedQueryId, - }; - - return { - kql_query: savedKqlQuery, - }; - } - - const query = RuleQuery.parse(formValue.queryBar.query.query); - const language = KqlQueryLanguage.parse(formValue.queryBar.query.language); - - const inlineKqlQuery: InlineKqlQuery = { - type: KqlQueryType.inline_query, - query, - language, - filters: formValue.queryBar.filters, - }; - - return { kql_query: inlineKqlQuery }; -} - -export function kqlQueryDeserializer( - fieldValue: FormData, - finalDiffableRule: DiffableRule -): { - ruleType: Type; - queryBar: FieldValueQueryBar; -} { - const parsedFieldValue = RuleKqlQuery.parse(fieldValue); - - if (parsedFieldValue.type === KqlQueryType.inline_query) { - const returnValue = { - ruleType: finalDiffableRule.type, - queryBar: { - query: { - query: parsedFieldValue.query, - language: parsedFieldValue.language, - }, - filters: isFilters(parsedFieldValue.filters) ? parsedFieldValue.filters : [], - saved_id: null, - }, - }; - - return returnValue; - } - - const returnValue = { - ruleType: finalDiffableRule.type, - queryBar: { - query: { - query: '', - language: '', - }, - filters: [], - saved_id: parsedFieldValue.saved_query_id, - }, - }; - - return returnValue; -} - -interface UseRuleIndexPatternParameters { - dataSourceType: DataSourceType; - index: string[]; - dataViewId: string | undefined; -} - -function getUseRuleIndexPatternParameters( - finalDiffableRule: DiffableRule, - defaultIndexPattern: string[] -): UseRuleIndexPatternParameters { - if (!('data_source' in finalDiffableRule) || !finalDiffableRule.data_source) { - return { - dataSourceType: DataSourceType.IndexPatterns, - index: defaultIndexPattern, - dataViewId: undefined, - }; - } - if (finalDiffableRule.data_source.type === DataSourceTypeSnakeCase.data_view) { - return { - dataSourceType: DataSourceType.DataView, - index: [], - dataViewId: finalDiffableRule.data_source.data_view_id, - }; - } - return { - dataSourceType: DataSourceType.IndexPatterns, - index: finalDiffableRule.data_source.index_patterns, - dataViewId: undefined, - }; -} - -function getSavedQueryId(diffableRule: DiffableRule): string | undefined { - if (diffableRule.type === 'saved_query' && 'saved_query_id' in diffableRule.kql_query) { - return diffableRule.kql_query.saved_query_id; - } - - return undefined; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/index.ts new file mode 100644 index 0000000000000..f04cdb36c19a9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './kql_query_edit_form'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/kql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/kql_query_edit.tsx new file mode 100644 index 0000000000000..e1e4ddb0d14e9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/kql_query_edit.tsx @@ -0,0 +1,139 @@ +/* + * 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, { useCallback } from 'react'; +import useToggle from 'react-use/lib/useToggle'; +import { css } from '@emotion/css'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { schema } from '../../../../../../../rule_creation_ui/components/step_define_rule/schema'; +import { HiddenField, UseField } from '../../../../../../../../shared_imports'; +import { QueryBarDefineRule } from '../../../../../../../rule_creation_ui/components/query_bar'; +import * as stepDefineRuleI18n from '../../../../../../../rule_creation_ui/components/step_define_rule/translations'; +import { useRuleIndexPattern } from '../../../../../../../rule_creation_ui/pages/form'; +import { DataSourceType as DataSourceTypeSnakeCase } from '../../../../../../../../../common/api/detection_engine'; +import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; +import { useDefaultIndexPattern } from '../../../../../../hooks/use_default_index_pattern'; +import { DataSourceType } from '../../../../../../../../detections/pages/detection_engine/rules/types'; +import type { SetRuleQuery } from '../../../../../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; +import { useRuleFromTimeline } from '../../../../../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; +import { useGetSavedQuery } from '../../../../../../../../detections/pages/detection_engine/rules/use_get_saved_query'; +import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; + +export function KqlQueryEdit({ + finalDiffableRule, + setFieldValue, +}: RuleFieldEditComponentProps): JSX.Element { + const defaultIndexPattern = useDefaultIndexPattern(); + const indexPatternParameters = getRuleIndexPatternParameters( + finalDiffableRule, + defaultIndexPattern + ); + const { indexPattern, isIndexPatternLoading } = useRuleIndexPattern(indexPatternParameters); + + const [isTimelineSearchOpen, toggleIsTimelineSearchOpen] = useToggle(false); + + const handleSetRuleFromTimeline = useCallback( + ({ queryBar: timelineQueryBar }) => { + setFieldValue('queryBar', timelineQueryBar); + }, + [setFieldValue] + ); + + const { onOpenTimeline } = useRuleFromTimeline(handleSetRuleFromTimeline); + + const isSavedQueryRule = finalDiffableRule.type === 'saved_query'; + + const { savedQuery } = useGetSavedQuery({ + savedQueryId: getSavedQueryId(finalDiffableRule), + ruleType: finalDiffableRule.type, + }); + + return ( + <> + + + ), + }} + component={QueryBarDefineRule} + componentProps={{ + indexPattern, + isLoading: isIndexPatternLoading, + openTimelineSearch: isTimelineSearchOpen, + onCloseTimelineSearch: toggleIsTimelineSearchOpen, + onOpenTimeline, + isDisabled: isSavedQueryRule, + defaultSavedQuery: savedQuery, + resetToSavedQuery: isSavedQueryRule, + }} + /> + + ); +} + +const timelineButtonClassName = css` + height: 18px; + font-size: 12px; +`; + +function ImportTimelineQueryButton({ + handleOpenTimelineSearch, +}: { + handleOpenTimelineSearch: () => void; +}) { + return ( + + {stepDefineRuleI18n.IMPORT_TIMELINE_QUERY} + + ); +} + +interface RuleIndexPatternParameters { + dataSourceType: DataSourceType; + index: string[]; + dataViewId: string | undefined; +} + +function getRuleIndexPatternParameters( + finalDiffableRule: DiffableRule, + defaultIndexPattern: string[] +): RuleIndexPatternParameters { + if (!('data_source' in finalDiffableRule) || !finalDiffableRule.data_source) { + return { + dataSourceType: DataSourceType.IndexPatterns, + index: defaultIndexPattern, + dataViewId: undefined, + }; + } + if (finalDiffableRule.data_source.type === DataSourceTypeSnakeCase.data_view) { + return { + dataSourceType: DataSourceType.DataView, + index: [], + dataViewId: finalDiffableRule.data_source.data_view_id, + }; + } + return { + dataSourceType: DataSourceType.IndexPatterns, + index: finalDiffableRule.data_source.index_patterns, + dataViewId: undefined, + }; +} + +function getSavedQueryId(diffableRule: DiffableRule): string | undefined { + if (diffableRule.type === 'saved_query' && 'saved_query_id' in diffableRule.kql_query) { + return diffableRule.kql_query.saved_query_id; + } + + return undefined; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/kql_query_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/kql_query_edit_form.tsx new file mode 100644 index 0000000000000..b6bab6e57976c --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/kql_query/kql_query_edit_form.tsx @@ -0,0 +1,118 @@ +/* + * 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 { Type } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; +import { schema } from '../../../../../../../rule_creation_ui/components/step_define_rule/schema'; +import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; +import type { FieldValueQueryBar } from '../../../../../../../rule_creation_ui/components/query_bar'; +import { + KqlQueryLanguage, + KqlQueryType, + RuleQuery, + SavedQueryId, + RuleKqlQuery, +} from '../../../../../../../../../common/api/detection_engine'; +import type { + DiffableRule, + DiffableRuleTypes, + InlineKqlQuery, + SavedKqlQuery, +} from '../../../../../../../../../common/api/detection_engine'; +import { isFilters } from '../../../../helpers'; +import { KqlQueryEdit } from './kql_query_edit'; + +export function KqlQueryEditForm(): JSX.Element { + return ( + + ); +} + +const kqlQuerySchema = { + ruleType: schema.ruleType, + queryBar: schema.queryBar, +} as FormSchema<{ + ruleType: DiffableRuleTypes; + queryBar: FieldValueQueryBar; +}>; + +function kqlQueryDeserializer( + fieldValue: FormData, + finalDiffableRule: DiffableRule +): { + ruleType: Type; + queryBar: FieldValueQueryBar; +} { + const parsedFieldValue = RuleKqlQuery.parse(fieldValue.kql_query); + + if (parsedFieldValue.type === KqlQueryType.inline_query) { + const returnValue = { + ruleType: finalDiffableRule.type, + queryBar: { + query: { + query: parsedFieldValue.query, + language: parsedFieldValue.language, + }, + filters: isFilters(parsedFieldValue.filters) ? parsedFieldValue.filters : [], + saved_id: null, + }, + }; + + return returnValue; + } + + const returnValue = { + ruleType: finalDiffableRule.type, + queryBar: { + query: { + query: '', + language: '', + }, + filters: [], + saved_id: parsedFieldValue.saved_query_id, + }, + }; + + return returnValue; +} + +function kqlQuerySerializer(formData: FormData): { + kql_query: RuleKqlQuery; +} { + const formValue = formData as { ruleType: Type; queryBar: FieldValueQueryBar }; + + if (formValue.ruleType === 'saved_query') { + const savedQueryId = SavedQueryId.parse(formValue.queryBar.saved_id); + + const savedKqlQuery: SavedKqlQuery = { + type: KqlQueryType.saved_query, + saved_query_id: savedQueryId, + }; + + return { + kql_query: savedKqlQuery, + }; + } + + const query = RuleQuery.parse(formValue.queryBar.query.query); + const language = KqlQueryLanguage.parse(formValue.queryBar.query.language); + + const inlineKqlQuery: InlineKqlQuery = { + type: KqlQueryType.inline_query, + query, + language, + filters: formValue.queryBar.filters, + }; + + return { kql_query: inlineKqlQuery }; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_component_props.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_component_props.ts new file mode 100644 index 0000000000000..46ba6efdd847b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_component_props.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FormData } from '../../../../../../../shared_imports'; +import type { DiffableRule } from '../../../../../../../../common/api/detection_engine'; + +export interface RuleFieldEditComponentProps { + finalDiffableRule: DiffableRule; + setFieldValue: SetFieldValueFn; + resetForm: ResetFormFn; +} + +type SetFieldValueFn = (fieldName: string, fieldValue: unknown) => void; + +export type ResetFormFn = (options?: { + resetValues?: boolean; + defaultValue?: Partial | undefined; +}) => void; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/field_form_wrapper.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_form_wrapper.tsx similarity index 61% rename from x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/field_form_wrapper.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_form_wrapper.tsx index b4a53ee7aea0a..26a2574489b16 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/field_form_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_form_wrapper.tsx @@ -5,28 +5,30 @@ * 2.0. */ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { EuiButtonEmpty, EuiFlexGroup } from '@elastic/eui'; -import { useForm, Form } from '../../../../../../shared_imports'; -import type { FormSchema, FormData } from '../../../../../../shared_imports'; +import { useForm, Form } from '../../../../../../../shared_imports'; +import type { FormSchema, FormData } from '../../../../../../../shared_imports'; import type { DiffableAllFields, DiffableRule, -} from '../../../../../../../common/api/detection_engine'; -import { useFinalSideContext } from '../final_side/final_side_context'; -import { useDiffableRuleContext } from '../diffable_rule_context'; -import * as i18n from '../translations'; +} from '../../../../../../../../common/api/detection_engine'; +import { useFinalSideContext } from '../../final_side/final_side_context'; +import { useDiffableRuleContext } from '../../diffable_rule_context'; +import * as i18n from '../../translations'; +import type { RuleFieldEditComponentProps } from './rule_field_edit_component_props'; -type FieldComponent = React.ComponentType<{ - finalDiffableRule: DiffableRule; - setValidity: (isValid: boolean) => void; - setFieldValue: (fieldName: string, fieldValue: unknown) => void; -}>; +type RuleFieldEditComponent = React.ComponentType; -interface FieldFormWrapperProps { - component: FieldComponent; - fieldFormSchema: FormSchema; - deserializer?: (fieldValue: FormData, finalDiffableRule: DiffableRule) => FormData; +export type FieldDeserializerFn = ( + defaultRuleFieldValue: FormData, + finalDiffableRule: DiffableRule +) => FormData; + +interface RuleFieldEditFormWrapperProps { + component: RuleFieldEditComponent; + ruleFieldFormSchema: FormSchema; + deserializer?: FieldDeserializerFn; serializer?: (formData: FormData) => FormData; } @@ -35,30 +37,23 @@ interface FieldFormWrapperProps { * * @param {Object} props - Component props. * @param {React.ComponentType} props.component - Field component to be wrapped. - * @param {FormSchema} props.fieldFormSchema - Configuration schema for the field. + * @param {FormSchema} props.ruleFieldFormSchema - Configuration schema for the field. * @param {Function} props.deserializer - Deserializer prepares initial form data. It converts field value from a DiffableRule format to a format used by the form. * @param {Function} props.serializer - Serializer prepares form data for submission. It converts form data back to a DiffableRule format. */ -export function FieldFormWrapper({ +export function RuleFieldEditFormWrapper({ component: FieldComponent, - fieldFormSchema, + ruleFieldFormSchema, deserializer, serializer, -}: FieldFormWrapperProps) { +}: RuleFieldEditFormWrapperProps) { const { fieldName, setReadOnlyMode } = useFinalSideContext(); const { finalDiffableRule, setRuleFieldResolvedValue } = useDiffableRuleContext(); const deserialize = useCallback( - (defaultValue: FormData): FormData => { - if (!deserializer) { - return defaultValue; - } - - const rule = finalDiffableRule as Record; - const fieldValue = rule[fieldName] as FormData; - return deserializer(fieldValue, finalDiffableRule); - }, - [deserializer, fieldName, finalDiffableRule] + (defaultValue: FormData): FormData => + deserializer ? deserializer(defaultValue, finalDiffableRule) : defaultValue, + [deserializer, finalDiffableRule] ); const handleSubmit = useCallback( @@ -78,16 +73,18 @@ export function FieldFormWrapper({ ); const { form } = useForm({ - schema: fieldFormSchema, + schema: ruleFieldFormSchema, defaultValue: getDefaultValue(fieldName, finalDiffableRule), deserializer: deserialize, serializer, onSubmit: handleSubmit, }); - const [validity, setValidity] = useState(undefined); - - const isValid = validity === undefined ? form.isValid : validity; + // form.isValid has `undefined` value until all fields are dirty. + // Run the validation upfront to visualize form validity state. + useEffect(() => { + form.validate(); + }, [form]); return ( <> @@ -95,15 +92,15 @@ export function FieldFormWrapper({ {i18n.CANCEL_BUTTON_LABEL} - + {i18n.SAVE_BUTTON_LABEL}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/final_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/final_edit.tsx index 698d138208d70..5c32c8edc7924 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/final_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/final_edit.tsx @@ -20,9 +20,11 @@ import type { UpgradeableThreatMatchFields, UpgradeableThresholdFields, UpgradeableNewTermsFields, + UpgradeableEqlFields, } from '../../../../model/prebuilt_rule_upgrade/fields'; import { isCommonFieldName } from '../../../../model/prebuilt_rule_upgrade/fields'; import { useFinalSideContext } from '../final_side/final_side_context'; +import { EqlRuleFieldEdit } from './eql_rule_field_edit'; export function FinalEdit() { const { finalDiffableRule } = useDiffableRuleContext(); @@ -40,7 +42,7 @@ export function FinalEdit() { case 'saved_query': return ; case 'eql': - return {'Rule type not yet implemented'}; + return ; case 'esql': return {'Rule type not yet implemented'}; case 'threat_match': 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 183200aef1c43..e5d01b3cfff7d 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 @@ -6,14 +6,9 @@ */ import React from 'react'; -import { FieldFormWrapper } from './field_form_wrapper'; -import { - KqlQueryEdit, - kqlQuerySchema, - kqlQuerySerializer, - kqlQueryDeserializer, -} from './fields/kql_query'; import type { UpgradeableNewTermsFields } from '../../../../model/prebuilt_rule_upgrade/fields'; +import { KqlQueryEditForm } from './fields/kql_query'; +import { DataSourceEditForm } from './fields/data_source'; interface NewTermsRuleFieldEditProps { fieldName: UpgradeableNewTermsFields; @@ -22,14 +17,9 @@ interface NewTermsRuleFieldEditProps { export function NewTermsRuleFieldEdit({ fieldName }: NewTermsRuleFieldEditProps) { switch (fieldName) { case 'kql_query': - return ( - - ); + return ; + case 'data_source': + return ; default: return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx index fa573e6339e9f..851b8f6c95fb5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx @@ -6,14 +6,9 @@ */ import React from 'react'; -import { FieldFormWrapper } from './field_form_wrapper'; -import { - KqlQueryEdit, - kqlQuerySchema, - kqlQuerySerializer, - kqlQueryDeserializer, -} from './fields/kql_query'; import type { UpgradeableSavedQueryFields } from '../../../../model/prebuilt_rule_upgrade/fields'; +import { KqlQueryEditForm } from './fields/kql_query'; +import { DataSourceEditForm } from './fields/data_source'; interface SavedQueryRuleFieldEditProps { fieldName: UpgradeableSavedQueryFields; @@ -22,14 +17,9 @@ interface SavedQueryRuleFieldEditProps { export function SavedQueryRuleFieldEdit({ fieldName }: SavedQueryRuleFieldEditProps) { switch (fieldName) { case 'kql_query': - return ( - - ); + return ; + case 'data_source': + return ; default: return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/threat_match_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/threat_match_rule_field_edit.tsx index 5f2adbb113fd5..6a92f7372563e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/threat_match_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/threat_match_rule_field_edit.tsx @@ -6,14 +6,9 @@ */ import React from 'react'; -import { FieldFormWrapper } from './field_form_wrapper'; -import { - KqlQueryEdit, - kqlQuerySchema, - kqlQuerySerializer, - kqlQueryDeserializer, -} from './fields/kql_query'; import type { UpgradeableThreatMatchFields } from '../../../../model/prebuilt_rule_upgrade/fields'; +import { KqlQueryEditForm } from './fields/kql_query'; +import { DataSourceEditForm } from './fields/data_source'; interface ThreatMatchRuleFieldEditProps { fieldName: UpgradeableThreatMatchFields; @@ -22,14 +17,9 @@ interface ThreatMatchRuleFieldEditProps { export function ThreatMatchRuleFieldEdit({ fieldName }: ThreatMatchRuleFieldEditProps) { switch (fieldName) { case 'kql_query': - return ( - - ); + return ; + case 'data_source': + return ; default: return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/threshold_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/threshold_rule_field_edit.tsx index 4975ca49205e7..d1fc2372d7a16 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/threshold_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/threshold_rule_field_edit.tsx @@ -6,14 +6,9 @@ */ import React from 'react'; -import { FieldFormWrapper } from './field_form_wrapper'; -import { - KqlQueryEdit, - kqlQuerySchema, - kqlQuerySerializer, - kqlQueryDeserializer, -} from './fields/kql_query'; import type { UpgradeableThresholdFields } from '../../../../model/prebuilt_rule_upgrade/fields'; +import { KqlQueryEditForm } from './fields/kql_query'; +import { DataSourceEditForm } from './fields/data_source'; interface ThresholdRuleFieldEditProps { fieldName: UpgradeableThresholdFields; @@ -22,14 +17,9 @@ interface ThresholdRuleFieldEditProps { export function ThresholdRuleFieldEdit({ fieldName }: ThresholdRuleFieldEditProps) { switch (fieldName) { case 'kql_query': - return ( - - ); + return ; + case 'data_source': + 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/use_default_index_pattern.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx similarity index 63% rename from x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/use_default_index_pattern.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx index 3482df562bac0..b5ca86c6f1f57 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/use_default_index_pattern.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx @@ -5,23 +5,21 @@ * 2.0. */ -import { useKibana } from '../../../../common/lib/kibana/kibana_react'; -import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useKibana } from '../../../common/lib/kibana/kibana_react'; +import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; /** * Gets the default index pattern for cases when rule has neither index patterns or data view. * First checks the config value. If it's not present falls back to the hardcoded default value. */ -export function useDefaultIndexPattern() { +export function useDefaultIndexPattern(): string[] { const { services } = useKibana(); const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( 'prebuiltRulesCustomizationEnabled' ); - if (isPrebuiltRulesCustomizationEnabled) { - return services.settings.client.get(DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN); - } - - return []; + return isPrebuiltRulesCustomizationEnabled + ? services.settings.client.get(DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN) + : []; } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 2d79dd1aa40f2..f4a38ce6ee929 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -36043,7 +36043,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError": "Une requête personnalisée est requise.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredEmptyError": "Toutes les correspondances requièrent un champ et un champ d'index des menaces.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredError": "Au moins une correspondance d'indicateur est requise.", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.dataViewSelectorFieldRequired": "Veuillez sélectionner une vue des données ou un modèle d'index disponible.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.EqlQueryBarLabel": "Requête EQL", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlQueryFieldRequiredError": "Une requête EQL est requise.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlSequenceSuppressionDisableText": "La suppression n'est pas prise en charge pour les requêtes de séquence EQL.", @@ -36087,7 +36086,6 @@ "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.outputIndiceNameFieldRequiredError": "Au minimum un modèle d'indexation 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", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1b438d92fd062..27b79ebde86f6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -35787,7 +35787,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError": "カスタムクエリが必要です。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredEmptyError": "すべての一致には、フィールドと脅威インデックスフィールドの両方が必要です。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredError": "1 つ以上のインジケーター一致が必要です。", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.dataViewSelectorFieldRequired": "使用可能なデータビューまたはインデックスパターンを選択してください。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.EqlQueryBarLabel": "EQL クエリ", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlQueryFieldRequiredError": "EQLクエリは必須です。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlSequenceSuppressionDisableText": "EQLシーケンスクエリでは抑制はサポートされていません。", @@ -35831,7 +35830,6 @@ "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.outputIndiceNameFieldRequiredError": "インデックスパターンが最低1つ必要です。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError": "URLの形式が無効です", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton": "デフォルトインデックスパターンにリセット", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.rulePreviewTitle": "ルールプレビュー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 504465351a958..ee8601400fef0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -35831,7 +35831,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError": "需要定制查询。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredEmptyError": "所有匹配项都需要字段和威胁索引字段。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredError": "至少需要一个指标匹配。", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.dataViewSelectorFieldRequired": "请选择可用的数据视图或索引模式。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.EqlQueryBarLabel": "EQL 查询", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlQueryFieldRequiredError": "EQL 查询必填。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlSequenceSuppressionDisableText": "EQL 序列查询不支持阻止。", @@ -35875,7 +35874,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText": "选择字段", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel": "字段", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin": "至少需要一个字段。", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.outputIndiceNameFieldRequiredError": "至少需要一种索引模式。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError": "URL 的格式无效", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton": "重置为默认索引模式", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.rulePreviewTitle": "规则预览",